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: if pos_set.reference_notional is None: incorrect_set = any([ pos.quantity is None or pos.weight is not None for pos in pos_set.positions ]) if incorrect_set: raise MqValueError( 'If you would like to upload position sets without notionals, ' 'every position must have a quantity and cannot have a weight.' ) new_sets.append(pos_set) else: new_sets.append( self._convert_pos_set_with_weights(pos_set, currency)) GsPortfolioApi.update_positions( portfolio_id=self.id, position_sets=[p.to_target() for p in new_sets]) time.sleep(3) else: raise NotImplementedError
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
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 from_portfolio_id(portfolio_id: str): portfolio = GsPortfolioApi.get_portfolio(portfolio_id) response = GsPortfolioApi.get_latest_positions(portfolio_id) 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
def set_aum_source(self, aum_source: RiskAumSource): """ Set portfolio AUM Source :param aum_source: aum source for portfolio :return: """ portfolio = GsPortfolioApi.get_portfolio(self.portfolio_id) portfolio.aum_source = aum_source GsPortfolioApi.update_portfolio(portfolio)
def set_entitlements(self, entitlements: Entitlements): """ Set the entitlements of a portfolio :param entitlements: Entitlements object """ 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 get(cls, portfolio_id: str = None, portfolio_name: str = None): 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 ret._get_instruments(position_date, True) return ret
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
def test_get_portfolio(mocker): id_1 = 'MP1' mock_response = Portfolio(id=id_1, currency='USD', name='Example Port') # mock GsSession mocker.patch.object(GsSession.__class__, 'current', return_value=GsSession.get(Environment.QA, 'client_id', 'secret')) mocker.patch.object(GsSession.current, '_get', return_value=mock_response) # run test response = GsPortfolioApi.get_portfolio(id_1) GsSession.current._get.assert_called_with('/portfolios/{id}'.format(id=id_1), cls=Portfolio) assert response == mock_response
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()
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()