def check_or_create_property(api_factory, property_key): split_prop = property_key.split("/") if len(split_prop) != 3: raise ValueError( f"Invalid Value: {property_key}. The property key must have exactly two '/' slashes!" ) domain = split_prop[0] scope = split_prop[1] code = split_prop[2] prop_def_req = models.CreatePropertyDefinitionRequest( domain=domain, scope=scope, code=code, display_name=f"{code}", data_type_id=models.ResourceId(scope="system", code="string")) try: api_factory.build( lusid.api.PropertyDefinitionsApi).create_property_definition( create_property_definition_request=prop_def_req) except ApiException: logging.info( f"Property definition {domain}/{scope}/{code} already exists.")
def test_create_portfolio_with_properties(self): _, scope, property_code, _ = self.id_generator.generate_scope_and_code( "property_definition", scope=TestDataUtilities.tutorials_scope, code_prefix="fund-style-", annotations=["Portfolio"]) data_type_id = models.ResourceId("system", "string") # property definition property_definition = models.CreatePropertyDefinitionRequest( domain="Portfolio", scope=TestDataUtilities.tutorials_scope, code=property_code, value_required=False, display_name="Fund Style", life_time="Perpetual", data_type_id=data_type_id) # create the property definition property_definition_result = self.property_definitions_api.create_property_definition( create_property_definition_request=property_definition) # property value property_value = "Active" portfolio_property = models.ModelProperty( key=property_definition_result.key, value=models.PropertyValue(label_value=property_value)) _, scope, portfolio_code = self.id_generator.generate_scope_and_code( "portfolio", scope=TestDataUtilities.tutorials_scope, code_prefix="portfolio-") # details of the portfolio to be created request = models.CreateTransactionPortfolioRequest( display_name=portfolio_code, code=portfolio_code, base_currency="GBP", # set the property value when creating the portfolio properties={property_definition_result.key: portfolio_property}) # create the portfolio portfolio = self.transaction_portfolios_api.create_portfolio( scope=scope, create_transaction_portfolio_request=request) portfolio_code = portfolio.id.code self.assertEqual(portfolio_code, request.code) portfolio_properties = self.portfolios_api.get_portfolio_properties( TestDataUtilities.tutorials_scope, portfolio_code) self.assertEqual(len(portfolio_properties.properties), 1) self.assertEqual( portfolio_properties.properties[ property_definition_result.key].value.label_value, property_value)
def test_upsert_simple_order(self): """Makes a request for a single order.""" orders_scope = "Orders-SimpleUpsert-TestScope" # Create unique order id order_id = str(uuid.uuid4()) # Construct order arguments instrument_identifiers = { TestDataUtilities.lusid_luid_identifier: self.instrument_ids[0] } ##Create ResourceId for order order_resource_id = models.ResourceId(orders_scope, order_id) portfolio_id = ResourceId(orders_scope, "OrdersTestPortfolio") properties = { f"Order/{orders_scope}/TIF": PerpetualProperty(f"Order/{orders_scope}/TIF", PropertyValue("GTC")), f"Order/{orders_scope}/OrderBook": PerpetualProperty(f"Order/{orders_scope}/OrderBook", PropertyValue("UK Test Orders")), f"Order/{orders_scope}/PortfolioManager": PerpetualProperty(f"Order/{orders_scope}/PortfolioManager", PropertyValue("F Bar")), f"Order/{orders_scope}/Account": PerpetualProperty(f"Order/{orders_scope}/Account", PropertyValue("J Wilson")), f"Order/{orders_scope}/Strategy": PerpetualProperty(f"Order/{orders_scope}/Strategy", PropertyValue("RiskArb")) } order_book_id = ResourceId(orders_scope, "OrdersTestBook") quantity = 100 # Construct request order_request = OrderRequest( properties=properties, instrument_identifiers=instrument_identifiers, quantity=quantity, side='buy', order_book_id=order_book_id, portfolio_id=portfolio_id, id=order_resource_id) order_set_request = OrderSetRequest(order_requests=[order_request]) upsert_result = self.orders_api.upsert_orders( order_set_request=order_set_request) self.assertEqual(len(upsert_result.values), 1) response = upsert_result.values[0].to_dict() self.assertEqual(response['id']['code'], order_id) self.assertEqual(response['lusid_instrument_id'], self.instrument_ids[0]) self.assertEqual(response['quantity'], 100) self.assertEqual( response['properties'][f"Order/{orders_scope}/TIF"]['key'], f"Order/{orders_scope}/TIF")
def ensure_property_definition(cls, code): try: cls.property_definitions_api.get_property_definition( domain="Instrument", scope=TestDataUtilities.tutorials_scope, code=code) except ApiException as e: # property definition doesn't exist (returns 404), so create one property_definition = models.CreatePropertyDefinitionRequest( domain="Instrument", scope=TestDataUtilities.tutorials_scope, life_time="Perpetual", code=code, value_required=False, data_type_id=models.ResourceId("system", "string")) # create the property cls.property_definitions_api.create_property_definition( definition=property_definition)
def request_define_property(domain, scope, code, display_name, api_factory): # Create a request to define our strategy property property_request = models.CreatePropertyDefinitionRequest( domain='Transaction', scope=scope, code='strategy', value_required=False, display_name='strategy', data_type_id=models.ResourceId(scope='system', code='string')) # Call LUSID to create our new property property_response = api_factory.build( lusid.api.PropertyDefinitionsApi).create_property_definition( create_property_definition_request=property_request) # Grab the key off the response to use when referencing this property in other LUSID calls strategy_property_key = property_response.key # Pretty print our strategy property key prettyprint.heading('Strategy Property Key: ', strategy_property_key) return strategy_property_key
def test_aggregation(self, _, in_line_recipe, recipe_scope, recipe_code) -> None: """ General valuation/aggregation test """ # create recipe (provides model parameters, locations to use in resolving market data etc. # and push it into LUSID. Only needs to happen once each time when updated, or first time run to create. recipe = self.create_configuration_recipe(recipe_scope, recipe_code) self.upsert_recipe_request(recipe) # Set valuation result key valuation_key = "Sum(Holding/default/PV)" # create valuation request valuation_request = models.ValuationRequest( recipe_id=models.ResourceId(scope=recipe_scope, code=recipe_code), metrics=[ models.AggregateSpec("Instrument/default/Name", "Value"), models.AggregateSpec("Holding/default/PV", "Proportion"), models.AggregateSpec("Holding/default/PV", "Sum"), ], group_by=["Instrument/default/Name"], valuation_schedule=models.ValuationSchedule( effective_at=self.effective_date), portfolio_entity_ids=[ models.PortfolioEntityId( scope=TestDataUtilities.tutorials_scope, code=self.portfolio_code) ]) # Complete aggregation aggregation = self.aggregation_api.get_valuation( valuation_request=valuation_request) # Asserts self.assertEqual(len(aggregation.data), 3) self.assertEqual(aggregation.data[0][valuation_key], 10000) self.assertEqual(aggregation.data[1][valuation_key], 20000) self.assertEqual(aggregation.data[2][valuation_key], 30000)
def valuation(api_factory, marketdata_scope, portfolio_group, time): time_parts = [time[:10], time[11:]] if time_parts[1] == "LSE_market_close": time_parts[1] = "16:30:00.000000+00:00" elif time_parts[1] == "NYSE_market_close": time_parts[1] = "21:00:00.000000+00:00" time = "T".join(time_parts) # Create a recipe to perform a valuation configuration_recipe = models.ConfigurationRecipe( scope="User", code="quotes_recipe", market=models.MarketContext( market_rules=[ models.MarketDataKeyRule( key="Equity.Figi.*", supplier="DataScope", data_scope=marketdata_scope, quote_type="Price", field="Mid", ), models.MarketDataKeyRule( key="Equity.Isin.*", supplier="DataScope", data_scope=marketdata_scope, quote_type="Price", field="Mid", ), models.MarketDataKeyRule( key="Equity.LusidInstrumentId.*", supplier="DataScope", data_scope=marketdata_scope, quote_type="Price", field="Mid", ), models.MarketDataKeyRule( key="Fx.CurrencyPair.*", supplier="DataScope", data_scope=marketdata_scope, quote_type="Rate", field="Mid", ), ], suppliers=models.MarketContextSuppliers( commodity="DataScope", credit="DataScope", equity="DataScope", fx="DataScope", rates="DataScope", ), options=models.MarketOptions( default_supplier="DataScope", default_instrument_code_type="Figi", default_scope=marketdata_scope, ), ), ) upsert_configuration_recipe_response = api_factory.build( lusid.api.ConfigurationRecipeApi).upsert_configuration_recipe( upsert_recipe_request=models.UpsertRecipeRequest( configuration_recipe=configuration_recipe)) # Create the valuation request valuation_request = models.ValuationRequest( recipe_id=models.ResourceId(scope="User", code="quotes_recipe"), metrics=[ models.AggregateSpec(key="Instrument/default/LusidInstrumentId", op="Value"), models.AggregateSpec(key="Instrument/default/Name", op="Value"), models.AggregateSpec(key="Holding/default/Units", op="Sum"), models.AggregateSpec(key="Holding/default/Cost", op="Sum"), models.AggregateSpec(key="Holding/default/PV", op="Sum"), models.AggregateSpec(key="Holding/default/PV", op="Proportion"), ], group_by=["Instrument/default/LusidInstrumentId"], portfolio_entity_ids=[ models.PortfolioEntityId( scope=portfolio_group.scope, code=portfolio_group.code, portfolio_entity_type="GroupPortfolio", ) ], valuation_schedule=models.ValuationSchedule(effective_at=time), ) # Perform a valuation response = api_factory.build(lusid.api.AggregationApi).get_valuation( valuation_request=valuation_request) dataframe = prettyprint.aggregation_responses_generic_df([response]) dataframe = dataframe.append(dataframe.sum(numeric_only=True), ignore_index=True) return dataframe
name="name1", identifiers=["Luid", "Figi"], state="Active", ) }, ) portfolio_success = models.Portfolio( links=[], version="1", type="Transaction", display_name="name2", description="test portfolio", created="2019-01-01", href="https://www.tes.lusid/code/portfolio?asAt=2019-12-05T10%3A25%3A45.6141270%2B00%3A00", id=models.ResourceId(code="ID00001", scope="default test"), ) transaction_success = models.UpsertPortfolioTransactionsResponse( href="https://www.notadonaim.lusid.com/api/api/code/transactions?asAt=2019-12-05T10%3A25%3A45.6141270%2B00%3A00", links=[], version="1", ) quote_success = models.UpsertQuotesResponse( failed={ "BBG001MM1KV4-Figi_2019-10-28": models.Quote( as_at="2019-01-16", quote_id=models.QuoteId( quote_series_id=models.QuoteSeriesId( provider="Default",
def test_reconcile_portfolio(self): # create the portfolio scope = TestDataUtilities.tutorials_scope portfolio_code = self.test_data_utilities.create_transaction_portfolio( scope) self.id_generator.add_scope_and_code("portfolio", scope, portfolio_code) today = datetime.now().astimezone(tz=pytz.utc) yesterday = today - timedelta(1) # create transactions for yesterday yesterdays_transactions = [ self.test_data_utilities.build_transaction_request( instrument_id=self.instrument_ids[0], units=1000.0, price=100.0, currency="GBP", trade_date=yesterday + timedelta(hours=8), transaction_type="StockIn"), self.test_data_utilities.build_transaction_request( instrument_id=self.instrument_ids[0], units=2300.0, price=101.0, currency="GBP", trade_date=yesterday + timedelta(hours=12), transaction_type="StockIn"), self.test_data_utilities.build_transaction_request( instrument_id=self.instrument_ids[1], units=-1000.0, price=102.0, currency="GBP", trade_date=yesterday + timedelta(hours=9), transaction_type="StockIn"), self.test_data_utilities.build_transaction_request( instrument_id=self.instrument_ids[2], units=1200.0, price=103.0, currency="GBP", trade_date=yesterday + timedelta(hours=16), transaction_type="StockIn"), self.test_data_utilities.build_transaction_request( instrument_id=self.instrument_ids[3], units=2000.0, price=103.0, currency="GBP", trade_date=yesterday + timedelta(hours=9), transaction_type="StockIn"), ] # add the transactions to LUSID self.transaction_portfolios_api.upsert_transactions( scope=TestDataUtilities.tutorials_scope, code=portfolio_code, transaction_request=yesterdays_transactions) # transactions for today todays_transactions = [ # net long 300 self.test_data_utilities.build_transaction_request( instrument_id=self.instrument_ids[0], units=-3000.0, price=101.78, currency="GBP", trade_date=today + timedelta(hours=8), transaction_type="StockIn"), # net long 1800 self.test_data_utilities.build_transaction_request( instrument_id=self.instrument_ids[0], units=1500.0, price=101.78, currency="GBP", trade_date=today + timedelta(hours=12), transaction_type="StockIn"), # flat self.test_data_utilities.build_transaction_request( instrument_id=self.instrument_ids[1], units=1000.0, price=102.0, currency="GBP", trade_date=today + timedelta(hours=12), transaction_type="StockIn"), # net long 2400 self.test_data_utilities.build_transaction_request( instrument_id=self.instrument_ids[2], units=1200.0, price=103.0, currency="GBP", trade_date=today + timedelta(hours=16), transaction_type="StockIn"), # net long 3000 self.test_data_utilities.build_transaction_request( instrument_id=self.instrument_ids[3], units=1000.0, price=103.0, currency="GBP", trade_date=today + timedelta(hours=9), transaction_type="StockIn"), # net long 5000 self.test_data_utilities.build_transaction_request( instrument_id=self.instrument_ids[3], units=2000.0, price=103.0, currency="GBP", trade_date=today + timedelta(hours=20), transaction_type="StockIn"), ] # add the transactions to LUSID transactions_response = self.transaction_portfolios_api.upsert_transactions( scope=TestDataUtilities.tutorials_scope, code=portfolio_code, transaction_request=todays_transactions) # get the time of the last update last_as_at = transactions_response.version.as_at_date # We now have the portfolio with 2 days worth of transactions, going to reconcile from T-1 20:00 to now, # this should reflect breaks for each instrument equal to the transactions from yesterday till 20:00 today reconciliation_request = models.PortfoliosReconciliationRequest( left=models.PortfolioReconciliationRequest( portfolio_id=models.ResourceId( scope=TestDataUtilities.tutorials_scope, code=portfolio_code), effective_at=yesterday + timedelta(hours=20), as_at=last_as_at), right=models.PortfolioReconciliationRequest( portfolio_id=models.ResourceId( scope=TestDataUtilities.tutorials_scope, code=portfolio_code), effective_at=today + timedelta(hours=16), as_at=last_as_at), instrument_property_keys=[TestDataUtilities.lusid_luid_identifier]) breaks = self.reconciliations_api.reconcile_holdings( portfolios_reconciliation_request=reconciliation_request) for rec_break in breaks.values: print("{}\t{}\t{}".format(rec_break.instrument_uid, rec_break.difference_units, rec_break.difference_cost.amount)) rec_map = {b.instrument_uid: b for b in breaks.values} self.assertEqual(-1500, rec_map[self.instrument_ids[0]].difference_units) self.assertEqual(1000, rec_map[self.instrument_ids[1]].difference_units) self.assertEqual(1200, rec_map[self.instrument_ids[2]].difference_units) self.assertEqual(1000, rec_map[self.instrument_ids[3]].difference_units)
def test_add_transaction_to_portfolio_with_property(self): guid = str(uuid.uuid4()) property_name = "traderId-{0}".format(guid) # details of the property to be created property_definition = models.CreatePropertyDefinitionRequest( # The domain the property is to be applied to domain="Transaction", # the scope the property will be created in scope=TestDataUtilities.tutorials_scope, life_time="Perpetual", # when the property value is set it will be valid forever and cannot be changed. # properties whose values can change over time should be created with LifeTimeEnum.TIMEVARIANT code=property_name, value_required=False, display_name="Trader Id", data_type_id=models.ResourceId("system", "string")) # create the property definition property_definition_result = self.property_definitions_api.create_property_definition( definition=property_definition) # effective date for which portfolio is created effective_date = datetime(2018, 1, 1, tzinfo=pytz.utc) # create the portfolio portfolio_id = self.test_data_utilities.create_transaction_portfolio( TestDataUtilities.tutorials_scope) property_value_as_string = "A Trader" property_value = models.PropertyValue(property_value_as_string) # details of the transaction to be added transaction = models.TransactionRequest( transaction_id=str(uuid.uuid4()), type="Buy", instrument_identifiers={ TestDataUtilities.lusid_luid_identifier: self.instrument_ids[0] }, transaction_date=effective_date, settlement_date=effective_date, units=100, transaction_price=models.TransactionPrice(12.3), total_consideration=models.CurrencyAndAmount(1230, "GBP"), source="Client", # add the property to the transaction properties={ property_definition_result.key: models.PerpetualProperty(property_definition_result.key, property_value) }) # add the transaction self.transaction_portfolios_api.upsert_transactions( TestDataUtilities.tutorials_scope, portfolio_id, transactions=[transaction]) # get the trades trades = self.transaction_portfolios_api.get_transactions( TestDataUtilities.tutorials_scope, portfolio_id) self.assertEqual(len(trades.values), 1) self.assertEqual(trades.values[0].transaction_id, transaction.transaction_id) self.assertEqual( trades.values[0].properties[ property_definition_result.key].value.label_value, property_value_as_string)
def test_portfolio_aggregation(self): effective_date = datetime(2019, 4, 15, tzinfo=pytz.utc) portfolio_code = self.test_data_utilities.create_transaction_portfolio(TestDataUtilities.tutorials_scope) self.id_generator.add_scope_and_code("portfolio", TestDataUtilities.tutorials_scope, portfolio_code) transactions = [ self.test_data_utilities.build_transaction_request(instrument_id=self.instrument_ids[0], units=100, price=101, currency="GBP", trade_date=effective_date, transaction_type="StockIn"), self.test_data_utilities.build_transaction_request(instrument_id=self.instrument_ids[1], units=100, price=102, currency="GBP", trade_date=effective_date, transaction_type="StockIn"), self.test_data_utilities.build_transaction_request(instrument_id=self.instrument_ids[2], units=100, price=103, currency="GBP", trade_date=effective_date, transaction_type="StockIn") ] self.transaction_portfolios_api.upsert_transactions(scope=TestDataUtilities.tutorials_scope, code=portfolio_code, transaction_request=transactions) prices = [ (self.instrument_ids[0], 100), (self.instrument_ids[1], 200), (self.instrument_ids[2], 300) ] requests = [ models.UpsertQuoteRequest( quote_id=models.QuoteId( models.QuoteSeriesId( provider="DataScope", instrument_id=price[0], instrument_id_type="LusidInstrumentId", quote_type="Price", field="mid" ), effective_at=effective_date ), metric_value=models.MetricValue( value=price[1], unit="GBP" ) ) for price in prices ] self.quotes_api.upsert_quotes(TestDataUtilities.tutorials_scope, request_body={"quote" + str(request_number): requests[request_number] for request_number in range(len(requests))}) recipe_scope = 'cs-tutorials' recipe_code = 'quotes_recipe' self.id_generator.add_scope_and_code("recipe", recipe_scope, recipe_code) demo_recipe = models.ConfigurationRecipe( scope=recipe_scope, code=recipe_code, market=models.MarketContext( market_rules=[], suppliers=models.MarketContextSuppliers( equity='DataScope' ), options=models.MarketOptions( default_supplier='DataScope', default_instrument_code_type='LusidInstrumentId', default_scope=TestDataUtilities.tutorials_scope) ) ) upsert_recipe_request = models.UpsertRecipeRequest(demo_recipe) self.recipes_api.upsert_configuration_recipe(upsert_recipe_request) valuation_request = models.ValuationRequest( recipe_id=models.ResourceId(scope=recipe_scope,code=recipe_code), metrics=[ models.AggregateSpec("Instrument/default/Name", "Value"), models.AggregateSpec("Holding/default/PV", "Proportion"), models.AggregateSpec("Holding/default/PV", "Sum") ], group_by=["Instrument/default/Name"], portfolio_entity_ids = [ models.PortfolioEntityId(scope = TestDataUtilities.tutorials_scope, code = portfolio_code) ], valuation_schedule=models.ValuationSchedule(effective_at=effective_date) ) # do the aggregation aggregation = self.aggregation_api.get_valuation(valuation_request=valuation_request) for item in aggregation.data: print("\t{}\t{}\t{}".format(item["Instrument/default/Name"], item["Proportion(Holding/default/PV)"], item["Sum(Holding/default/PV)"])) # Asserts self.assertEqual(len(aggregation.data),3) self.assertEqual(aggregation.data[0]["Sum(Holding/default/PV)"], 10000) self.assertEqual(aggregation.data[1]["Sum(Holding/default/PV)"], 20000) self.assertEqual(aggregation.data[2]["Sum(Holding/default/PV)"], 30000)
def test_Perform_a_reconciliation(self): # load sheet sht = self.get_sheet("Perform a reconciliation") # get parameters from excel LeftScope = "F24" LeftCode = "F25" LeftEffectiveAt = "F26" LeftAsAt = "F27" RightScope = "F28" RightCode = "F29" RightEffectiveAt = "F30" RightAsAt = "F31" left_scope = sht.range(LeftScope).value left_code = sht.range(LeftCode).value left_effective_at = get_date(LeftEffectiveAt, sht) left_as_at = get_date(LeftAsAt, sht) right_scope = sht.range(RightScope).value right_code = sht.range(RightCode).value right_effective_at = get_date(RightEffectiveAt, sht) right_as_at = get_date(RightAsAt, sht) # get validation data request_reconciliation = models.PortfoliosReconciliationRequest( left=models.PortfolioReconciliationRequest( portfolio_id=models.ResourceId(scope=left_scope, code=left_code), effective_at=left_effective_at, as_at=left_as_at ), right=models.PortfolioReconciliationRequest( portfolio_id=models.ResourceId(scope=right_scope, code=right_code), effective_at=right_effective_at, as_at=right_as_at ), instrument_property_keys=['Instrument/default/LusidInstrumentId'] ) reconciliation_validation_response = self.reconciliations_api.reconcile_holdings( request=request_reconciliation ).values # format validation data reconciliation_validation = [ { "Instrument Uid": data.instrument_uid, "Left Units": data.left_units, "Right Units": data.right_units, "Units Difference": data.difference_units, "Left Cost Amount": data.left_cost.amount, "Right Cost Amount": data.right_cost.amount, } for data in reconciliation_validation_response ] # get excel data reconciliation_response_excel = sht.range("E39", "O49").value headers = reconciliation_response_excel.pop(0) # format excel data reconciliation_excel = [ { "Instrument Uid": data[headers.index('Instrument Uid')], "Left Units": data[headers.index("Left Units")], "Right Units": data[headers.index("Right Units")], "Units Difference": data[headers.index("Units Difference")], "Left Cost Amount": data[headers.index("Left Cost Amount")], "Right Cost Amount": data[headers.index("Right Cost Amount")] } for data in reconciliation_response_excel if (data[0] != "" and data[0] is not None) ] # assert excel data exists within validation set [self.assertIn(sample, reconciliation_validation) for sample in reconciliation_excel]
def create_property_definitions_from_file(client, scope, domain, data_frame, missing_property_columns): """ Creates the property definitions for all the columns in a file :param LusidApi client: The LusidApi client to use :param str scope: The scope to create the property definitions in :param str domain: The domain to create the property definitions in :param Pandas Series data_frame_dtypes: The dataframe dtypes to add definitions for :return: dict property_key_mapping: A mapping of data_frame columns to property keys """ missing_property_data_frame = data_frame.loc[:, missing_property_columns] # Ensure that all data types in the file have been mapped if not (set([ str(data_type) for data_type in missing_property_data_frame.dtypes.unique() ]) <= set(globals['data_type_mapping'])): raise Exception( '''There are data types in the data_frame which have not been mapped to LUSID data types, please ensure that all data types have been mapped before retrying''' ) # Initialise a dictionary to hold the keys property_key_mapping = {} # Iterate over the each column and its data type for column_name, data_type in missing_property_data_frame.dtypes.iteritems( ): # Make the column name LUSID friendly lusid_friendly_code = make_code_lusid_friendly(column_name) # If there is no data Pandas infers a type of float, would prefer to infer object if missing_property_data_frame[column_name].isnull().all(): print('{} is null'.format(column_name)) data_type = 'object' data_frame[column_name] = data_frame[column_name].astype( 'object', copy=False) # Create a request to define the property, assumes value_required is false for all property_request = models.CreatePropertyDefinitionRequest( domain=domain, scope=scope, code=lusid_friendly_code, value_required=False, display_name=column_name, data_type_id=models.ResourceId( scope='default', code=globals['data_type_mapping'][str(data_type)])) # Call LUSID to create the new property property_response = client.property_definitions.create_property_definition( definition=property_request) print('Created - {} - with datatype {}'.format( property_response.key, property_response.data_type_id.code)) # Grab the key off the response to use when referencing this property in other LUSID calls property_key_mapping[column_name] = property_response.key return property_key_mapping, data_frame
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, )