-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SE-702: Fixed merge confilcts to master
- Loading branch information
Showing
11 changed files
with
1,372 additions
and
120 deletions.
There are no files selected for viewing
114 changes: 114 additions & 0 deletions
114
sdk/tests/tutorials/valuation/test_quotes_scaling_factor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import unittest | ||
|
||
import lusid | ||
import lusid.models as models | ||
from utilities import TestDataUtilities, BaseValuationUtilities | ||
|
||
|
||
class ScalingFactor(BaseValuationUtilities): | ||
"""This is an example of using the scale_factor in an | ||
upsert_quotes request, which is applicable to instruments | ||
where the price requires scaling, such as bonds. For a bond | ||
with quotes as a percentage of par of 100, adding this value | ||
as the scaling factor will account for the transformation when | ||
running a valuation | ||
""" | ||
|
||
def upsert_quotes(self, scale_factor) -> None: | ||
""" | ||
Upserts quotes using a scaling factor parameter, that can be | ||
used to for non-standard quotes such as bond prices that are | ||
quotes as percentage as par (i.e. 'scaling_factor=100'). | ||
:param float scale_factor: scale factor for non-standard quotes | ||
:return: None | ||
""" | ||
# Add prices as a percentage of par | ||
prices = [ | ||
(self.instrument_ids[0], 100), | ||
] | ||
|
||
requests = [ | ||
models.UpsertQuoteRequest( | ||
quote_id=models.QuoteId( | ||
models.QuoteSeriesId( | ||
provider="Lusid", | ||
instrument_id=price[0], | ||
instrument_id_type="LusidInstrumentId", | ||
quote_type="Price", | ||
field="mid", | ||
), | ||
effective_at=self.effective_date, | ||
), | ||
metric_value=models.MetricValue(value=price[1], unit="GBP"), | ||
# Set a scaling factor for the par value | ||
scale_factor=scale_factor, | ||
) | ||
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)) | ||
}, | ||
) | ||
|
||
def create_configuration_recipe( | ||
self, recipe_scope, recipe_code | ||
) -> lusid.models.ConfigurationRecipe: | ||
""" | ||
Creates a configuration recipe that can be used inline or upserted | ||
:param str recipe_scope: The scope for the configuration recipe | ||
:param str recipe_code: The code of the the configuration recipe | ||
:return: ConfigurationRecipe | ||
""" | ||
|
||
return models.ConfigurationRecipe( | ||
scope=recipe_scope, | ||
code=recipe_code, | ||
market=models.MarketContext( | ||
market_rules=[], | ||
suppliers=models.MarketContextSuppliers(equity="Lusid"), | ||
options=models.MarketOptions( | ||
default_supplier="Lusid", | ||
default_instrument_code_type="LusidInstrumentId", | ||
default_scope=TestDataUtilities.tutorials_scope, | ||
), | ||
), | ||
) | ||
|
||
def test_par_scaled_valuation(self) -> None: | ||
""" | ||
Valuation test for a simple instrument using quotes upserted with a | ||
scale_factor of 100, this would typically be applicable to bonds or | ||
other instruments where the par amount is other than 1. | ||
""" | ||
|
||
# Upsert quotes with scale_factor of 100 | ||
self.upsert_quotes(100) | ||
|
||
# Upsert recipe | ||
recipe = self.create_configuration_recipe(self.recipe_scope, self.recipe_code) | ||
self.upsert_recipe_request(recipe) | ||
|
||
# Create valuation request | ||
valuation_request = self.create_valuation_request( | ||
self.portfolio_scope, | ||
self.portfolio_code, | ||
self.recipe_scope, | ||
self.recipe_code, | ||
) | ||
|
||
# Complete valuation | ||
valuation = self.aggregation_api.get_valuation( | ||
valuation_request=valuation_request | ||
) | ||
|
||
# Asserts | ||
self.assertEqual(len(valuation.data), 3) | ||
self.assertEqual(valuation.data[0][self.valuation_portfolio_key], 100) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
136 changes: 136 additions & 0 deletions
136
sdk/tests/tutorials/valuation/test_recipe_currency_subunits.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import unittest | ||
from datetime import datetime | ||
|
||
import lusid | ||
import lusid.models as models | ||
from utilities import BaseValuationUtilities | ||
|
||
|
||
class CurrencySubUnitValuation(BaseValuationUtilities): | ||
""" "This is an example of running valuation with prices quoted | ||
in currency subunits, a typical example will be GBp/GBx where a | ||
valuation may be carried out in GBP. In order for LUSID to carry out | ||
the fx transformation, the key parameter is 'attempt_to_infer_missing_fx' | ||
which needs to set to 'True' in the ConfigurationRecipe's pricing options | ||
""" | ||
|
||
def create_configuration_recipe( | ||
self, recipe_scope, recipe_code, infer_fx_rate | ||
) -> lusid.models.ConfigurationRecipe: | ||
""" | ||
Creates a configuration recipe that infers fx_rates when required, | ||
this can be used to have the valuation request look for inverse fx | ||
quotes or currency sub-units such as GBp/GBx. A key distinction in | ||
valuation, is the use of the metric/key that will ensure valuation | ||
is returned in either the domestic or portfolio currency, these | ||
are 'Valuation/Pv' and 'Valuation/PvInPortfolioCcy' respectively. | ||
:param str recipe_scope: The scope for the configuration recipe | ||
:param str recipe_code: The code of the the configuration recipe | ||
:param bool infer_fx_rate: looks up fx_rate from quote store when set to 'True' | ||
:return: ConfigurationRecipe | ||
""" | ||
|
||
return models.ConfigurationRecipe( | ||
scope=recipe_scope, | ||
code=recipe_code, | ||
market=models.MarketContext( | ||
market_rules=[ | ||
models.MarketDataKeyRule( | ||
key="Equity.Figi.*", | ||
supplier="Lusid", | ||
data_scope=self.market_data_scope, | ||
quote_type="Price", | ||
field="mid", | ||
quote_interval="1D.0D", | ||
), | ||
], | ||
suppliers=models.MarketContextSuppliers( | ||
equity=self.market_data_provider | ||
), | ||
options=models.MarketOptions( | ||
default_supplier=self.market_data_provider, | ||
default_instrument_code_type="Figi", | ||
default_scope=self.market_data_scope, | ||
attempt_to_infer_missing_fx=infer_fx_rate, | ||
), | ||
), | ||
) | ||
|
||
def upsert_quotes(self, quotes_date) -> models.UpsertQuotesResponse: | ||
""" | ||
Upserts quotes into LUSID to be used in pricing valuation | ||
:param datetime quotes_date: The date of the upserted quotes | ||
:return: UpsertQuotesResponse | ||
""" | ||
|
||
prices = [ | ||
("BBG000BF46Y8", 10000), | ||
("BBG000PQKVN8", 20000), | ||
("BBG000FD8G46", 30000), | ||
] | ||
|
||
requests = [ | ||
models.UpsertQuoteRequest( | ||
quote_id=models.QuoteId( | ||
models.QuoteSeriesId( | ||
provider="Lusid", | ||
instrument_id=price[0], | ||
instrument_id_type="Figi", | ||
quote_type="Price", | ||
field="mid", | ||
), | ||
effective_at=quotes_date, | ||
), | ||
metric_value=models.MetricValue(value=price[1], unit="GBp"), | ||
) | ||
for price in prices | ||
] | ||
|
||
return self.quotes_api.upsert_quotes( | ||
scope=self.market_data_scope, | ||
request_body={ | ||
"quote" + str(request_number): requests[request_number] | ||
for request_number in range(len(requests)) | ||
}, | ||
) | ||
|
||
def test_currency_subunit(self): | ||
""" | ||
Tests that a recipe including an 'infer_fx_rate' boolean reconciles | ||
market data when quotes provided in currency subunits such as GBp/GBx. | ||
LUSID will look to translate the quotes back to the portfolio base currency, | ||
based on prices in the quotes store | ||
:return: None | ||
""" | ||
|
||
# Upsert quotes with a timedelta of 2 days against out valuation date | ||
quotes_response = self.upsert_quotes(self.effective_date) | ||
self.assertEqual(len(quotes_response.failed), 0) | ||
|
||
# Upsert recipe with fx inference set to True | ||
recipe = self.create_configuration_recipe( | ||
self.recipe_scope, self.recipe_code, infer_fx_rate=True | ||
) | ||
self.upsert_recipe_request(recipe) | ||
|
||
# Create valuation request | ||
valuation_request = self.create_valuation_request( | ||
self.portfolio_scope, | ||
self.portfolio_code, | ||
self.recipe_scope, | ||
self.recipe_code, | ||
) | ||
|
||
valuation = self.aggregation_api.get_valuation( | ||
valuation_request=valuation_request, | ||
) | ||
|
||
# Asserts | ||
self.assertEqual(len(valuation.data), 3) | ||
self.assertEqual(valuation.data[0][self.valuation_portfolio_key], 10000) | ||
self.assertEqual(valuation.data[1][self.valuation_portfolio_key], 20000) | ||
self.assertEqual(valuation.data[2][self.valuation_portfolio_key], 30000) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
Oops, something went wrong.