Skip to content

Commit

Permalink
SE-702: Fixed merge confilcts to master
Browse files Browse the repository at this point in the history
  • Loading branch information
cscialino committed Jan 11, 2022
1 parent d76a07f commit e82cbc1
Show file tree
Hide file tree
Showing 11 changed files with 1,372 additions and 120 deletions.
114 changes: 114 additions & 0 deletions sdk/tests/tutorials/valuation/test_quotes_scaling_factor.py
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 sdk/tests/tutorials/valuation/test_recipe_currency_subunits.py
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()
Loading

0 comments on commit e82cbc1

Please sign in to comment.