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` """ self.__finish_populating_attributes_for_existing_basket() edit_inputs, rebal_inputs = self.__get_updates() if edit_inputs is None and rebal_inputs is None: 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.__error_messages.remove(ErrorMessage.UNMODIFIABLE) self.__init__(gs_asset=gs_asset) return response.as_dict()
def __validate_ticker(self, 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')
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(response.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 {status}. The current rebalance request will \ not be submitted in the meantime.') _logger.info('Your basket edits have completed successfuly. Submitting rebalance request now...') response = GsIndexApi.rebalance(self.id, rebal_inputs) return response
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()
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')
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)
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()
def __populate_current_attributes_for_existing_basket( self, gs_asset: GsAsset): self.__description = get(gs_asset, 'description', None) self.__gs_asset_type = get(gs_asset, 'type', None) self.__live_date = get(gs_asset, 'live_date', None) self.__ticker = get(gs_asset, 'xref.ticker', None) self.__clone_parent_id = get(gs_asset, 'parameters.clone_parent_id', None) self.__default_backcast = get(gs_asset, 'parameters.default_backcast', None) self.__flagship = get(gs_asset, 'parameters.flagship', None) self.__hedge_id = get(gs_asset, 'parameters.hedge_id', None) self.__return_type = get(gs_asset, 'parameters.index_calculation_type', None) last_positions = GsAssetApi.get_latest_positions(self.__id) self.__divisor = get(last_positions, 'divisor', None) last_initial_price = GsIndexApi.initial_price(gs_asset.id, DateLimit.TODAY.value) self.__initial_price = get(last_initial_price, 'price', None) report = self.__get_last_create_report() self.__include_price_history = get(report, 'parameters.include_price_history', None) self.__publish_to_bloomberg = get(report, 'parameters.publish_to_bloomberg', None) self.__publish_to_factset = get(report, 'parameters.publish_to_factset', None) self.__publish_to_reuters = get(report, 'parameters.publish_to_reuters', None)
def test_get_asset_positions_data(mocker): marquee_id = 'MQA1234567890' position_date = dt.date(2019, 2, 19) mock_response = { 'results': [ { 'underlyingAssetId': 'MA4B66MW5E27UAFU2CD', 'divisor': 8305900333.262549, 'quantity': 0.016836826158, 'positionType': 'close', 'bbid': 'EXPE UW', 'assetId': 'MA4B66MW5E27U8P32SB', 'positionDate': '2019-11-07', 'assetClassificationsGicsSector': 'Consumer Discretionary', 'closePrice': 98.29, 'ric': 'EXPE.OQ' }, ] } expected_response = [ { 'underlyingAssetId': 'MA4B66MW5E27UAFU2CD', 'divisor': 8305900333.262549, 'quantity': 0.016836826158, 'positionType': 'close', 'bbid': 'EXPE UW', 'assetId': 'MA4B66MW5E27U8P32SB', 'positionDate': '2019-11-07', 'assetClassificationsGicsSector': 'Consumer Discretionary', 'closePrice': 98.29, 'ric': 'EXPE.OQ' }, ] # 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=mock_response) # run test response = GsIndexApi.get_positions_data(marquee_id, position_date, position_date) position_date_str = position_date.isoformat() GsSession.current._get.assert_called_with( '/indices/{id}/positions/data?startDate={start_date}&endDate={end_date}' .format(id=marquee_id, start_date=position_date_str, end_date=position_date_str)) testfixtures.compare(response, expected_response)
def test_basket_initial_price(mocker): # construct mock response mock_response = {'price': 100} # setup mock session and api response mock_session() mocker.return_value = mock_response # run test response = GsIndexApi.initial_price(basket_id, date) assert response == mock_response
def test_basket_last_rebalance_data(mocker): # construct mock response mock_response = {'reportId': report_id, 'assetId': basket_id} # setup mock session and api response mock_session() mocker.return_value = mock_response # run test response = GsIndexApi.last_rebalance_data(basket_id) assert response == mock_response
def test_basket_rebalance(mocker): # construct inputs and mock response inputs = CustomBasketsRebalanceInputs(position_set, pricing_parameters) mock_response = CustomBasketsResponse(report_id, basket_id, 'done') # setup mock session and api response mock_session() mocker.return_value = mock_response # run test response = GsIndexApi.rebalance(basket_id, AssetType.Custom_Basket, inputs) assert response == mock_response
def test_basket_edit(mocker): # construct inputs and mock response inputs = CustomBasketsEditInputs(name=name, flagship=True) mock_response = CustomBasketsResponse(report_id, basket_id, 'done') # setup mock session and api response mock_session() mocker.return_value = mock_response # run test response = GsIndexApi.edit(basket_id, inputs) assert response == mock_response
def test_basket_cancel_rebalance(mocker): # construct inputs and mock response inputs = CustomBasketsRebalanceAction(comment='test cancel') mock_response = f'Rebalance submission for {basket_id} has been cancelled' # setup mock session and api response mock_session() mocker.return_value = mock_response # run test response = GsIndexApi.cancel_rebalance(basket_id, inputs) assert response == mock_response
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: return GsPortfolioApi.get_positions_data(self.id, start, end, fields, position_type) raise NotImplementedError
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() entitlements = self.__entitlements.to_target() response = None if not entitlements == self.__initial_entitlements: response = GsAssetApi.update_asset_entitlements( self.id, entitlements) if edit_inputs is None and rebal_inputs is None: if response: return response.as_dict() 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()
def test_basket_create(mocker): # construct inputs and mock response inputs = CustomBasketsCreateInputs(name, position_set, pricing_parameters, return_type, ticker, publish_parameters=publish_parameters) mock_response = CustomBasketsResponse(report_id, basket_id, 'done') # setup mock session and api response mock_session() mocker.return_value = mock_response # run test response = GsIndexApi.create(inputs) assert response == mock_response
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: 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()
def __finish_populating_attributes_for_existing_basket(self): """ Populates all values not originally fetched during initialization due to required API calls """ if self.__populate_api_attrs: position_set = GsAssetApi.get_latest_positions(self.id, PositionType.ANY) report = get(self, '__latest_create_report', self.__get_latest_create_report()) self.__divisor = get(position_set, 'divisor') self.__initial_price = get(GsIndexApi.initial_price(self.id, dt.date.today()), 'price') self.__parent_basket = None if self.__clone_parent_id is None else \ get(GsAssetApi.get_asset(self.__clone_parent_id), 'id') self.__position_set = PositionSet.from_target(position_set) self.__publish_to_bloomberg = get(report, 'parameters.publish_to_bloomberg') self.__publish_to_factset = get(report, 'parameters.publish_to_factset') self.__publish_to_reuters = get(report, 'parameters.publish_to_reuters') set_(self.__initial_state, 'divisor', self.__divisor) set_(self.__initial_state, 'initial_price', self.__initial_price) set_(self.__initial_state, 'position_set', self.__position_set) 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) self.__initial_positions = set(deepcopy(self.__position_set.positions)) self.__populate_api_attrs = False
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)
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)
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 = get(self, '__latest_create_report', self.__get_latest_create_report()) self.__publish_to_bloomberg = get( report, 'parameters.publish_to_bloomberg') self.__publish_to_factset = get( report, 'parameters.publish_to_factset') self.__publish_to_reuters = get( report, 'parameters.publish_to_reuters') 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) if not has(self, '__entitlements'): self.__entitlements = BasketEntitlements.from_target( self.__initial_entitlements) self.__set_error_messages()
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)
def get_last_rebalance_date(self) -> dt.date: if not self.id: self.__raise_initialization_error('retrieve latest rebalance date') last_rebalance = GsIndexApi.last_rebalance_data(self.id) return dt.datetime.strptime(last_rebalance['date'], '%Y-%m-%d').date()
def get_last_rebalance_data(self) -> Dict: if not self.id: self.__raise_initialization_error('retrieve latest rebalance data') return GsIndexApi.last_rebalance_data(self.id)