def get_performance_report(self) -> PerformanceReport: reports = GsReportApi.get_reports( limit=1, position_source_type='Portfolio', position_source_id=self.id, report_type='Portfolio Performance Analytics') return PerformanceReport.from_target(reports[0]) if reports else None
def pnl(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 PnL of a portfolio :param portfolio_id: id of portfolio :param start_date: start date for getting pnl :param end_date: end date for getting pnl :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 values """ reports = GsPortfolioApi.get_reports(portfolio_id) performance_report_id = "" for report in reports: if report.type == ReportType.Portfolio_Performance_Analytics: performance_report_id = report.id data = PerformanceReport.get_pnl(performance_report_id, start_date, end_date) df = pd.DataFrame.from_records(data) df.set_index('date', inplace=True) return _extract_series_from_df(df, QueryType.PNL, True)
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, dtype="float64") # 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(datetime.date(2020, 1, 1), datetime.date(2019, 1, 3)): actual = mr.normalized_performance('MP1', k) assert all((actual.values - v.values) < 0.01) replace.restore()
def get_performance_report(self) -> PerformanceReport: reports = GsReportApi.get_reports( limit=100, position_source_type='Portfolio', position_source_id=self.id, report_type='Portfolio Performance Analytics') if len(reports) == 0: raise MqError('This portfolio has no performance report.') return PerformanceReport.from_target(reports[0])
def test_normalized_performance_default_aum(): idx = pd.date_range('2020-01-02', freq='D', periods=3) expected = pd.Series(data=[1, 1 / 0.8, 1 / 0.6], index=idx, name='normalizedPerformance', dtype='float64') with DataContext(datetime.date(2020, 1, 1), datetime.date(2019, 1, 3)): df = MarketDataResponseFrame(data=ppa_data, dtype="float64") replace = Replacer() # mock GsPortfolioApi.get_reports() 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_many_measures() mock = replace( 'gs_quant.markets.report.PerformanceReport.get_many_measures', Mock()) mock.return_value = df # mock PerformanceReport.get_custom_aum() mock = replace( 'gs_quant.api.gs.portfolios.GsPortfolioApi.get_custom_aum', Mock()) mock.return_value = aum # 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 = replace( 'gs_quant.api.gs.portfolios.GsPortfolioApi.get_portfolio', Mock()) mock.return_value = Portfolio('USD', 'P1', id_='MP1') actual = mr.normalized_performance('MP1', None) assert all(actual.values == expected.values) replace.restore()
def get_reports(self) -> List[Report]: if self.positioned_entity_type == EntityType.PORTFOLIO: reports_as_target = GsPortfolioApi.get_reports(portfolio_id=self.id) 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)) else: report_objects.append(Report.from_target(report)) return report_objects raise NotImplementedError
def test_normalized_performance_no_custom_aum(): with DataContext(datetime.date(2020, 1, 1), datetime.date(2019, 1, 3)): df = MarketDataResponseFrame(data=ppa_data, dtype="float64") replace = Replacer() # mock GsPortfolioApi.get_reports() 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_many_measures() mock = replace( 'gs_quant.markets.report.PerformanceReport.get_many_measures', Mock()) mock.return_value = df # mock PerformanceReport.get_portfolio_constituents() mock = replace( 'gs_quant.markets.report.PerformanceReport.get_portfolio_constituents', Mock()) mock.return_value = MarketDataResponseFrame(data=constituents_data, dtype="float64") # mock PerformanceReport.get_custom_aum() mock = replace( 'gs_quant.api.gs.portfolios.GsPortfolioApi.get_custom_aum', Mock()) mock.return_value = pd.DataFrame({}) # 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 pytest.raises(MqError): mr.normalized_performance('MP1', 'Custom AUM') replace.restore()
def get_reports(self) -> List[Report]: """ Get a list of all reports associated with the portfolio :return: list of Report objects """ reports = [] reports_as_targets = GsPortfolioApi.get_reports(self.__portfolio_id) for report_target in reports_as_targets: if report_target.type in [ ReportType.Portfolio_Factor_Risk, ReportType.Asset_Factor_Risk ]: reports.append(FactorRiskReport.from_target(report_target)) if report_target.type == ReportType.Portfolio_Performance_Analytics: reports.append(PerformanceReport.from_target(report_target)) return reports
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_financial_conditions_index(): data = { 'pnl': [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 = ('PNL', ) replace = Replacer() # mock PerformanceReport.get_pnl() mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = df # mock GsPortfolioApi.get_reports() 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() 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(datetime.date(2020, 1, 1), datetime.date(2019, 1, 3)): actual = mp.portfolio_pnl('MP1') assert actual.index.equals(idx) assert all(actual.values == data['pnl']) replace.restore()
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")
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(datetime.date(2020, 1, 1), datetime.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(datetime.date(2020, 1, 1), datetime.date(2019, 1, 3)): actual = mr.long_pnl('MP1') assert all(actual.values == expected.values) replace.restore()
def normalized_performance(report_id: str, aum_source: 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 aum_source: source to normalize pnl from, default is the aum source on your portfolio, if no aum source is set on your portfolio the default is gross :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 based on AUM source. If :math:`aum_source` is "Custom AUM": We read AUM from custom AUM uploaded to that portfolio and normalize performance based on that exposure If :math:`aum_source` is one of: Long, Short, RiskAumSource.Net, AumSource.Gross, we take these exposures from the calculated exposures based on daily positions :math:`NP(L/S)_{t} = SUM( PNL(L/S)_{t}/ ( EXP(L/S)_{t} ) - cPNL(L/S)_{t-1) ) if ( EXP(L/S)_{t} ) - cPNL(L/S)_{t-1)) > 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} * (EXP(L)_{t} / AUM_{t}) + NP(S)_{t} * (EXP(S)_{t} / AUM_{t}) + 1` This takes into account varying AUM and adjusts for exposure change due to PNL 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:`AUM_{t}` is portfolio 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 - relativedelta(1) end_date = DataContext.current.end_time start_date = start_date.date() end_date = end_date.date() performance_report = PerformanceReport.get(report_id) if not aum_source: port = GsPortfolioApi.get_portfolio( performance_report.position_source_id) aum_source = port.aum_source if port.aum_source else RiskAumSource.Gross else: aum_source = RiskAumSource(aum_source) constituent_data = performance_report.get_portfolio_constituents( fields=['assetId', 'pnl', 'quantity', 'netExposure'], start_date=start_date, end_date=end_date).set_index('date') aum_col_name = aum_source.value.lower() aum_col_name = f'{aum_col_name}Exposure' if aum_col_name != 'custom aum' else 'aum' # 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") # Get aum source data if aum_source == RiskAumSource.Custom_AUM: custom_aum = pd.DataFrame( GsPortfolioApi.get_custom_aum( performance_report.position_source_id, start_date, end_date)) if custom_aum.empty: raise MqError( f'No custom AUM for portfolio {performance_report.position_source_id} between dates {start_date},' f' {end_date}') data = pd.DataFrame.from_records(custom_aum).set_index(['date']) else: data = performance_report.get_many_measures( [aum_col_name], start_date, end_date).set_index(['date']) long_side = long_side.join(data[[f'{aum_col_name}']], how='inner') short_side = short_side.join(data[[f'{aum_col_name}']], how='inner') long_side['longRetWeighted'] = (long_side['longMetrics'] - 1) * long_side['exposure'] * \ (1 / long_side[f'{aum_col_name}']) short_side['shortRetWeighted'] = (short_side['shortMetrics'] - 1) * short_side['exposure'] *\ (1 / short_side[f'{aum_col_name}']) 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()
from gs_quant.session import * from gs_quant.target.reports import ReportStatus, PositionSourceType, ReportType, ReportParameters, 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',
def normalized_performance(report_id: str, aum_source: 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 aum_source: source to normalize pnl from, default is the aum source on your portfolio, if no aum source is set on your portfolio the default is gross :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 based on AUM source. If :math:`aum_source` is "Custom AUM": We read AUM from custom AUM uploaded to that portfolio and normalize performance based on that exposure If :math:`aum_source` is one of: Long, Short, RiskAumSource.Net, AumSource.Gross, we take these exposures from the calculated exposures based on daily positions :math:`NP_{t} = SUM( PNL_{t}/ ( AUM_{t} ) - cPNL_{t-1) ) if ( AUM_{t} ) - cPNL_{t-1) ) > 0 else: 1/ SUM( PNL_{t}/ ( AUM_{t} ) - cPNL_{t-1) )` This takes into account varying AUM and adjusts for exposure change due to PNL where :math:`cPNL_{t-1}` is your performance reports cumulative PNL at date t-1 where :math:`PNL_{t}` is your performance reports pnl at date t where :math:`AUM_{t}` is portfolio exposure on date t """ start_date = DataContext.current.start_date end_date = DataContext.current.end_date ppa_report = PerformanceReport.get(report_id) if not aum_source: port = GsPortfolioApi.get_portfolio(ppa_report.position_source_id) aum_source = port.aum_source if port.aum_source else RiskAumSource.Net else: aum_source = RiskAumSource(aum_source) aum_col_name = aum_source.value.lower() aum_col_name = f'{aum_col_name}Exposure' if aum_col_name != 'custom aum' else 'aum' measures = [aum_col_name, 'pnl' ] if aum_source != RiskAumSource.Custom_AUM else ['pnl'] data = ppa_report.get_many_measures(measures, start_date, end_date) data.loc[0, 'pnl'] = 0 data['cumulativePnlT-1'] = data['pnl'].cumsum(axis=0) data = pd.DataFrame.from_records(data).set_index(['date']) if aum_source == RiskAumSource.Custom_AUM: custom_aum = pd.DataFrame( GsPortfolioApi.get_custom_aum(ppa_report.position_source_id, start_date, end_date)) if custom_aum.empty: raise MqError( f'No custom AUM for portfolio {ppa_report.position_source_id} between dates {start_date},' f' {end_date}') custom_aum = pd.DataFrame.from_records(custom_aum).set_index(['date']) data = data.join(custom_aum.loc[:, aum_col_name], how='inner') if aum_source == RiskAumSource.Short: data[f'{aum_col_name}'] = -1 * data[f'{aum_col_name}'] data['normalizedExposure'] = data[f'{aum_col_name}'] - data[ 'cumulativePnlT-1'] data[ 'pnlOverNormalizedExposure'] = data['pnl'] / data['normalizedExposure'] data['normalizedPerformance'] = data['pnlOverNormalizedExposure'].cumsum( axis=0) + 1 data.loc[data.normalizedExposure < 0, 'normalizedPerformance'] = 1 / data.loc[:, 'normalizedPerformance'] return pd.Series(data['normalizedPerformance'], name="normalizedPerformance").dropna()
def test_normalized_performance(): idx = pd.date_range('2020-01-02', freq='D', periods=3) expected = { RiskAumSource.Net: pd.Series(data=[1, 1 + 2 / 4, 1 + 6 / 6], index=idx, name='normalizedPerformance', dtype='float64'), RiskAumSource.Gross: pd.Series(data=[1, 1 + 2 / 1.2, 1 + 6 / 1.3], index=idx, name='normalizedPerformance', dtype='float64'), RiskAumSource.Long: pd.Series(data=[1, 1 + 2 / 1.2, 1 + 6 / 1.3], index=idx, name='normalizedPerformance', dtype='float64'), RiskAumSource.Short: pd.Series(data=[1, 1 + 2 / 1.2, 1 + 6 / 1.3], index=idx, name='normalizedPerformance', dtype='float64'), RiskAumSource.Custom_AUM: pd.Series(data=[1, 1 + 2 / 2.2, 1 + 6 / 2.4], index=idx, name='normalizedPerformance', dtype='float64') } with DataContext(datetime.date(2020, 1, 1), datetime.date(2019, 1, 3)): for k, v in expected.items(): df = MarketDataResponseFrame(data=ppa_data, dtype="float64") replace = Replacer() # mock GsPortfolioApi.get_reports() 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, dtype="float64") # mock PerformanceReport.get_many_measures() mock = replace( 'gs_quant.markets.report.PerformanceReport.get_many_measures', Mock()) mock.return_value = df # mock PerformanceReport.get_custom_aum() mock = replace( 'gs_quant.api.gs.portfolios.GsPortfolioApi.get_custom_aum', Mock()) mock.return_value = aum # 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')) actual = mr.normalized_performance('MP1', k.value) assert all(actual.values == v.values) replace.restore()
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()