def test_lookup_instrument_by_unique_id(self): figi = "BBG000FD8G46" # set up the instrument response = self.instruments_api.upsert_instruments(requests={ figi: models.InstrumentDefinition( name="HISCOX LTD", identifiers={ "Figi": models.InstrumentIdValue(value=figi), "ClientInternal": models.InstrumentIdValue(value="internal_id_1") } ) }) self.assertEqual(len(response.values), 1, response.failed) # look up an instrument that already exists in the instrument master by a # unique id, in this case an OpenFigi, and also return a list of aliases looked_up_instruments = self.instruments_api.get_instruments(identifier_type="Figi", identifiers=[figi], instrument_property_keys=[ "Instrument/default/ClientInternal" ]) self.assertTrue(figi in looked_up_instruments.values, msg=f"cannot find {figi}") instrument = looked_up_instruments.values[figi] self.assertTrue(instrument.name, "HISCOX LTD") property = next(filter(lambda i: i.key == "Instrument/default/ClientInternal", instrument.properties), None) self.assertTrue(property.value, "internal_id_1")
def test_seed_instrument_master(self): response = self.instruments_api.upsert_instruments( instruments={ "BBG000FD8G46": models.InstrumentDefinition(name="HISCOX LTD", identifiers={ "Figi": models.InstrumentIdValue( value="BBG000FD8G46"), "ClientInternal": models.InstrumentIdValue( value="internal_id_1") }), "BBG000DW76R4": models.InstrumentDefinition(name="ITV PLC", identifiers={ "Figi": models.InstrumentIdValue( value="BBG000DW76R4"), "ClientInternal": models.InstrumentIdValue( value="internal_id_2") }), "BBG000PQKVN8": models.InstrumentDefinition(name="MONDI PLC", identifiers={ "Figi": models.InstrumentIdValue( value="BBG000PQKVN8"), "ClientInternal": models.InstrumentIdValue( value="internal_id_3") }), "BBG000BDWPY0": models.InstrumentDefinition(name="NEXT PLC", identifiers={ "Figi": models.InstrumentIdValue( value="BBG000BDWPY0"), "ClientInternal": models.InstrumentIdValue( value="internal_id_4") }), "BBG000BF46Y8": models.InstrumentDefinition(name="TESCO PLC", identifiers={ "Figi": models.InstrumentIdValue( value="BBG000BF46Y8"), "ClientInternal": models.InstrumentIdValue( value="internal_id_5") }) }) self.assertEqual(len(response.values), 5, response.failed)
def test_seed_instrument_master(self): response = self.instruments_api.upsert_instruments( request_body={ "BBG00KTDTF73": models.InstrumentDefinition( name="AT&T INC", identifiers={ "Figi": models.InstrumentIdValue(value="BBG00KTDTF73"), "ClientInternal": models.InstrumentIdValue(value="internal_id_1_example") }), "BBG00Y271826": models.InstrumentDefinition( name="BYTES TECHNOLOGY GROUP PLC", identifiers={ "Figi": models.InstrumentIdValue(value="BBG00Y271826"), "ClientInternal": models.InstrumentIdValue(value="internal_id_2_example") }), "BBG00L7XVNP1": models.InstrumentDefinition( name="CUSHMAN & WAKEFIELD PLC", identifiers={ "Figi": models.InstrumentIdValue(value="BBG00L7XVNP1"), "ClientInternal": models.InstrumentIdValue(value="internal_id_3_example") }), "BBG005D5KGM0": models.InstrumentDefinition( name="FIRST CITRUS BANCORPORATION", identifiers={ "Figi": models.InstrumentIdValue(value="BBG005D5KGM0"), "ClientInternal": models.InstrumentIdValue(value="internal_id_4_example") }), "BBG000DPM932": models.InstrumentDefinition( name="FRASERS GROUP PLC", identifiers={ "Figi": models.InstrumentIdValue(value="BBG000DPM932"), "ClientInternal": models.InstrumentIdValue(value="internal_id_5_example") }) }) self.assertEqual(len(response.values), 5, response.failed)
def test_create_custom_instrument(self): # create a definition for the instrument swap_definition = models.InstrumentDefinition( name="10mm 5Y Fixed", # The set of identifiers used for identifying the instrument # e.g. for uploading transactions identifiers={ "ClientInternal": models.InstrumentIdValue(value="SW-1") }, # The details for valuing the instrument definition=models.InstrumentEconomicDefinition( # Identifies which valuation engine to use instrument_format="CustomFormat", content="<customFormat>upload in custom xml or JSON format</customFormat>")) # create the swap swap_response = self.instruments_api.upsert_instruments(requests={ "request": swap_definition }) self.assertEqual(len(swap_response.failed), 0, swap_response.failed.values())
def batch_upsert(instrument_universe, api_factory): # Initialise our batch upsert request batch_upsert_request = {} # Iterate over our instrument universe for row, instrument in instrument_universe.iterrows(): # Set our identifier columns identifier_columns = [('isin', 'Isin'), ('figi', 'Figi'), ('ticker', 'Ticker'), ('sedol', 'Sedol'), ('client_internal', 'ClientInternal')] # Create our identifiers identifiers = {} for identifier in identifier_columns: identifiers[identifier[1]] = models.InstrumentIdValue( value=instrument[identifier[0]]) # Add the instrument to our batch request using the FIGI as the main unique identifier batch_upsert_request[ instrument['instrument_name']] = models.InstrumentDefinition( name=instrument['instrument_name'], identifiers=identifiers) # Call LUSID to upsert our batch instrument_response = api_factory.build( lusid.api.InstrumentsApi).upsert_instruments( request_body=batch_upsert_request) # Pretty print the response from LUSID prettyprint.instrument_response(instrument_response, identifier='Figi') return batch_upsert_request
def expected_response(self): instrument_definition = models.Bond( start_date=datetime(2002, 2, 28, tzinfo=pytz.utc), maturity_date=datetime(2032, 3, 1, tzinfo=pytz.utc), dom_ccy="USD", coupon_rate=0.07, principal=500000000, flow_conventions=models.FlowConventions( # coupon payment currency currency="USD", # semi-annual coupon payments payment_frequency="6M", # using an Actual/365 day count convention (other options : Act360, ActAct, ... day_count_convention="Thirty360", # modified following rolling convention (other options : ModifiedPrevious, NoAdjustment, EndOfMonth,...) roll_convention="MF", # no holiday calendar supplied payment_calendars=["US"], reset_calendars=["US"], settle_days=2, reset_days=2, ), identifiers={"clientInternal": "imd_34534538"}, instrument_type="Bond", ) instrument_request = { "ClientInternal: imd_34534538": models.InstrumentDefinition( # instrument display name name="DISNEY_7_2032", # unique instrument identifier identifiers={ "ClientInternal": models.InstrumentIdValue("imd_34534538"), "Isin": models.InstrumentIdValue("US25468PBW59"), }, # instrument definition definition=instrument_definition, ) } return self.instruments_api.upsert_instruments(instrument_request)
def setup_instruments(api_factory, instrument_name, instrument_client_internal, properties: list): # Create an instrument test_instrument_req1 = models.InstrumentDefinition( name=instrument_name, identifiers={"ClientInternal": models.InstrumentIdValue(value=instrument_client_internal)}, properties=properties ) api_factory.build(lusid.api.InstrumentsApi).upsert_instruments( request_body={"request1": test_instrument_req1} )
def test_load_otc_instrument_transaction(self): # create the portfolio portfolio_code = self.test_data_utilities.create_transaction_portfolio( TestDataUtilities.tutorials_scope) trade_date = datetime(2018, 1, 1, tzinfo=pytz.utc) swap_definition = models.InstrumentDefinition( name="10mm 5Y Fixed", identifiers={ "ClientInternal": models.InstrumentIdValue(value="SW-1") }, definition=models.InstrumentEconomicDefinition( instrument_format="CustomFormat", content= "<customFormat>upload in custom xml or JSON format</customFormat>" )) # create the swap swap_response = self.instruments_api.upsert_instruments( requests={"request": swap_definition}) # get the LUID for the created instrument swap_id = list(swap_response.values.values())[0].lusid_instrument_id # details of the transaction to be added transaction = models.TransactionRequest( transaction_id=str(uuid.uuid4()), type="Buy", instrument_identifiers={ TestDataUtilities.lusid_luid_identifier: swap_id }, transaction_date=trade_date, settlement_date=trade_date, units=1, transaction_price=models.TransactionPrice(0.0), total_consideration=models.CurrencyAndAmount(0.0, "GBP"), source="Client") # add the transaction self.transaction_portfolios_api.upsert_transactions( scope=TestDataUtilities.tutorials_scope, code=portfolio_code, transactions=[transaction]) # get the transaction transactions = self.transaction_portfolios_api.get_transactions( scope=TestDataUtilities.tutorials_scope, code=portfolio_code) self.assertEqual(len(transactions.values), 1) self.assertEqual(transactions.values[0].transaction_id, transaction.transaction_id)
def test_create_zero_coupon_bond(self): zero_coupon_bond = models.Bond(start_date=datetime(2020, 2, 7, 00, tzinfo=pytz.utc), maturity_date=datetime(2020, 9, 18, 00, tzinfo=pytz.utc), dom_ccy="GBP", principal=100000, coupon_rate=0, flow_conventions=models.FlowConventions( scope=None, code=None, currency="GBP", payment_frequency="0Invalid", roll_convention="None", day_count_convention="Invalid", payment_calendars=[], reset_calendars=[], settle_days=2, reset_days=2), identifiers={}, instrument_type="Bond") request_id = "upsert_request_001" zero_coupon_bond_identifier = "ZeroCouponBondInstrument" upsert_zero_coupon_bond = self.instruments_api.upsert_instruments( request_body={ request_id: models.InstrumentDefinition( name="Zero Coupon Bond Example", identifiers={ "ClientInternal": models.InstrumentIdValue(zero_coupon_bond_identifier) }, definition=zero_coupon_bond) }) # Assert instrument was created self.assertIsNotNone( upsert_zero_coupon_bond.values[request_id].instrument_definition) self.assertIsNotNone( upsert_zero_coupon_bond.values[request_id].lusid_instrument_id) # Remove the test instrument self.instruments_api.delete_instrument("ClientInternal", zero_coupon_bond_identifier)
def load_instruments(self): instruments_to_create = { i.Figi: models.InstrumentDefinition( name=i.Name, identifiers={ "Figi": models.InstrumentIdValue(value=i.Figi) } ) for i in self._instruments } response = self.instruments_api.upsert_instruments(request_body=instruments_to_create) assert (len(response.failed) == 0) return sorted([i.lusid_instrument_id for i in response.values.values()])
def upsert_otc_to_lusid(self, instrument, name, lusid_id): response = self.instruments_api.upsert_instruments( request_body={ lusid_id: models.InstrumentDefinition( name=name, identifiers={ "ClientInternal": models.InstrumentIdValue( value=lusid_id) }, definition=instrument, ) }) # Check for failures with response self.assertEqual(len(response.values), 1, response.failed)
def upsert_instruments(client, data_frame, instrument_identifier_mapping, instrument_mapping_required, instrument_mapping_optional): """ This function upserts instruments :param client: :param data_frame: :param instrument_identifier_mapping: :param instrument_mapping_required: :param instrument_mapping_optional: :return: """ # Initialise our batch upsert request batch_upsert_request = {} # Iterate over our instrument universe for row, instrument in data_frame.iterrows(): # Create our identifiers identifiers = {} for identifier_lusid, identifier_column in instrument_identifier_mapping[ 'identifier_mapping'].items(): if not pd.isna(instrument[identifier_column]): identifiers[identifier_lusid] = models.InstrumentIdValue( value=instrument[identifier_column]) # Add the instrument to our batch request using the FIGI as the main unique identifier single_instrument = models.InstrumentDefinition( name=instrument[instrument_mapping_required['name']], identifiers=identifiers) batch_upsert_request[instrument[ instrument_mapping_required['name']]] = single_instrument # Call LUSID to upsert our batch instrument_response = client.instruments.upsert_instruments( requests=batch_upsert_request) # Pretty print the response from LUSID return instrument_response
def test_create_term_deposit(self): term_deposit = models.TermDeposit( start_date=datetime(2020, 2, 5, 00, tzinfo=pytz.utc), maturity_date=datetime(2020, 2, 8, 00, tzinfo=pytz.utc), contract_size=1000000, flow_convention=models.FlowConventions( scope=None, code=None, currency="GBP", payment_frequency="6M", roll_convention="MF", day_count_convention="Act365", payment_calendars=[], reset_calendars=[], settle_days=1, reset_days=0), rate=0.03, instrument_type="TermDeposit") request_id = "upsert_request_001" term_deposit_identifier = "TermDepositInstrument" upsert_term_deposit = self.instruments_api.upsert_instruments( request_body={ request_id: models.InstrumentDefinition(name="Term Deposit Example", identifiers={ "ClientInternal": models.InstrumentIdValue( term_deposit_identifier) }, definition=term_deposit) }) # Assert instrument was created self.assertIsNotNone( upsert_term_deposit.values[request_id].instrument_definition) self.assertIsNotNone( upsert_term_deposit.values[request_id].lusid_instrument_id) # Remove the test instrument self.instruments_api.delete_instrument("ClientInternal", term_deposit_identifier)
def test_name_change_corporate_action(self): """The code below shows how to process a corporate action name change in LUSID: Create two instruments, the original and the updated instrument. Create a portfolio and add a transaction to it for the original instrument. Create a corporate action source, and a corporate action comprising a transition. Upsert the corporate action, then check that the holding instrument was changed. """ # Define details for the corporate action. instrument_name = "instrument-name" instrument_original_figi = "FR0123456789" instrument_updated_figi = "FR5555555555" effective_at_date = datetime(2021, 1, 1, tzinfo=pytz.utc) # Create two instruments: an "original" instrument which # will be renamed and the instrument it will be renamed to. self.instruments_api.upsert_instruments( request_body={ instrument_original_figi: models.InstrumentDefinition( name=instrument_name, identifiers={ "Figi": models.InstrumentIdValue( value=instrument_original_figi) }, ), instrument_updated_figi: models.InstrumentDefinition( name=instrument_name, identifiers={ "Figi": models.InstrumentIdValue(value=instrument_updated_figi) }, ), }) _, scope, portfolio_code = self.id_generator.generate_scope_and_code( "portfolio", scope=TestDataUtilities.tutorials_scope, code_prefix="corporate-actions-portfolio-") try: # Create a transaction portfolio to hold the original instrument. self.transaction_portfolios_api.create_portfolio( scope=scope, create_transaction_portfolio_request=models. CreateTransactionPortfolioRequest( code=portfolio_code, display_name=portfolio_code, base_currency="GBP", created=effective_at_date, ), ) except lusid.ApiException as e: if json.loads(e.body)["name"] == "PortfolioWithIdAlreadyExists": pass # ignore if the portfolio exists # Add a transaction for the original instrument. self.transaction_portfolios_api.upsert_transactions( scope=TestDataUtilities.tutorials_scope, code=portfolio_code, transaction_request=[ models.TransactionRequest( transaction_id=str(uuid.uuid4()), type="Buy", instrument_identifiers={ TestDataUtilities.lusid_figi_identifier: instrument_original_figi }, transaction_date=effective_at_date, settlement_date=effective_at_date, transaction_price=models.TransactionPrice(0.0), units=60000, total_consideration=models.CurrencyAndAmount(0, "GBP"), source="Client", ) ], ) corporate_action_source_code = "name-change-corporate-actions-source" corporate_action_code = "name-change-corporate-action" self.id_generator.add_scope_and_code("ca_source", TestDataUtilities.tutorials_scope, corporate_action_source_code) # Create a corporate actions source. corporate_action_source = models.CreateCorporateActionSourceRequest( scope=TestDataUtilities.tutorials_scope, code=corporate_action_source_code, display_name=corporate_action_source_code, description="Name change corporate actions source", ) try: self.corporate_actions_sources_api.create_corporate_action_source( create_corporate_action_source_request=corporate_action_source) except lusid.ApiException as e: if json.loads(e.body)["name"] == "EntityWithIdAlreadyExists": pass # ignore if the property definition exists # Apply the corporate actions source to the transaction portfolio. self.transaction_portfolios_api.upsert_portfolio_details( scope=TestDataUtilities.tutorials_scope, code=portfolio_code, effective_at=effective_at_date, create_portfolio_details=models.CreatePortfolioDetails( corporate_action_source_id=models.ResourceId( scope=TestDataUtilities.tutorials_scope, code=corporate_action_source_code, )), ) # Create a transition which applies to the original instrument above transition_in = models.CorporateActionTransitionComponentRequest( instrument_identifiers={ TestDataUtilities.lusid_figi_identifier: instrument_original_figi }, cost_factor=1, units_factor=1, ) # and has the effect of changing its FIGI to the updated FIGI rename_figi_transition = models.CorporateActionTransitionComponentRequest( instrument_identifiers={ TestDataUtilities.lusid_figi_identifier: instrument_updated_figi }, cost_factor=1, units_factor=1, ) # while zeroing the original instrument's position. zero_previous_position_transition = ( models.CorporateActionTransitionComponentRequest( instrument_identifiers={ TestDataUtilities.lusid_figi_identifier: instrument_original_figi }, cost_factor=0, units_factor=0, )) # The effect of the corporate action is the transition which # combines the input transition and the output transitions. transition = models.CorporateActionTransitionRequest( input_transition=transition_in, output_transitions=[ rename_figi_transition, zero_previous_position_transition, ], ) self.id_generator.add_scope_and_code("corp_action", TestDataUtilities.tutorials_scope, corporate_action_source_code, [corporate_action_code]) # Create a request to upsert a corporate action with the transition above. corporate_action_request = models.UpsertCorporateActionRequest( corporate_action_code=corporate_action_code, announcement_date=effective_at_date + timedelta(days=1), ex_date=effective_at_date + timedelta(days=1), record_date=effective_at_date + timedelta(days=1), payment_date=effective_at_date + timedelta(days=1), transitions=[transition], ) # Make the request through the CorporateActionSourcesApi. self.corporate_actions_sources_api.batch_upsert_corporate_actions( scope=TestDataUtilities.tutorials_scope, code=corporate_action_source_code, upsert_corporate_action_request=[corporate_action_request], ) # Fetch holdings in portfolio once corporate action is applied. holdings = self.transaction_portfolios_api.get_holdings( scope=TestDataUtilities.tutorials_scope, code=portfolio_code, property_keys=["Instrument/default/Figi"], ) # The holding for the original instrument is now against the new instrument's FIGI. self.assertEqual( holdings.values[0].properties[ TestDataUtilities.lusid_figi_identifier].value.label_value, instrument_updated_figi, )
def create_identifiers( index, row: pd.Series, file_type: str, instrument_identifier_mapping: dict = None, unique_identifiers: list = None, full_key_format: bool = True, prepare_key: Callable = prepare_key, ) -> dict: """ Parameters ---------- index The index of the row in the DataFrame row : pd.Series The row of the DataFrame to create identifiers for file_type : str The file type to create identifiers for instrument_identifier_mapping : dict The instrument identifier mapping to use unique_identifiers : list The list of allowable unique instrument identifiers full_key_format : bool Whether or not the full key format i.e. 'Instrument/default/Figi' is required prepare_key : callable The function to use to prepare the key Returns ------- identifiers : dict The identifiers to use on the request """ # Populate the identifiers for this instrument identifiers = { # Handles specifying the entire property key e.g. Instrument/default/Figi or just the code e.g. Figi prepare_key(identifier_lusid, full_key_format): models.InstrumentIdValue(value=row[identifier_column]) if file_type == "instrument" else row[identifier_column] for identifier_lusid, identifier_column in instrument_identifier_mapping.items() if not pd.isna( # Only use the identifier it it has a value row[identifier_column]) } # If there are no identifiers raise an error if len(identifiers) == 0: raise ValueError( f"""The row at index {str(index)} has no value for every single one of the provided identifiers. Please ensure that each row has at least one identifier and try again""" ) # Check that at least one unique identifier exists if it is an instrument file (need to move this out of here) if file_type == "instrument": # Get the unique list of unique identifiers which have been populated unique_identifiers_populated = list( set(unique_identifiers).intersection(set(list( identifiers.keys())))) # If there are no unique identifiers raise an Exception as you need at least one to make a successful call if len(unique_identifiers_populated) == 0: raise ValueError( f"""The instrument at index {str(index)} has no value for at least one unique identifier. Please ensure that each instrument has at least one unique identifier and try again. The allowed unique identifiers are {str(unique_identifiers)}""") else: # If the transaction/holding is cash remove all other identifiers and just use this one if "Instrument/default/Currency" in list(identifiers.keys()): currency_value = identifiers["Instrument/default/Currency"] identifiers.clear() identifiers["Instrument/default/Currency"] = currency_value return identifiers
portfolio_request = models.CreateTransactionPortfolioRequest( display_name=f"Portfolio-{guid}", code=f"Id-{guid}", base_currency="GBP", created=portfolio_creation_date) portfolio = tx_portfolios_api.create_portfolio( scope, create_transaction_portfolio_request=portfolio_request) portfolio_code = portfolio.id.code print("portfolio:", portfolio_code) # Add instruments # FIGIs are from https://www.openfigi.com/search#!?page=1 figis_to_create = { figi: models.InstrumentDefinition( name=name, identifiers={"Figi": models.InstrumentIdValue(value=figi)}) for figi, name in [("BBG000C6K6G9", "VODAFONE GROUP PLC"), ("BBG000C04D57", "BARCLAYS PLC")] } instruments_api.upsert_instruments(request_body=figis_to_create) instruments = { value.name: value.lusid_instrument_id for _, value in instruments_api.get_instruments( identifier_type="Figi", request_body=list( figis_to_create.keys())).values.items() } inverted_instruments = {v: k for k, v in instruments.items()}
code=f"Id-{guid}", base_currency="GBP", created=datetime.datetime(2021, 3, 20, tzinfo=pytz.utc) ) portfolio = tx_portfolios_api.create_portfolio(scope, create_transaction_portfolio_request=portfolio_request) portfolio_code = portfolio.id.code print("Porfolio Code:", portfolio_code) # Upsert instruments instruments_api = api_factory.build(lusid.api.InstrumentsApi) # FIGI is from https://www.openfigi.com/id/BBG000C6K6G9 figis_to_create = { "BBG000C6K6G9": models.InstrumentDefinition(name="VODAFONE GROUP PLC", identifiers={"Figi": models.InstrumentIdValue(value="BBG000C6K6G9")} ) } instruments_api.upsert_instruments(request_body=figis_to_create) # Get instruments instruments_response = instruments_api.get_instruments( identifier_type="Figi", request_body=list(figis_to_create.keys())) name_to_luid = { value.name: value.lusid_instrument_id for _, value in instruments_response.values.items() } luid_to_name = {v: k for k, v in name_to_luid.items()} # Upsert transactions