Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SE-702: Fixing merge conflicts #101

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 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,119 @@
import unittest

import lusid
import lusid.models as models
from lusidfeature import lusid_feature
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
"""

@lusid_feature("F14-5")
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 = [
("BBG00Y271826", 100),
("BBG005D5KGM0", 200),
("BBG000DPM932", 300)
]

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=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="Figi",
default_scope=TestDataUtilities.tutorials_scope,
),
),
)

@lusid_feature("F10-7")
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()
138 changes: 138 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,138 @@
import unittest
from datetime import datetime

import lusid
import lusid.models as models
from lusidfeature import lusid_feature
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,
),
),
)

@lusid_feature("F14-6")
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 = [
("BBG00Y271826", 10000),
("BBG005D5KGM0", 20000),
("BBG000DPM932", 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