From 653e1516d51e0058586fa6a22caee27decbaacd9 Mon Sep 17 00:00:00 2001 From: fmak404 Date: Wed, 17 Jul 2024 10:01:09 +0100 Subject: [PATCH 01/14] Create IRR_Sample_Notebook.ipynb --- examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb diff --git a/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb b/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb @@ -0,0 +1 @@ + From 0843c67b22a3a6c692c76a33444310549fb0a321 Mon Sep 17 00:00:00 2001 From: fmak404 Date: Wed, 17 Jul 2024 10:02:32 +0100 Subject: [PATCH 02/14] Add files via upload --- .../irr-calculation/IRR_Sample_Notebook.ipynb | 816 +++++++++++++++++- .../IRR_instruments_upsert.csv | 2 + .../irr-calculation/IRR_transactions.csv | 3 + 3 files changed, 820 insertions(+), 1 deletion(-) create mode 100644 examples/use-cases/irr-calculation/IRR_instruments_upsert.csv create mode 100644 examples/use-cases/irr-calculation/IRR_transactions.csv diff --git a/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb b/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb index 8b137891..8b5fda5b 100644 --- a/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb +++ b/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb @@ -1 +1,815 @@ - +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "dea4f0a4-1a34-4c2a-97df-bf90f660d899", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " \n", + " \n", + "\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from lusidtools.jupyter_tools import toggle_code\n", + "\n", + "\"\"\"IRR Valuation\n", + "\n", + "Attributes\n", + "----------\n", + "instruments\n", + "transactions\n", + "quotes\n", + "Recipe\n", + "IRR Valuation\n", + "\"\"\"\n", + "\n", + "toggle_code(\"Toggle Docstring\")" + ] + }, + { + "cell_type": "markdown", + "id": "ba3551db-87ce-48af-b029-214a79e67cd0", + "metadata": {}, + "source": [ + "# IRR Calculations\n", + "\n", + "In this example we demonstrate how Internal Rate of Return can be calculated using the GetValuation endpoint. We will set up a portfolio, create a simple instrument and upsert a number of related transactions. \n", + "\n", + "Once done, we will create a recipe for valuation and upsert quotes for the the simple instrument that we had created.\n", + "\n", + "Using the valuation function we will illustrate the calculation of the IRR for the series of cashflows." + ] + }, + { + "cell_type": "markdown", + "id": "a49c6cc8-8347-4092-ae4c-50e38ebf98c7", + "metadata": {}, + "source": [ + "## Table of Contents:\n", + "- 1. [Imports](#1.-Imports)\n", + "- 2. [LUSID APIs](#2.-LUSID-APIs)\n", + "- 3. [Portfolio Creation](#3.-Portfolio-Creation)\n", + "- 4. [Instrument Creation](#4.-Instrument-Creation)\n", + "- 5. [Upsert Transactions](#5.-Upsert-Transactions)\n", + "- 6. [Valuation Recipe Creation](#6.-Valuation-Recipe-Creation)\n", + "- 7. [Upserting Market Data / Quotes Creation](#7.-Upserting-Market-Data-/-Quotes-Creation)\n", + "- 8. [Valuation with IRR](#8.-Valuation-with-IRR)\n", + "- 9. [Data Cleaning](#9.-Data-Cleaning)" + ] + }, + { + "cell_type": "markdown", + "id": "ca52fa05-6dbe-496e-9e13-40202f614933", + "metadata": {}, + "source": [ + "# 1. Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2f3d179-2273-408f-9e7a-cf62c445f6a5", + "metadata": {}, + "outputs": [], + "source": [ + "# Import LUSID libraries\n", + "import lusid \n", + "import lusid.models as lm\n", + "\n", + "from lusidtools.pandas_utils.lusid_pandas import lusid_response_to_data_frame\n", + "\n", + "# Import Libraries\n", + "from datetime import datetime, timedelta\n", + "from lusidtools.lpt.lpt import to_date\n", + "import pytz\n", + "import pandas as pd\n", + "import numpy as np\n", + "import json\n", + "import os\n", + "from lusidtools.cocoon.utilities import create_scope_id\n", + "from lusidtools.cocoon.cocoon import load_from_data_frame\n", + "from lusidtools.cocoon.cocoon_printer import (\n", + " format_instruments_response,\n", + " format_portfolios_response,\n", + " format_transactions_response,\n", + " format_quotes_response,\n", + " format_holdings_response,\n", + ")\n", + "from lusidtools.jupyter_tools import toggle_code\n", + "from lusidjam.refreshing_token import RefreshingToken\n", + "\n", + "# Settings and utility functions to display objects and responses more clearly.\n", + "pd.set_option('float_format', '{:,.4f}'.format)\n", + "\n", + "# Set the secrets path\n", + "secrets_path = os.getenv(\"FBN_SECRETS_PATH\")\n", + "\n", + "# Initiate an API Factory which is the client side object for interacting with LUSID APIs\n", + "api_factory = lusid.utilities.ApiClientFactory(\n", + " token=RefreshingToken(),\n", + " api_secrets_filename = secrets_path,\n", + " app_name=\"LusidJupyterNotebook\")\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "dd902deb-5c00-409b-879c-63f2ac793ed1", + "metadata": {}, + "source": [ + "# 2. LUSID APIs\n", + "\n", + "Firstly, we initialize the LUSID APIs required for the notebook" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ecfff26-4283-4cbf-8023-8bf6bc66c2cd", + "metadata": {}, + "outputs": [], + "source": [ + "# Initiate the LUSID APIs required for the notebook\n", + "instruments_api = api_factory.build(lusid.api.InstrumentsApi)\n", + "transaction_portfolio_api = api_factory.build(lusid.api.TransactionPortfoliosApi)\n", + "portfolio_api = api_factory.build(lusid.api.PortfoliosApi)\n", + "quotes_api = api_factory.build(lusid.api.QuotesApi)\n", + "configuration_recipe_api = api_factory.build(lusid.api.ConfigurationRecipeApi)\n", + "aggregation_api = api_factory.build(lusid.AggregationApi)" + ] + }, + { + "cell_type": "markdown", + "id": "cb46e528-6f37-432a-ad47-28c55aed886a", + "metadata": {}, + "source": [ + "# 3. Portfolio Creation\n", + "\n", + "We proceed by creating a basic transaction portfolio:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3174573e-6a2f-4299-bfda-174eb6ac7ae0", + "metadata": {}, + "outputs": [], + "source": [ + "portfolio_scope= \"Analytics-Examples1\"\n", + "portfolio_code=\"IRR-Notebook-Equity1\"\n", + "portfolio_name=\"IRR-Notebook-Equity1\"\n", + "instrument_scope= \"TestIRR1\"\n", + "effective_at = datetime(2024, 5, 27, 0, 0, tzinfo=pytz.utc)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e5397479-ccc4-4227-a07a-67898dc60e79", + "metadata": {}, + "outputs": [], + "source": [ + "def create_portfolio(scope, portfolio_code, name,instrument_scope):\n", + "\n", + " pf_df = pd.DataFrame(data=[\n", + " {\"portfolio_code\": portfolio_code, \"portfolio_name\": name, \"instrument_scope\": instrument_scope},\n", + " ])\n", + " \n", + " portfolio_mapping = {\n", + " \"required\": {\n", + " \"code\": \"portfolio_code\",\n", + " \"display_name\": \"portfolio_name\",\n", + " \"base_currency\": \"$USD\",\n", + " \"instrument_scopes\": \"instrument_scope\"\n", + " },\n", + " \"optional\": {\n", + " \"created\": f\"${'01-01-2024'}\"\n", + " },\n", + " }\n", + " \n", + " result = load_from_data_frame(\n", + " api_factory=api_factory,\n", + " scope=scope,\n", + " data_frame=pf_df,\n", + " mapping_required=portfolio_mapping[\"required\"],\n", + " mapping_optional=portfolio_mapping[\"optional\"],\n", + " file_type=\"portfolios\",\n", + " )\n", + "\n", + " succ, failed = format_portfolios_response(result)\n", + " display(pd.DataFrame(data=[{\"success\": len(succ), \"failed\": len(failed)}])) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7abe69ac-d122-42f9-9dbd-f3ae6d519491", + "metadata": {}, + "outputs": [], + "source": [ + "create_portfolio(portfolio_scope, portfolio_code, portfolio_name, instrument_scope)" + ] + }, + { + "cell_type": "markdown", + "id": "debaf103-f741-4847-a95e-7aed86bc2be3", + "metadata": {}, + "source": [ + "# 4. Instrument Creation\n", + "\n", + "We create an equity instruments using lumi" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6a363e6-bfea-4b71-8d1c-2fff5cd36a62", + "metadata": {}, + "outputs": [], + "source": [ + "instr_df = pd.read_csv(\"IRR_instruments_upsert.csv\")\n", + "display(instr_df)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dff996fe-b814-4781-babe-ca7fcfa2fd9c", + "metadata": {}, + "outputs": [], + "source": [ + "instrument_mapping = {\n", + " \"identifier_mapping\": {\n", + " \"ClientInternal\": \"client_internal\",\n", + " },\n", + " \"required\": {\n", + " \"name\": \"instrument_name\"\n", + " },\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c05224e4-753c-4da0-a52f-8921ef4d023e", + "metadata": {}, + "outputs": [], + "source": [ + "result = load_from_data_frame(\n", + " api_factory=api_factory,\n", + " scope=portfolio_scope,\n", + " data_frame=instr_df,\n", + " mapping_required=instrument_mapping[\"required\"],\n", + " mapping_optional={},\n", + " file_type=\"instruments\",\n", + " identifier_mapping=instrument_mapping[\"identifier_mapping\"]\n", + ")\n", + "\n", + "succ, failed, errors = format_instruments_response(result)\n", + "pd.DataFrame(data=[{\"success\": len(succ), \"failed\": len(failed), \"errors\": len(errors)}])" + ] + }, + { + "cell_type": "markdown", + "id": "da54df0d-0f42-4c12-9e8e-3ab1edca7ad4", + "metadata": {}, + "source": [ + "# 5. Upsert Transactions\n", + "\n", + "We can enter into a position in the equity, buy 100 @ $400 on 1st March for MSFT_41" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68f12af7-42cd-474d-9a08-d0ee2985d194", + "metadata": {}, + "outputs": [], + "source": [ + "df1 = pd.read_csv(\"IRR_transactions.csv\")\n", + "df1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25837f5e-c79a-4ea3-b265-250ce761796f", + "metadata": {}, + "outputs": [], + "source": [ + "transaction_mapping = {\n", + " \"identifier_mapping\": {\"ClientInternal\": \"client_internal\",\"LusidInstrumentId\": \"instrument_id\"},\n", + " \"required\": {\n", + " \"code\": \"portfolio_code\",\n", + " \"transaction_id\": \"txn_id\",\n", + " \"type\": \"txn_type\",\n", + " \"transaction_price.price\": \"txn_price\",\n", + " \"transaction_price.type\": \"$Price\",\n", + " \"total_consideration.amount\": \"txn_consideration\",\n", + " \"units\": \"txn_units\",\n", + " \"transaction_date\": \"txn_trade_date\",\n", + " \"total_consideration.currency\": \"portfolio_base_currency\",\n", + " \"settlement_date\": \"txn_settle_date\",\n", + " },\n", + " \"optional\": {},\n", + " \"properties\": [],\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4a30187-a2ca-4dfa-b51e-b65e14bee49f", + "metadata": {}, + "outputs": [], + "source": [ + "result = load_from_data_frame(\n", + " api_factory=api_factory,\n", + " scope=portfolio_scope,\n", + " data_frame=df1,\n", + " mapping_required=transaction_mapping[\"required\"],\n", + " mapping_optional=transaction_mapping[\"optional\"],\n", + " file_type=\"transactions\",\n", + " identifier_mapping=transaction_mapping[\"identifier_mapping\"],\n", + " property_columns=transaction_mapping[\"properties\"],\n", + ")\n", + "\n", + "succ, failed = format_transactions_response(result)\n", + "pd.DataFrame(\n", + " data=[{\"success\": len(succ), \"failed\": len(failed), \"errors\": len(errors)}]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "3c574de5-5c76-400b-9bc0-0c32c4f52779", + "metadata": {}, + "source": [ + "# 6. Valuation Recipe Creation\n", + "\n", + "Following the initial setup, we can see to configuring how LUSID will conduct valuation on the swap. This introduces the concept of recipes, which are a set of steps we specify to the valuation engine relating to market data and model specification.\r", + "\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b4732d8-9acf-4126-8886-3dfac7ea81f8", + "metadata": {}, + "outputs": [], + "source": [ + "recipe_code = \"TestIRR_RecipeCode1\"\n", + "recipe_scope = \"Analytics-Examples1\"\n", + "model_name = \"SimpleStatic\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4caeaad-68e3-477a-aea7-25f32594e093", + "metadata": {}, + "outputs": [], + "source": [ + "# Create two different recipes depending on the AllowPartiallySuccessfulEvaluation option\n", + "def UpsertRecipe(recipe_scope,recipe_code,model_name): \n", + " try:\n", + " configuration_recipe = lm.ConfigurationRecipe(\n", + " scope=recipe_scope,\n", + " code=recipe_code,\n", + " market=lm.MarketContext(\n", + " market_rules=[\n", + " lm.MarketDataKeyRule(\n", + " key=\"Quote.ClientInternal.*\",\n", + " supplier=\"Lusid\",\n", + " data_scope=recipe_scope,\n", + " quote_type=\"Price\",\n", + " field=\"mid\",\n", + " quote_interval=\"5D\",\n", + " )\n", + " ]\n", + " ),\n", + " pricing=lm.PricingContext(\n", + " model_rules=[\n", + " lm.VendorModelRule(\n", + " supplier = \"Lusid\",\n", + " model_name = model_name,\n", + " instrument_type = \"Equity\",\n", + " parameters = \"{}\",\n", + " )\n", + " ], \n", + " )\n", + " )\n", + " \n", + " upsert_configuration_recipe_response = configuration_recipe_api.upsert_configuration_recipe(\n", + " upsert_recipe_request=lm.UpsertRecipeRequest(\n", + " configuration_recipe=configuration_recipe\n", + " )\n", + " )\n", + " \n", + " print (f\"Recipe {recipe_code} Upserted Successfully!\")\n", + "\n", + " except lusid.ApiException as e:\n", + " print(f\"Recipie Creation Failed!\")\n", + " print(json.loads(e.body))\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4088901d-1d21-4370-8d44-d64bc1c62c04", + "metadata": {}, + "outputs": [], + "source": [ + "UpsertRecipe(recipe_scope,recipe_code,model_name)" + ] + }, + { + "cell_type": "markdown", + "id": "70922a18-8299-42a4-bbfb-533938371a85", + "metadata": {}, + "source": [ + "# 7. Upserting Market Data / Quotes Creation\n", + "We will be upserting quotes for the equity upserted earlier." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64677cb1-ca93-414b-9095-27451cf2ac77", + "metadata": {}, + "outputs": [], + "source": [ + "#For first instrument\n", + "equity_prices = pd.DataFrame({\n", + " 'date' :[\"2024-03-01\", \"2024-03-27\", \"2024-04-01\",\"2024-04-27\", \"2024-05-01\", \"2024-05-27\"],\n", + " 'price' : [410, 420, 420, 430,430,440]\n", + "})\n", + "equity_prices.insert(0, 'ClientInternal', 'MSFT_41')\n", + "equity_prices.insert(3, 'currency', 'USD')\n", + "\n", + "equity_prices" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b85924c-22f2-4076-aa26-854cd60ef6de", + "metadata": {}, + "outputs": [], + "source": [ + "quotes_mapping = {\n", + " \"quote_id.effective_at\": \"date\",\n", + " \"quote_id.quote_series_id.provider\": \"$Lusid\",\n", + " \"quote_id.quote_series_id.quote_type\": \"$Price\",\n", + " \"quote_id.quote_series_id.instrument_id_type\": \"$ClientInternal\",\n", + " \"quote_id.quote_series_id.instrument_id\": \"ClientInternal\",\n", + " \"metric_value.unit\": \"currency\",\n", + " \"metric_value.value\": \"price\",\n", + " \"quote_id.quote_series_id.field\": \"$mid\",\n", + " \n", + "}\n", + "\n", + " \n", + "result = load_from_data_frame(\n", + " api_factory = api_factory,\n", + " scope=recipe_scope,\n", + " data_frame=equity_prices,\n", + " mapping_required=quotes_mapping,\n", + " mapping_optional={},\n", + " file_type=\"quotes\"\n", + ")\n", + "\n", + "\n", + "\n", + "succ, failed, errors = format_quotes_response(result)\n", + "display(pd.DataFrame(data=[{\"success\": len(succ), \"failed\": len(failed), \"errors\": len(errors)}]))" + ] + }, + { + "cell_type": "markdown", + "id": "e201c93e-7fb2-4916-b3a5-29e32d49e805", + "metadata": {}, + "source": [ + "# 8. Valuation with IRR" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3be2f7ee-27f5-4804-baa4-8e24b7405777", + "metadata": {}, + "outputs": [], + "source": [ + "#Function to get valuation\n", + "def get_valuation(date: datetime, portfolio_scope: str, portfolio_code: str, recipe_scope: str, recipe_code: str, metrics: list, groupBy: str=[\"Instrument/default/Name\"]) -> pd.DataFrame: \n", + " \n", + " try:\n", + " valuation_request = lm.ValuationRequest(\n", + " recipe_id=lm.ResourceId(\n", + " scope=recipe_scope,\n", + " code=recipe_code\n", + " ),\n", + " metrics=metrics,\n", + " group_by=groupBy,\n", + " portfolio_entity_ids=[\n", + " lm.PortfolioEntityId(scope=portfolio_scope, code=portfolio_code)\n", + " ],\n", + " valuation_schedule=lm.ValuationSchedule(effective_at=date.isoformat()),\n", + " )\n", + " \n", + " val_response = aggregation_api.get_valuation(valuation_request=valuation_request)\n", + " val_data = val_response.data\n", + " vals_df = pd.DataFrame(val_data)\n", + " \n", + " return vals_df\n", + " \n", + " except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"errorDetails\"][0][\"id\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5285a600-6180-4ae2-b3ed-c2ab8b83d70f", + "metadata": {}, + "outputs": [], + "source": [ + "# Set the metrics to be requested from valuation\n", + "metrics = [\n", + " lm.AggregateSpec(\"Instrument/default/Name\", \"Value\"),\n", + " lm.AggregateSpec(\"Instrument/default/ClientInternal\", \"Value\"),\n", + " lm.AggregateSpec(\"Valuation/PV\", \"Value\"),\n", + " lm.AggregateSpec(\"ProfitAndLoss/PortfolioInternalRateOfReturn\", \"Value\", {\"Window\" : \"MTD\"}),\n", + " lm.AggregateSpec(\"Holding/default/Units\", \"Value\")\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67840aee-06ae-43d9-9de5-7feaf23f46be", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "df = get_valuation(effective_at, portfolio_scope,portfolio_code,recipe_scope,recipe_code,metrics,[\"Instrument/default/Name\"])\n", + "df" + ] + }, + { + "cell_type": "markdown", + "id": "d860c351-af55-41d4-90c1-28aacf9ad766", + "metadata": {}, + "source": [ + "# 8.1 IRR Explained\n", + "We get a Portfolio IRR of 38%, to validate this, we first confirm the valuation at the start of the Month" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76600bd9-a1ca-469d-ad5f-647192179a7e", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "df_start = get_valuation(datetime(2024, 5, 1, 0, 0, tzinfo=pytz.utc), portfolio_scope,portfolio_code,recipe_scope,recipe_code,[ lm.AggregateSpec(\"Instrument/default/Name\", \"Value\"), lm.AggregateSpec(\"Valuation/PV\", \"Value\") ],[\"Portfolio/default/Name\"])\n", + "\n", + "df_start" + ] + }, + { + "cell_type": "markdown", + "id": "f548883e-76ce-45b3-bc07-863ce12b4966", + "metadata": {}, + "source": [ + "We have a value of 43,000 on 1st May 2024 (which is expected as the stock was $430) and then a final value of 44,000 on 27th May 2024.\n", + "\n", + "The IRR value of 38\\% can be validated in Excel using XIRR(), note the inital value should be set to -43,000.\n", + "\n", + "We can also confirm it here by showing that:-43,000 + 44,000 / (1+irr) ^ (26 / 365) = 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e104f68-a231-4e85-b054-638382084cdb", + "metadata": {}, + "outputs": [], + "source": [ + "irr = df.iloc[0,3]\n", + "days = 27 - 1\n", + "\n", + "divisor = (1+irr)**(days/365)\n", + "\n", + "-43_000 + 44_000 / divisor" + ] + }, + { + "cell_type": "markdown", + "id": "4003a064-e69a-4baf-9eb3-c3e14628a7cc", + "metadata": {}, + "source": [ + "# 9. Data Cleaning\n", + "The following chunks of code help you clean data by deleting recipes, quotes, instruments and portfolio created during the above sample\n", + "\n", + "(for quotes and instruments you have to specify the instrument individually and effective date for quotes must match the effective date at time of creation)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "613418d8-a1d0-4e3b-bca5-553ed1f41b9b", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Recipe\n", + "try:\n", + " delete_recipe = configuration_recipe_api.delete_configuration_recipe(\n", + " scope=recipe_scope,\n", + " code=recipe_code,\n", + " )\n", + "\n", + " print(delete_recipe)\n", + "\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a61fd2e1-00e2-4ff6-ad11-ee34d86ae8cb", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Quotes\n", + "\n", + "#Have to run this for individual instruments by changing instrument_id\n", + "\n", + "try:\n", + " delete_quotes = quotes_api.delete_quotes(\n", + " scope=recipe_scope,\n", + " request_body={ \n", + " \"request_1\": lm.QuoteId(\n", + " quote_series_id=lm.QuoteSeriesId(\n", + " provider='Lusid', \n", + " quote_type='Price',\n", + " instrument_id_type= 'ClientInternal',\n", + " instrument_id= 'MSFT_41',\n", + " field='mid'\n", + " ),\n", + " effective_at=\"2024-03-01T00:00:00Z\"\n", + " ),\n", + " \"request_2\": lm.QuoteId(\n", + " quote_series_id=lm.QuoteSeriesId(\n", + " provider='Lusid', \n", + " quote_type='Price',\n", + " instrument_id_type= 'ClientInternal',\n", + " instrument_id= 'MSFT_41',\n", + " field='mid'\n", + " ),\n", + " effective_at=\"2024-03-27T00:00:00Z\"\n", + " ),\n", + " \"request_3\": lm.QuoteId(\n", + " quote_series_id=lm.QuoteSeriesId(\n", + " provider='Lusid', \n", + " quote_type='Price',\n", + " instrument_id_type= 'ClientInternal',\n", + " instrument_id= 'MSFT_41',\n", + " field='mid'\n", + " ),\n", + " effective_at=\"2024-04-01T00:00:00Z\"\n", + " ),\n", + " \"request_4\": lm.QuoteId(\n", + " quote_series_id=lm.QuoteSeriesId(\n", + " provider='Lusid', \n", + " quote_type='Price',\n", + " instrument_id_type= 'ClientInternal',\n", + " instrument_id= 'MSFT_41',\n", + " field='mid'\n", + " ),\n", + " effective_at=\"2024-04-27T00:00:00Z\"\n", + " ),\n", + " \"request_5\": lm.QuoteId(\n", + " quote_series_id=lm.QuoteSeriesId(\n", + " provider='Lusid', \n", + " quote_type='Price',\n", + " instrument_id_type= 'ClientInternal',\n", + " instrument_id= 'MSFT_41',\n", + " field='mid'\n", + " ),\n", + " effective_at=\"2024-05-01T00:00:00Z\"\n", + " ),\n", + " \"request_6\": lm.QuoteId(\n", + " quote_series_id=lm.QuoteSeriesId(\n", + " provider='Lusid', \n", + " quote_type='Price',\n", + " instrument_id_type= 'ClientInternal',\n", + " instrument_id= 'MSFT_41',\n", + " field='mid'\n", + " ),\n", + " effective_at=\"2024-05-27T00:00:00Z\"\n", + " )\n", + " \n", + " \n", + " }\n", + " )\n", + " \n", + " print(delete_quotes)\n", + "\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1366c6c4-b180-42fd-b5e8-cdeb25e5d6b3", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Instruments\n", + "\n", + "#Have to run this for individual instruments by changing identifier value\n", + "\n", + "try:\n", + " delete_instrument = instruments_api.delete_instrument(\n", + " identifier_type=\"ClientInternal\", identifier= 'MSFT_41'\n", + " )\n", + "\n", + " print(delete_instrument)\n", + "\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93ffd96f-82bb-416b-9b2c-bf7381056c16", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Portfolio\n", + "try:\n", + " delete_portfolio = portfolio_api.delete_portfolio(portfolio_scope, portfolio_code)\n", + " \n", + " print(delete_portfolio)\n", + "\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/use-cases/irr-calculation/IRR_instruments_upsert.csv b/examples/use-cases/irr-calculation/IRR_instruments_upsert.csv new file mode 100644 index 00000000..6fbf9e05 --- /dev/null +++ b/examples/use-cases/irr-calculation/IRR_instruments_upsert.csv @@ -0,0 +1,2 @@ +instrument_name,client_internal,currency,figi,exchange_code,ticker,market_sector +Microsoft_41,MSFT_41,USD,BBG000BVPXP1,UN,msft_41,equity diff --git a/examples/use-cases/irr-calculation/IRR_transactions.csv b/examples/use-cases/irr-calculation/IRR_transactions.csv new file mode 100644 index 00000000..ebcc3b1e --- /dev/null +++ b/examples/use-cases/irr-calculation/IRR_transactions.csv @@ -0,0 +1,3 @@ +portfolio_code,portfolio_name,portfolio_base_currency,instrument_id,client_internal,txn_id,txn_type,txn_trade_date,txn_settle_date,txn_units,txn_price,txn_consideration +IRR-Notebook-Equity1,IRR-Notebook-Equity1,USD,CCY_USD,,TXN_IRRSample_1,FundsIn,01/03/2024,01/03/2024,40000,0,40000 +IRR-Notebook-Equity1,IRR-Notebook-Equity1,USD,,MSFT_41,TXN_IRRSample_2,Buy,01/03/2024,01/03/2024,100,400,40000 From 4cdd3207e98b21b9a15a6a1633435b66e613b104 Mon Sep 17 00:00:00 2001 From: fmak404 Date: Wed, 17 Jul 2024 10:20:40 +0100 Subject: [PATCH 03/14] Update IRR_Sample_Notebook.ipynb --- .../use-cases/irr-calculation/IRR_Sample_Notebook.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb b/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb index 8b5fda5b..1739b46a 100644 --- a/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb +++ b/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb @@ -309,8 +309,8 @@ "metadata": {}, "outputs": [], "source": [ - "df1 = pd.read_csv(\"IRR_transactions.csv\")\n", - "df1" + "df_transac = pd.read_csv(\"IRR_transactions.csv\")\n", + "df_transac" ] }, { @@ -349,7 +349,7 @@ "result = load_from_data_frame(\n", " api_factory=api_factory,\n", " scope=portfolio_scope,\n", - " data_frame=df1,\n", + " data_frame=df_transac,\n", " mapping_required=transaction_mapping[\"required\"],\n", " mapping_optional=transaction_mapping[\"optional\"],\n", " file_type=\"transactions\",\n", From cdf4b610823745aeba0de55495e49b26a64209e7 Mon Sep 17 00:00:00 2001 From: fmak404 Date: Tue, 23 Jul 2024 10:48:26 +0100 Subject: [PATCH 04/14] Create amortisation-rulesets --- examples/use-cases/amortisation-rulesets | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/use-cases/amortisation-rulesets diff --git a/examples/use-cases/amortisation-rulesets b/examples/use-cases/amortisation-rulesets new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/use-cases/amortisation-rulesets @@ -0,0 +1 @@ + From 022d87df9f4df4921bbc17457c8626f4d550ee0f Mon Sep 17 00:00:00 2001 From: fmak404 Date: Tue, 23 Jul 2024 10:49:58 +0100 Subject: [PATCH 05/14] Delete examples/use-cases/amortisation-rulesets --- examples/use-cases/amortisation-rulesets | 1 - 1 file changed, 1 deletion(-) delete mode 100644 examples/use-cases/amortisation-rulesets diff --git a/examples/use-cases/amortisation-rulesets b/examples/use-cases/amortisation-rulesets deleted file mode 100644 index 8b137891..00000000 --- a/examples/use-cases/amortisation-rulesets +++ /dev/null @@ -1 +0,0 @@ - From 86c87b0ac2c344e5ca0f1c770f99a34565864cb1 Mon Sep 17 00:00:00 2001 From: fmak404 Date: Tue, 23 Jul 2024 10:51:26 +0100 Subject: [PATCH 06/14] Create AmortisationRules --- examples/use-cases/amortisation-rulesets/AmortisationRules | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/use-cases/amortisation-rulesets/AmortisationRules diff --git a/examples/use-cases/amortisation-rulesets/AmortisationRules b/examples/use-cases/amortisation-rulesets/AmortisationRules new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/use-cases/amortisation-rulesets/AmortisationRules @@ -0,0 +1 @@ + From a90e105df76501ce07b5c0f7d8652122cf33291a Mon Sep 17 00:00:00 2001 From: fmak404 Date: Tue, 23 Jul 2024 10:51:49 +0100 Subject: [PATCH 07/14] Add files via upload --- .../AmortisationRules.ipynb | 1164 +++++++++++++++++ .../Transaction_Data.csv | 3 + 2 files changed, 1167 insertions(+) create mode 100644 examples/use-cases/amortisation-rulesets/AmortisationRules.ipynb create mode 100644 examples/use-cases/amortisation-rulesets/Transaction_Data.csv diff --git a/examples/use-cases/amortisation-rulesets/AmortisationRules.ipynb b/examples/use-cases/amortisation-rulesets/AmortisationRules.ipynb new file mode 100644 index 00000000..bec6a5c2 --- /dev/null +++ b/examples/use-cases/amortisation-rulesets/AmortisationRules.ipynb @@ -0,0 +1,1164 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "61f113e0-ba28-400b-9ed8-216883e8df87", + "metadata": {}, + "outputs": [], + "source": [ + "from lusidtools.jupyter_tools import toggle_code\n", + "\n", + "\"\"\"Amortisation RuleSet\n", + "\n", + "Attributes\n", + "----------\n", + "Instruments\n", + "Transactions\n", + "Amortisation RuleSets\n", + "AmortisationRules\n", + "Recipe\n", + "Valuation\n", + "\"\"\"\n", + "\n", + "toggle_code(\"Toggle Docstring\")" + ] + }, + { + "cell_type": "markdown", + "id": "0d686878-b96e-4112-9528-974ce3ff08b3", + "metadata": {}, + "source": [ + "# Amortisation Rule Sets\n", + "\n", + "This example demonstrates how to create, update, and delete Amortisation Rule Sets and Rules, and how changes to the amortisation rules affect the amortisation valuation.\n", + "\n", + "We will create bond instruments, add properties to a specific instrument, create an amortisation ruleset, set up a portfolio, add properties to the portfolio, and configure the amortisation rules.\n", + "\n", + "Further we will upsert similar transactions to the respective bond instruments to demonstrate the effects of the amortisation rules through comparison. \n", + "\n", + "We will then create a simple recipe for valuation, including the valuation function.\n", + "\n", + "Once Done, we will then demostrate how updating the amortisation rules impacts the valuation." + ] + }, + { + "cell_type": "markdown", + "id": "3581f291-8ca2-4ef9-9cd6-03f44385abd2", + "metadata": {}, + "source": [ + "## Table of Contents:\n", + "- 1. [Imports](#1.-Imports)\n", + "- 2. [LUSID APIs](#2.-LUSID-APIs)\n", + "- 3. [Instrument Creation](#3.-Instrument-Creation)\n", + "- 4. [Amortisation RuleSet Creation](#4.-Amortisation-RuleSet-Creation)\n", + "- 5. [Portfolio Creation](#5.-Portfolio-Creation)\n", + "- 6. [Setting Amortisation Rule](#6.-Setting-Amortisation-Rule)\n", + "- 7. [Upsert Transactions](#7.-Upsert-Transactions)\n", + "- 8. [Recipe Creation](#8.-Recipe-Creation)\n", + "- 9. [Valuation](#9.-Valuation)\n", + "- 10. [Data Cleaning](#9.-Data-Cleaning)" + ] + }, + { + "cell_type": "markdown", + "id": "1ace0ed9-9c99-4db4-937e-2cfc6a80aabc", + "metadata": {}, + "source": [ + "# 1. Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60ee9804-9071-46f9-907d-3d9c19855b4b", + "metadata": {}, + "outputs": [], + "source": [ + "# Import necessary libraries\n", + "import lusid\n", + "import finbourne_access\n", + "import lusid.models as models\n", + "import openpyxl\n", + "import subprocess\n", + "from lusidtools.cocoon.cocoon import load_from_data_frame\n", + "from lusidtools.cocoon.cocoon_printer import (\n", + " format_instruments_response,\n", + " format_portfolios_response,\n", + " format_transactions_response,\n", + " format_quotes_response,\n", + ")\n", + "from lusidtools.pandas_utils.lusid_pandas import lusid_response_to_data_frame\n", + "from lusidjam import RefreshingToken\n", + "from fbnsdkutilities import ApiClientFactory\n", + "from finbourne_access.utilities import ApiClientFactory as AccessApiClientFactory\n", + "from finbourne_access import models as AccessModels\n", + "import os\n", + "import json\n", + "from IPython.display import Image\n", + "import pandas as pd\n", + "import pytz\n", + "from datetime import datetime\n", + "from lusidtools.lpt.lpt import to_date\n", + "pd.set_option(\"display.max_columns\", None)\n", + "\n", + "# Authenticate our user and create our API client\n", + "secrets_path = os.getenv(\"FBN_SECRETS_PATH\")\n", + "\n", + "lusid_api_factory = lusid.utilities.ApiClientFactory(\n", + " token=RefreshingToken(),\n", + " api_secrets_filename=secrets_path,\n", + " app_name=\"LusidJupyterNotebook\",\n", + ")\n", + "\n", + "api_client = lusid_api_factory.api_client\n", + "\n", + "lusid_api_url = api_client.configuration.host\n", + "access_api_url = lusid_api_url[: lusid_api_url.rfind(\"/\") + 1] + \"access\"\n", + "identity_api_url = lusid_api_url[: lusid_api_url.rfind(\"/\") + 1] + \"identity\"\n", + "\n", + "access_api_factory = finbourne_access.utilities.ApiClientFactory(\n", + " token=api_client.configuration.access_token,\n", + " access_url=access_api_url,\n", + " app_name=\"LusidJupyterNotebook\",\n", + ")\n", + "api_factory = ApiClientFactory(lusid, token=RefreshingToken())" + ] + }, + { + "cell_type": "markdown", + "id": "2b1c49bb-a0b7-413e-8153-2c495e00aa4a", + "metadata": {}, + "source": [ + "# 2. LUSID APIs\n", + "Firstly, we initialize the LUSID APIs required for the notebook" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25c79a41-31bc-403a-a69b-d33672a8726f", + "metadata": {}, + "outputs": [], + "source": [ + "# Initiate the LUSID APIs required for the notebook\n", + "\n", + "instruments_api = api_factory.build(lusid.api.InstrumentsApi)\n", + "amortisation_api = api_factory.build(lusid.api.AmortisationRuleSetsApi)\n", + "transaction_portfolio_api = api_factory.build(lusid.api.TransactionPortfoliosApi)\n", + "portfolio_api = api_factory.build(lusid.api.PortfoliosApi)\n", + "configuration_recipe_api = api_factory.build(lusid.api.ConfigurationRecipeApi)\n", + "aggregation_api = api_factory.build(lusid.AggregationApi)\n", + "property_api = api_factory.build(lusid.PropertyDefinitionsApi)" + ] + }, + { + "cell_type": "markdown", + "id": "1d68849e-cee3-483d-8c9b-c68a44df2888", + "metadata": {}, + "source": [ + "# 3. Instrument Creation\n", + "We will create the relevant bond instruments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74300dd0-961b-434a-be5b-6e8f88f04b0a", + "metadata": {}, + "outputs": [], + "source": [ + "#Setting Scope and Code to be used throughout the notebook\n", + "\n", + "scope = \"valuation-sample\"\n", + "portfolio_code = \"EQUITY_UK_1\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02dcb40b-9f46-48e7-b97e-7d876d1ce836", + "metadata": {}, + "outputs": [], + "source": [ + "#Defining function for bond instrument creation\n", + "\n", + "def create_bond(currency,payment_frequency,roll_convention,day_count_convention,payment_calendars,reset_calendars,settle_days,reset_days,start_date,maturity_date,dom_ccy,\n", + " principal,coupon_rate,bond_identifier,bond_name):\n", + "\n", + " flow_conventions = lusid.FlowConventions(\n", + " currency=currency,\n", + " payment_frequency=payment_frequency,\n", + " roll_convention=roll_convention,\n", + " day_count_convention=day_count_convention,\n", + " payment_calendars=payment_calendars,\n", + " reset_calendars=reset_calendars,\n", + " settle_days=settle_days,\n", + " reset_days=reset_days,\n", + " )\n", + "\n", + " bond = lusid.Bond(\n", + " start_date=start_date,\n", + " maturity_date=maturity_date,\n", + " dom_ccy=dom_ccy,\n", + " principal=principal,\n", + " coupon_rate=coupon_rate,\n", + " flow_conventions=flow_conventions,\n", + " identifiers={},\n", + " instrument_type=\"Bond\",\n", + " calculation_type=\"Standard\",\n", + " )\n", + "\n", + " # define the instrument to be upserted\n", + " bond_definition = lusid.InstrumentDefinition(\n", + " name=bond_name,\n", + " identifiers={\"ClientInternal\": lusid.InstrumentIdValue(bond_identifier)},\n", + " definition=bond,\n", + " )\n", + "\n", + " # upsert the instrument\n", + " upsert_request = {bond_identifier: bond_definition}\n", + " upsert_response = instruments_api.upsert_instruments(request_body=upsert_request)\n", + " bond_luid = upsert_response.values[bond_identifier].lusid_instrument_id\n", + " return(bond_luid)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd48d82d-ce0f-490f-b0ac-143e9712f8b5", + "metadata": {}, + "outputs": [], + "source": [ + "#Creating first Bond Instrument\n", + "\n", + "currency = \"GBP\"\n", + "payment_frequency = \"1M\"\n", + "roll_convention = \"none\"\n", + "day_count_convention = \"Actual365\"\n", + "payment_calendars = []\n", + "reset_calendars = []\n", + "settle_days = 0\n", + "reset_days = 0\n", + "start_date = datetime(2024, 1, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", + "maturity_date = datetime(2024, 8, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", + "dom_ccy = \"GBP\"\n", + "principal = 10487\n", + "coupon_rate = 0.07\n", + "bond_identifier = \"Test_Bond_A\"\n", + "bond_name = \"TestBond_A\"\n", + "\n", + "luid = create_bond(currency, payment_frequency, roll_convention, day_count_convention, payment_calendars, reset_calendars, settle_days, reset_days, start_date, maturity_date, dom_ccy, \n", + " principal, coupon_rate, bond_identifier, bond_name)\n", + "\n", + "luid" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0267add-3535-4eb6-a7c2-a57976952b5d", + "metadata": {}, + "outputs": [], + "source": [ + "#Creating second Bond Instrument\n", + "\n", + "currency = \"GBP\"\n", + "payment_frequency = \"1M\"\n", + "roll_convention = \"none\"\n", + "day_count_convention = \"Actual365\"\n", + "payment_calendars = []\n", + "reset_calendars = []\n", + "settle_days = 0\n", + "reset_days = 0\n", + "start_date = datetime(2024, 1, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", + "maturity_date = datetime(2024, 8, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", + "dom_ccy = \"GBP\"\n", + "principal = 10487\n", + "coupon_rate = 0.07\n", + "bond_identifier = \"Test_Bond_B\"\n", + "bond_name = \"TestBond_B\"\n", + "\n", + "luid_1=create_bond(currency, payment_frequency, roll_convention, day_count_convention, payment_calendars, reset_calendars, settle_days, reset_days, start_date, maturity_date, dom_ccy,\n", + " principal, coupon_rate, bond_identifier, bond_name)\n", + "\n", + "luid_1" + ] + }, + { + "cell_type": "markdown", + "id": "11bab45e-eb3e-469e-af35-747e83f53201", + "metadata": {}, + "source": [ + "# 3.1 Instrument Property\n", + "We will create and upsert instrument property for the first instrument so that it can be used when defining amortisation rules\n", + "\n", + "We will do so only for one instrument so that impact of amortisation rules on instruments can be compared based on instrument properties" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bdda65d2-d9f2-4e78-8b39-683e394a94a2", + "metadata": {}, + "outputs": [], + "source": [ + "#Creating Instrument Property\n", + "try:\n", + " instrument_prop = property_api.create_property_definition(\n", + " create_property_definition_request=lusid.CreatePropertyDefinitionRequest(\n", + " domain = \"Instrument\",\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " data_type_id= lusid.ResourceId(\n", + " scope = \"system\",\n", + " code = \"string\",\n", + " ),\n", + " life_time= \"Perpetual\",\n", + " constraint_style = \"Property\",\n", + " display_name = \"Bond_Test_Prop\",\n", + " property_description = \"Testing Bond Property\",\n", + " )\n", + " \n", + " )\n", + " print(\"Instrument Property Created\")\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "286fb7e7-c6d2-4963-86af-dd89df53b68b", + "metadata": {}, + "outputs": [], + "source": [ + "#Upserting property to first Bond Instrument\n", + "try:\n", + " upsert_instrument_prop = instruments_api.upsert_instruments_properties(\n", + " upsert_instrument_property_request=[\n", + " lusid.UpsertInstrumentPropertyRequest(\n", + " identifier_type=\"LusidInstrumentId\",\n", + " identifier=luid,\n", + " properties=[\n", + " lusid.ModelProperty(\n", + " key=\"Instrument/valuation-sample/EQUITY_UK_1\",\n", + " value=lusid.PropertyValue(\n", + " label_value='fahad'\n", + " )\n", + " )\n", + " ]\n", + " )\n", + " ]\n", + " )\n", + " print(\"Instrument Property Upserted\")\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "markdown", + "id": "c3ed4107-5074-4731-9e05-105ece1e1070", + "metadata": {}, + "source": [ + "# 4. Amortisation RuleSet Creation\n", + "We will create the relevant Amortisation Rule Set" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78b84358-17fc-43f8-9479-729ed2193348", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "#Creating Amortisation Ruleset\n", + "try:\n", + " create_amt = amortisation_api.create_amortisation_rule_set(\n", + " scope= scope,\n", + " create_amortisation_rule_set_request=models.CreateAmortisationRuleSetRequest(\n", + " code=portfolio_code,\n", + " display_name='Testing_Amortisation_Rule',\n", + " description='This is a test run'\n", + " )\n", + " )\n", + " print(\"Amortisation RuleSet Created\")\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "markdown", + "id": "84a6b0bf-8a3d-420f-8133-64e608b4c2f0", + "metadata": {}, + "source": [ + "# 5. Portfolio Creation\n", + "We proceed by creating a basic transaction portfolio" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12bdbe41-a719-4abc-b980-af6ac9b27650", + "metadata": {}, + "outputs": [], + "source": [ + "# Creating a Transaction Portfolio\n", + "\n", + "try:\n", + " create_portfolio_response = transaction_portfolio_api.create_portfolio(\n", + " scope=scope,\n", + " create_transaction_portfolio_request=models.CreateTransactionPortfolioRequest(\n", + " display_name=\"RuleSet_Test\",\n", + " code=portfolio_code,\n", + " base_currency=\"GBP\",\n", + " created=datetime(2024, 1, 1, 0, 0, tzinfo=pytz.utc).isoformat(),\n", + " sub_holding_keys=[],\n", + " amortisation_method='NoAmortisation',\n", + " amortisation_rule_set_id={\n", + " \"scope\": scope,\n", + " \"code\": portfolio_code\n", + " }\n", + "\n", + " ),\n", + " )\n", + "\n", + " print(\"Portfolio Created\")\n", + "\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])\n" + ] + }, + { + "cell_type": "markdown", + "id": "5ea98c2c-7be2-4912-87bc-7735482d1f64", + "metadata": {}, + "source": [ + "# 5.1 Portfolio Property\n", + "We will create and upsert portfolio property for the relevant portfolio" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18e40c13-2639-4ace-abfa-38f32c75d683", + "metadata": {}, + "outputs": [], + "source": [ + "#Creating Portfolio Property\n", + "try:\n", + " property_creation = property_api.create_property_definition(\n", + " create_property_definition_request=lusid.CreatePropertyDefinitionRequest(\n", + " domain = \"Portfolio\",\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " data_type_id= lusid.ResourceId(\n", + " scope = \"system\",\n", + " code = \"string\",\n", + " ),\n", + " life_time= \"Perpetual\",\n", + " constraint_style = \"Property\",\n", + " display_name = \"Portfolio_property\",\n", + " property_description = \"Testing amortisation rule Property\",\n", + " )\n", + " \n", + " )\n", + " print('Portfolio Property Created')\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ebf29d2-a432-4bcd-9dce-7c23dadbcaf7", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "#Upserting property to Portfolio\n", + "try:\n", + " upsert_port= portfolio_api.upsert_portfolio_properties(\n", + " scope= scope,\n", + " code= portfolio_code,\n", + " request_body={\n", + " \"Portfolio/valuation-sample/EQUITY_UK_1\": {\n", + " \n", + " \"key\": \"Portfolio/valuation-sample/EQUITY_UK_1\",\n", + " \"value\": {\n", + " \"labelValue\": \"fahad\"\n", + " },\n", + " },\n", + " }\n", + " \n", + " ) \n", + " print(\"Portfolio Property Upserted\")\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "markdown", + "id": "974d2d19-18e1-4b17-a592-4d3db16ae125", + "metadata": {}, + "source": [ + "# 6. Setting Amortisation Rule\n", + "We will upsert rules to the Amortisation Rule Set" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1685dc4-f641-4da3-937b-9f858ae8d5ec", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "#Set Amortisation Rules\n", + "\n", + "set_amt_rule = amortisation_api.set_amortisation_rules(\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", + " rules_interval=models.RulesInterval(\n", + " effective_range={\n", + " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", + " },\n", + " rules=[]\n", + " )\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "6853d35f-8b18-43a6-bc3d-56fde9f5794c", + "metadata": {}, + "source": [ + "# 6.1 List Amortisation RuleSets\n", + "Using this endpoint we can see the list of Amortisation RuleSets and associates Rules" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b2b1797-f5c0-46f9-96d8-baa83319d641", + "metadata": {}, + "outputs": [], + "source": [ + "#List RuleSets\n", + "\n", + "list_amt= amortisation_api.list_amortisation_rule_sets()\n", + "list_amt" + ] + }, + { + "cell_type": "markdown", + "id": "95e7d51f-227a-4834-9656-77faa317e441", + "metadata": {}, + "source": [ + "# 7. Upsert Transactions\n", + "We will upsert similar transactions for the created instruments to observe the impacts of the amortisation rules" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c07f66c-c27a-418a-9d28-3d79faeec7cd", + "metadata": {}, + "outputs": [], + "source": [ + "transactions = pd.read_csv(\"Transaction_Data.csv\")\n", + "transactions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c83f29b0-3530-4e28-bb6f-f32aa5f46ada", + "metadata": {}, + "outputs": [], + "source": [ + "#Upserting Transactions\n", + "transaction_request = [\n", + " lusid.TransactionRequest(\n", + " transaction_id=row[\"txn_id\"],\n", + " type=row[\"type\"],\n", + " instrument_identifiers={\n", + " \"Instrument/default/ClientInternal\": row[\"client_id\"]\n", + " },\n", + " transaction_date=to_date(row[\"trade_date\"]).isoformat(),\n", + " settlement_date=to_date(row[\"settlement_date\"]).isoformat(),\n", + " units=row[\"quantity\"],\n", + " transaction_price=lusid.TransactionPrice(price=row[\"price\"], type=\"Price\"),\n", + " total_consideration=lusid.CurrencyAndAmount(\n", + " amount=row[\"total_consideration\"], currency=row[\"currency\"]\n", + " ),\n", + " )\n", + " for index, row in transactions.iterrows()\n", + "]\n", + "\n", + "response = transaction_portfolio_api.upsert_transactions(\n", + " scope=scope, code=portfolio_code, transaction_request=transaction_request\n", + ")\n", + "\n", + "print(f\"Transactions succesfully updated at time: {response.version.as_at_date}\")" + ] + }, + { + "cell_type": "markdown", + "id": "2d169207-b6ce-48eb-a506-ee8009d5c528", + "metadata": {}, + "source": [ + "# 8. Recipe Creation\n", + "\n", + "Following the initial setup, we can see to configuring how LUSID will conduct valuation. This introduces the concept of recipes, which are a set of steps we specify to the valuation engine relating to market data and model specification.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "abacf1ce-1bb8-4c54-806b-4758ef2b6dcf", + "metadata": {}, + "outputs": [], + "source": [ + "# Creating Recipe to perform a valuation\n", + "try:\n", + " configuration_recipe = models.ConfigurationRecipe(\n", + " scope=scope,\n", + " code=portfolio_code,\n", + " market=models.MarketContext(\n", + " market_rules=[\n", + " # define how to resolve the quotes\n", + " models.MarketDataKeyRule(\n", + " key=\"Quote.ClientInternal.*\",\n", + " supplier=\"Lusid\",\n", + " data_scope=scope,\n", + " quote_type=\"Price\",\n", + " field= \"bid\",\n", + " ),\n", + " ],\n", + " options=models.MarketOptions(\n", + " default_supplier=\"Lusid\",\n", + " default_instrument_code_type=\"ClientInternal\",\n", + " default_scope=scope,\n", + " ),\n", + " ),\n", + " pricing=models.PricingContext(\n", + " options={\"AllowPartiallySuccessfulEvaluation\": True},\n", + " ),\n", + " )\n", + " \n", + " upsert_configuration_recipe_response = configuration_recipe_api.upsert_configuration_recipe(\n", + " upsert_recipe_request=models.UpsertRecipeRequest(\n", + " configuration_recipe=configuration_recipe\n", + " )\n", + " )\n", + " print(\"Recipe Created Successfully\")\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "markdown", + "id": "fab0149c-87d9-41aa-87d8-07c7bce5dd5d", + "metadata": {}, + "source": [ + "# 9. Valuation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3e702eb-232d-45dc-ab28-77c798d85a3e", + "metadata": {}, + "outputs": [], + "source": [ + "#Defining function for Valuation\n", + "\n", + "def generate_valuation_request(valuation_effectiveAt):\n", + "\n", + " # Create the valuation request\n", + " valuation_request = models.ValuationRequest(\n", + " recipe_id=models.ResourceId(\n", + " scope=scope, code=portfolio_code\n", + " ),\n", + " metrics=[\n", + " models.AggregateSpec(\"Instrument/default/Name\", \"Value\"),\n", + " models.AggregateSpec(\"Valuation/PvInReportCcy\", \"Proportion\"),\n", + " models.AggregateSpec(\"Valuation/PvInReportCcy\", \"Sum\"),\n", + " models.AggregateSpec(\"Holding/default/Units\", \"Sum\"),\n", + " models.AggregateSpec(\"Holding/AmortisedCost\", \"Value\"),\n", + " ],\n", + " group_by=[\"Instrument/default/Name\"],\n", + " portfolio_entity_ids=[\n", + " models.PortfolioEntityId(scope=scope, code=portfolio_code)\n", + " ],\n", + " valuation_schedule=models.ValuationSchedule(\n", + " effective_at=valuation_effectiveAt.isoformat()\n", + " ),\n", + " )\n", + "\n", + " return valuation_request" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c16f197-ef0c-4f80-9bef-b0cd2404989e", + "metadata": {}, + "outputs": [], + "source": [ + "#Getting Valuation\n", + "\n", + "aggregation = aggregation_api.get_valuation(\n", + " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", + " )\n", + ")\n", + "\n", + "pd.DataFrame(aggregation.data)" + ] + }, + { + "cell_type": "markdown", + "id": "f1e86e68-fb62-4d08-a1fc-a99fd59f3a86", + "metadata": {}, + "source": [ + "We can see that the Amortised Cost is calculated based on No Amortisation method as set when creating the portfolio. No Amortisation Rules were defined at this point " + ] + }, + { + "cell_type": "markdown", + "id": "52fe7dbb-1912-4e14-ab4e-026a3ae4d5c3", + "metadata": {}, + "source": [ + "# 9.1 Amortisation Rules Filters" + ] + }, + { + "cell_type": "markdown", + "id": "0405558f-31bb-431a-9a10-65a9ed68b81f", + "metadata": {}, + "source": [ + "Setting Amortisation Rule filter to True eq True. This means that the amortisation method set in the rule defined will take precedence for all transactions in the relevant portfolio" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29228d0a-6dc0-4ebb-8838-dee07397095f", + "metadata": {}, + "outputs": [], + "source": [ + "#Updating Amortisation Rule \n", + "\n", + "set_amt_rule = amortisation_api.set_amortisation_rules(\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", + " rules_interval=models.RulesInterval(\n", + " effective_range={\n", + " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", + " },\n", + " rules=[\n", + " {\n", + " \"name\" : \"Amortisation_Rule_1\",\n", + " \"description\" : \"Test Rule setting filter to True equals True\",\n", + " \"filter\" : \"True eq True\",\n", + " \"amortisationMethod\" : \"StraightLine\",\n", + " },\n", + " ]\n", + " )\n", + " )\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa6201bc-1eca-428a-8c30-96905d58ff64", + "metadata": {}, + "outputs": [], + "source": [ + "#Getting Valuation\n", + "\n", + "aggregation = aggregation_api.get_valuation(\n", + " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", + " )\n", + ")\n", + "\n", + "pd.DataFrame(aggregation.data)" + ] + }, + { + "cell_type": "markdown", + "id": "7ff51ca5-9075-472b-a4aa-59532eb0fc90", + "metadata": {}, + "source": [ + "We can see that the Amortised Cost is calculate based on StraightLine method as updated in the rule." + ] + }, + { + "cell_type": "markdown", + "id": "0e09aa4e-5060-42b8-b90e-61ab00808730", + "metadata": {}, + "source": [ + "Setting Amortisation Rule filter based on instrument property. This means that the amortisation method set in the rule defined will take precedence over the method set on portfolio creation, for the instrument with the relevant property that is used in the filter" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "933a83b6-cee3-435f-bc77-83c19f9a2ca2", + "metadata": {}, + "outputs": [], + "source": [ + "#Updating Amortisation Rule\n", + "\n", + "set_amt_rule = amortisation_api.set_amortisation_rules(\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", + " rules_interval=models.RulesInterval(\n", + " effective_range={\n", + " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", + " },\n", + " rules=[\n", + " {\n", + " \"name\" : \"Amortisation_Rule_1\",\n", + " \"description\" : \"Test Rule Description\",\n", + " \"filter\" : \"properties[Instrument/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"amortisationMethod\" : \"StraightLine\",\n", + " },\n", + " ]\n", + " )\n", + " )\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc4a8e74-fffb-465e-bf92-5eedf43328fc", + "metadata": {}, + "outputs": [], + "source": [ + "#Getting Valuation\n", + "\n", + "aggregation = aggregation_api.get_valuation(\n", + " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", + " )\n", + ")\n", + "\n", + "pd.DataFrame(aggregation.data)" + ] + }, + { + "cell_type": "markdown", + "id": "c5f251f3-72dd-49c5-99d1-c7e77054e45c", + "metadata": {}, + "source": [ + "We can see that the Amortised Cost is calculate based on StraightLine method as updated in the rule for TestBond_A as it has the relevant property whereas TestBond_B has amortised cost based on NoAmortisation method" + ] + }, + { + "cell_type": "markdown", + "id": "4da21ed6-881f-4554-9c60-354ec2b7544c", + "metadata": {}, + "source": [ + "Now, setting Amortisation Rule filter based on Portfolio property. This means that the amortisation method set in the rule defined will take precedence over the method set on portfolio creation, for the instruments belonging to that portfolio." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc0cc542-092a-4aa7-b9ed-a70c871ae97f", + "metadata": {}, + "outputs": [], + "source": [ + "#Set Amortisation Rules\n", + "\n", + "set_amt_rule = amortisation_api.set_amortisation_rules(\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", + " rules_interval=models.RulesInterval(\n", + " effective_range={\n", + " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", + " },\n", + " rules=[\n", + " {\n", + " \"name\" : \"Amortisation_Rule_2\",\n", + " \"description\" : \"Test Rule Description\",\n", + " \"filter\" : \"properties[Portfolio/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"amortisationMethod\" : \"EffectiveYield\",\n", + " },\n", + " \n", + " ]\n", + " )\n", + " )\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ccd9dac-435b-46f7-9498-9c2759627b65", + "metadata": {}, + "outputs": [], + "source": [ + "#Getting Valuation\n", + "\n", + "aggregation = aggregation_api.get_valuation(\n", + " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", + " )\n", + ")\n", + "\n", + "pd.DataFrame(aggregation.data)" + ] + }, + { + "cell_type": "markdown", + "id": "d079ced5-9f85-41b4-9293-23c26197f08a", + "metadata": {}, + "source": [ + "We can see that the Amortised Cost is calculated based on EffectiveYield method as updated in the rule." + ] + }, + { + "cell_type": "markdown", + "id": "f163318a-9439-4f59-9836-7989104bfc0f", + "metadata": {}, + "source": [ + "Now, we will show that when multiple rules exist, the first one that matches is implemented." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4ca98a9-1d46-47b1-a0d4-7bfcccfb5ad1", + "metadata": {}, + "outputs": [], + "source": [ + "#Set Amortisation Rules\n", + "\n", + "set_amt_rule = amortisation_api.set_amortisation_rules(\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", + " rules_interval=models.RulesInterval(\n", + " effective_range={\n", + " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", + " },\n", + " rules=[\n", + " {\n", + " \"name\" : \"Amortisation_Rule_1\",\n", + " \"description\" : \"Test Rule Description\",\n", + " \"filter\" : \"properties[Instrument/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"amortisationMethod\" : \"StraightLine\",\n", + " },\n", + " {\n", + " \"name\" : \"Amortisation_Rule_2\",\n", + " \"description\" : \"Test Rule Description\",\n", + " \"filter\" : \"properties[Portfolio/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"amortisationMethod\" : \"EffectiveYield\",\n", + " },\n", + " \n", + " ]\n", + " )\n", + " )\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33dcb965-54d8-488b-bdc6-5ecb8c8d84bc", + "metadata": {}, + "outputs": [], + "source": [ + "#Getting Valuation\n", + "\n", + "aggregation = aggregation_api.get_valuation(\n", + " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", + " )\n", + ")\n", + "\n", + "pd.DataFrame(aggregation.data)" + ] + }, + { + "cell_type": "markdown", + "id": "b7d031fa-9a9f-451c-b7f8-64b689bf4920", + "metadata": {}, + "source": [ + "We can see that the Amortised Cost is calculated based on StraightLine method for TestBond_A as it matches the first rule where as EffectiveYield method is used for TestBond_B as it matches the second rule in the list." + ] + }, + { + "cell_type": "markdown", + "id": "699637a4-e94d-492d-ae8f-5c15c370cdd7", + "metadata": {}, + "source": [ + "# 10. Data Cleaning" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b7ae1d9-f0ff-470a-81d7-c82f414bcf3a", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "#Delete Instruments\n", + "def DeleteInstruments(luid): \n", + " try:\n", + " delete_instrument = instruments_api.delete_instrument(\n", + " identifier_type=\"LusidInstrumentId\", identifier= luid\n", + " )\n", + " \n", + " print(delete_instrument)\n", + " \n", + " except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3fbcad5e-95d8-4d7c-a869-2398b4138e37", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Deleting Instruments\n", + "\n", + "DeleteInstruments(luid)\n", + "DeleteInstruments(luid_1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46f0ea57-113a-4a0a-bb9d-dd15bbbd77ec", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Rule\n", + "\n", + "delete_amt= amortisation_api.delete_amortisation_ruleset(\n", + " scope=scope,\n", + " code=portfolio_code\n", + ")\n", + "\n", + "delete_amt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "baee566b-9862-45bc-8ad7-a853b0b10683", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Portfolio\n", + "try:\n", + " delete_portfolio = portfolio_api.delete_portfolio(scope, portfolio_code)\n", + " \n", + " print(delete_portfolio)\n", + "\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b56cd719-5555-4ba9-b3f8-e5efd2117f71", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Recipe\n", + "try:\n", + " delete_recipe = configuration_recipe_api.delete_configuration_recipe(\n", + " scope=scope,\n", + " code=portfolio_code,\n", + " )\n", + "\n", + " print(delete_recipe)\n", + "\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3026aba6-3ad1-4695-addf-c0e591978d96", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Portfolio Properties\n", + "try:\n", + " delete_portfolio_prop = property_api.delete_property_definition(\n", + " domain= 'Portfolio',\n", + " scope= scope,\n", + " code= portfolio_code,\n", + " )\n", + " print(delete_portfolio_prop)\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4219244-1fd4-462d-9d74-f5e1a37a4f74", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Instrument Properties\n", + "try:\n", + " delete_instrument_prop = property_api.delete_property_definition(\n", + " domain= 'Instrument',\n", + " scope= scope,\n", + " code= portfolio_code,\n", + " )\n", + " print(delete_instrument_prop)\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/use-cases/amortisation-rulesets/Transaction_Data.csv b/examples/use-cases/amortisation-rulesets/Transaction_Data.csv new file mode 100644 index 00000000..d08fd368 --- /dev/null +++ b/examples/use-cases/amortisation-rulesets/Transaction_Data.csv @@ -0,0 +1,3 @@ +txn_id,type,trade_date,settlement_date,quantity,price,currency,client_id,total_consideration +txn001,Buy,2024-01-15T00:00:00Z,2024-01-15T00:00:00Z,1,1444,GBP,Test_Bond_A,0 +txn002,Buy,2024-01-15T00:00:00Z,2024-01-15T00:00:00Z,1,1444,GBP,Test_Bond_B,0 From de83ef0050f2dd422bf6f279f1e63560e52f7e7d Mon Sep 17 00:00:00 2001 From: fmak404 Date: Tue, 23 Jul 2024 10:52:31 +0100 Subject: [PATCH 08/14] Delete examples/use-cases/amortisation-rulesets/AmortisationRules --- examples/use-cases/amortisation-rulesets/AmortisationRules | 1 - 1 file changed, 1 deletion(-) delete mode 100644 examples/use-cases/amortisation-rulesets/AmortisationRules diff --git a/examples/use-cases/amortisation-rulesets/AmortisationRules b/examples/use-cases/amortisation-rulesets/AmortisationRules deleted file mode 100644 index 8b137891..00000000 --- a/examples/use-cases/amortisation-rulesets/AmortisationRules +++ /dev/null @@ -1 +0,0 @@ - From a85fe7f400cf3f4c3c18b456153fed19a340bc30 Mon Sep 17 00:00:00 2001 From: fmak404 Date: Tue, 23 Jul 2024 11:24:49 +0100 Subject: [PATCH 09/14] Update IRR_Sample_Notebook.ipynb --- examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb b/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb index 1739b46a..3b156c4f 100644 --- a/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb +++ b/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb @@ -370,8 +370,7 @@ "source": [ "# 6. Valuation Recipe Creation\n", "\n", - "Following the initial setup, we can see to configuring how LUSID will conduct valuation on the swap. This introduces the concept of recipes, which are a set of steps we specify to the valuation engine relating to market data and model specification.\r", - "\"." + "Following the initial setup, we can see to configuring how LUSID will conduct valuation on the swap. This introduces the concept of recipes, which are a set of steps we specify to the valuation engine relating to market data and model specification." ] }, { From 4f5c301b78a61e9bdbd914b7c39637a3cbc27d41 Mon Sep 17 00:00:00 2001 From: fmak404 Date: Wed, 24 Jul 2024 10:29:42 +0100 Subject: [PATCH 10/14] Delete examples/use-cases/amortisation-rulesets directory --- .../AmortisationRules.ipynb | 1164 ----------------- .../Transaction_Data.csv | 3 - 2 files changed, 1167 deletions(-) delete mode 100644 examples/use-cases/amortisation-rulesets/AmortisationRules.ipynb delete mode 100644 examples/use-cases/amortisation-rulesets/Transaction_Data.csv diff --git a/examples/use-cases/amortisation-rulesets/AmortisationRules.ipynb b/examples/use-cases/amortisation-rulesets/AmortisationRules.ipynb deleted file mode 100644 index bec6a5c2..00000000 --- a/examples/use-cases/amortisation-rulesets/AmortisationRules.ipynb +++ /dev/null @@ -1,1164 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "61f113e0-ba28-400b-9ed8-216883e8df87", - "metadata": {}, - "outputs": [], - "source": [ - "from lusidtools.jupyter_tools import toggle_code\n", - "\n", - "\"\"\"Amortisation RuleSet\n", - "\n", - "Attributes\n", - "----------\n", - "Instruments\n", - "Transactions\n", - "Amortisation RuleSets\n", - "AmortisationRules\n", - "Recipe\n", - "Valuation\n", - "\"\"\"\n", - "\n", - "toggle_code(\"Toggle Docstring\")" - ] - }, - { - "cell_type": "markdown", - "id": "0d686878-b96e-4112-9528-974ce3ff08b3", - "metadata": {}, - "source": [ - "# Amortisation Rule Sets\n", - "\n", - "This example demonstrates how to create, update, and delete Amortisation Rule Sets and Rules, and how changes to the amortisation rules affect the amortisation valuation.\n", - "\n", - "We will create bond instruments, add properties to a specific instrument, create an amortisation ruleset, set up a portfolio, add properties to the portfolio, and configure the amortisation rules.\n", - "\n", - "Further we will upsert similar transactions to the respective bond instruments to demonstrate the effects of the amortisation rules through comparison. \n", - "\n", - "We will then create a simple recipe for valuation, including the valuation function.\n", - "\n", - "Once Done, we will then demostrate how updating the amortisation rules impacts the valuation." - ] - }, - { - "cell_type": "markdown", - "id": "3581f291-8ca2-4ef9-9cd6-03f44385abd2", - "metadata": {}, - "source": [ - "## Table of Contents:\n", - "- 1. [Imports](#1.-Imports)\n", - "- 2. [LUSID APIs](#2.-LUSID-APIs)\n", - "- 3. [Instrument Creation](#3.-Instrument-Creation)\n", - "- 4. [Amortisation RuleSet Creation](#4.-Amortisation-RuleSet-Creation)\n", - "- 5. [Portfolio Creation](#5.-Portfolio-Creation)\n", - "- 6. [Setting Amortisation Rule](#6.-Setting-Amortisation-Rule)\n", - "- 7. [Upsert Transactions](#7.-Upsert-Transactions)\n", - "- 8. [Recipe Creation](#8.-Recipe-Creation)\n", - "- 9. [Valuation](#9.-Valuation)\n", - "- 10. [Data Cleaning](#9.-Data-Cleaning)" - ] - }, - { - "cell_type": "markdown", - "id": "1ace0ed9-9c99-4db4-937e-2cfc6a80aabc", - "metadata": {}, - "source": [ - "# 1. Imports" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "60ee9804-9071-46f9-907d-3d9c19855b4b", - "metadata": {}, - "outputs": [], - "source": [ - "# Import necessary libraries\n", - "import lusid\n", - "import finbourne_access\n", - "import lusid.models as models\n", - "import openpyxl\n", - "import subprocess\n", - "from lusidtools.cocoon.cocoon import load_from_data_frame\n", - "from lusidtools.cocoon.cocoon_printer import (\n", - " format_instruments_response,\n", - " format_portfolios_response,\n", - " format_transactions_response,\n", - " format_quotes_response,\n", - ")\n", - "from lusidtools.pandas_utils.lusid_pandas import lusid_response_to_data_frame\n", - "from lusidjam import RefreshingToken\n", - "from fbnsdkutilities import ApiClientFactory\n", - "from finbourne_access.utilities import ApiClientFactory as AccessApiClientFactory\n", - "from finbourne_access import models as AccessModels\n", - "import os\n", - "import json\n", - "from IPython.display import Image\n", - "import pandas as pd\n", - "import pytz\n", - "from datetime import datetime\n", - "from lusidtools.lpt.lpt import to_date\n", - "pd.set_option(\"display.max_columns\", None)\n", - "\n", - "# Authenticate our user and create our API client\n", - "secrets_path = os.getenv(\"FBN_SECRETS_PATH\")\n", - "\n", - "lusid_api_factory = lusid.utilities.ApiClientFactory(\n", - " token=RefreshingToken(),\n", - " api_secrets_filename=secrets_path,\n", - " app_name=\"LusidJupyterNotebook\",\n", - ")\n", - "\n", - "api_client = lusid_api_factory.api_client\n", - "\n", - "lusid_api_url = api_client.configuration.host\n", - "access_api_url = lusid_api_url[: lusid_api_url.rfind(\"/\") + 1] + \"access\"\n", - "identity_api_url = lusid_api_url[: lusid_api_url.rfind(\"/\") + 1] + \"identity\"\n", - "\n", - "access_api_factory = finbourne_access.utilities.ApiClientFactory(\n", - " token=api_client.configuration.access_token,\n", - " access_url=access_api_url,\n", - " app_name=\"LusidJupyterNotebook\",\n", - ")\n", - "api_factory = ApiClientFactory(lusid, token=RefreshingToken())" - ] - }, - { - "cell_type": "markdown", - "id": "2b1c49bb-a0b7-413e-8153-2c495e00aa4a", - "metadata": {}, - "source": [ - "# 2. LUSID APIs\n", - "Firstly, we initialize the LUSID APIs required for the notebook" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "25c79a41-31bc-403a-a69b-d33672a8726f", - "metadata": {}, - "outputs": [], - "source": [ - "# Initiate the LUSID APIs required for the notebook\n", - "\n", - "instruments_api = api_factory.build(lusid.api.InstrumentsApi)\n", - "amortisation_api = api_factory.build(lusid.api.AmortisationRuleSetsApi)\n", - "transaction_portfolio_api = api_factory.build(lusid.api.TransactionPortfoliosApi)\n", - "portfolio_api = api_factory.build(lusid.api.PortfoliosApi)\n", - "configuration_recipe_api = api_factory.build(lusid.api.ConfigurationRecipeApi)\n", - "aggregation_api = api_factory.build(lusid.AggregationApi)\n", - "property_api = api_factory.build(lusid.PropertyDefinitionsApi)" - ] - }, - { - "cell_type": "markdown", - "id": "1d68849e-cee3-483d-8c9b-c68a44df2888", - "metadata": {}, - "source": [ - "# 3. Instrument Creation\n", - "We will create the relevant bond instruments" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "74300dd0-961b-434a-be5b-6e8f88f04b0a", - "metadata": {}, - "outputs": [], - "source": [ - "#Setting Scope and Code to be used throughout the notebook\n", - "\n", - "scope = \"valuation-sample\"\n", - "portfolio_code = \"EQUITY_UK_1\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "02dcb40b-9f46-48e7-b97e-7d876d1ce836", - "metadata": {}, - "outputs": [], - "source": [ - "#Defining function for bond instrument creation\n", - "\n", - "def create_bond(currency,payment_frequency,roll_convention,day_count_convention,payment_calendars,reset_calendars,settle_days,reset_days,start_date,maturity_date,dom_ccy,\n", - " principal,coupon_rate,bond_identifier,bond_name):\n", - "\n", - " flow_conventions = lusid.FlowConventions(\n", - " currency=currency,\n", - " payment_frequency=payment_frequency,\n", - " roll_convention=roll_convention,\n", - " day_count_convention=day_count_convention,\n", - " payment_calendars=payment_calendars,\n", - " reset_calendars=reset_calendars,\n", - " settle_days=settle_days,\n", - " reset_days=reset_days,\n", - " )\n", - "\n", - " bond = lusid.Bond(\n", - " start_date=start_date,\n", - " maturity_date=maturity_date,\n", - " dom_ccy=dom_ccy,\n", - " principal=principal,\n", - " coupon_rate=coupon_rate,\n", - " flow_conventions=flow_conventions,\n", - " identifiers={},\n", - " instrument_type=\"Bond\",\n", - " calculation_type=\"Standard\",\n", - " )\n", - "\n", - " # define the instrument to be upserted\n", - " bond_definition = lusid.InstrumentDefinition(\n", - " name=bond_name,\n", - " identifiers={\"ClientInternal\": lusid.InstrumentIdValue(bond_identifier)},\n", - " definition=bond,\n", - " )\n", - "\n", - " # upsert the instrument\n", - " upsert_request = {bond_identifier: bond_definition}\n", - " upsert_response = instruments_api.upsert_instruments(request_body=upsert_request)\n", - " bond_luid = upsert_response.values[bond_identifier].lusid_instrument_id\n", - " return(bond_luid)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd48d82d-ce0f-490f-b0ac-143e9712f8b5", - "metadata": {}, - "outputs": [], - "source": [ - "#Creating first Bond Instrument\n", - "\n", - "currency = \"GBP\"\n", - "payment_frequency = \"1M\"\n", - "roll_convention = \"none\"\n", - "day_count_convention = \"Actual365\"\n", - "payment_calendars = []\n", - "reset_calendars = []\n", - "settle_days = 0\n", - "reset_days = 0\n", - "start_date = datetime(2024, 1, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", - "maturity_date = datetime(2024, 8, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", - "dom_ccy = \"GBP\"\n", - "principal = 10487\n", - "coupon_rate = 0.07\n", - "bond_identifier = \"Test_Bond_A\"\n", - "bond_name = \"TestBond_A\"\n", - "\n", - "luid = create_bond(currency, payment_frequency, roll_convention, day_count_convention, payment_calendars, reset_calendars, settle_days, reset_days, start_date, maturity_date, dom_ccy, \n", - " principal, coupon_rate, bond_identifier, bond_name)\n", - "\n", - "luid" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c0267add-3535-4eb6-a7c2-a57976952b5d", - "metadata": {}, - "outputs": [], - "source": [ - "#Creating second Bond Instrument\n", - "\n", - "currency = \"GBP\"\n", - "payment_frequency = \"1M\"\n", - "roll_convention = \"none\"\n", - "day_count_convention = \"Actual365\"\n", - "payment_calendars = []\n", - "reset_calendars = []\n", - "settle_days = 0\n", - "reset_days = 0\n", - "start_date = datetime(2024, 1, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", - "maturity_date = datetime(2024, 8, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", - "dom_ccy = \"GBP\"\n", - "principal = 10487\n", - "coupon_rate = 0.07\n", - "bond_identifier = \"Test_Bond_B\"\n", - "bond_name = \"TestBond_B\"\n", - "\n", - "luid_1=create_bond(currency, payment_frequency, roll_convention, day_count_convention, payment_calendars, reset_calendars, settle_days, reset_days, start_date, maturity_date, dom_ccy,\n", - " principal, coupon_rate, bond_identifier, bond_name)\n", - "\n", - "luid_1" - ] - }, - { - "cell_type": "markdown", - "id": "11bab45e-eb3e-469e-af35-747e83f53201", - "metadata": {}, - "source": [ - "# 3.1 Instrument Property\n", - "We will create and upsert instrument property for the first instrument so that it can be used when defining amortisation rules\n", - "\n", - "We will do so only for one instrument so that impact of amortisation rules on instruments can be compared based on instrument properties" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bdda65d2-d9f2-4e78-8b39-683e394a94a2", - "metadata": {}, - "outputs": [], - "source": [ - "#Creating Instrument Property\n", - "try:\n", - " instrument_prop = property_api.create_property_definition(\n", - " create_property_definition_request=lusid.CreatePropertyDefinitionRequest(\n", - " domain = \"Instrument\",\n", - " scope = scope,\n", - " code = portfolio_code,\n", - " data_type_id= lusid.ResourceId(\n", - " scope = \"system\",\n", - " code = \"string\",\n", - " ),\n", - " life_time= \"Perpetual\",\n", - " constraint_style = \"Property\",\n", - " display_name = \"Bond_Test_Prop\",\n", - " property_description = \"Testing Bond Property\",\n", - " )\n", - " \n", - " )\n", - " print(\"Instrument Property Created\")\n", - "except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "286fb7e7-c6d2-4963-86af-dd89df53b68b", - "metadata": {}, - "outputs": [], - "source": [ - "#Upserting property to first Bond Instrument\n", - "try:\n", - " upsert_instrument_prop = instruments_api.upsert_instruments_properties(\n", - " upsert_instrument_property_request=[\n", - " lusid.UpsertInstrumentPropertyRequest(\n", - " identifier_type=\"LusidInstrumentId\",\n", - " identifier=luid,\n", - " properties=[\n", - " lusid.ModelProperty(\n", - " key=\"Instrument/valuation-sample/EQUITY_UK_1\",\n", - " value=lusid.PropertyValue(\n", - " label_value='fahad'\n", - " )\n", - " )\n", - " ]\n", - " )\n", - " ]\n", - " )\n", - " print(\"Instrument Property Upserted\")\n", - "except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])" - ] - }, - { - "cell_type": "markdown", - "id": "c3ed4107-5074-4731-9e05-105ece1e1070", - "metadata": {}, - "source": [ - "# 4. Amortisation RuleSet Creation\n", - "We will create the relevant Amortisation Rule Set" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "78b84358-17fc-43f8-9479-729ed2193348", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "#Creating Amortisation Ruleset\n", - "try:\n", - " create_amt = amortisation_api.create_amortisation_rule_set(\n", - " scope= scope,\n", - " create_amortisation_rule_set_request=models.CreateAmortisationRuleSetRequest(\n", - " code=portfolio_code,\n", - " display_name='Testing_Amortisation_Rule',\n", - " description='This is a test run'\n", - " )\n", - " )\n", - " print(\"Amortisation RuleSet Created\")\n", - "except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])" - ] - }, - { - "cell_type": "markdown", - "id": "84a6b0bf-8a3d-420f-8133-64e608b4c2f0", - "metadata": {}, - "source": [ - "# 5. Portfolio Creation\n", - "We proceed by creating a basic transaction portfolio" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "12bdbe41-a719-4abc-b980-af6ac9b27650", - "metadata": {}, - "outputs": [], - "source": [ - "# Creating a Transaction Portfolio\n", - "\n", - "try:\n", - " create_portfolio_response = transaction_portfolio_api.create_portfolio(\n", - " scope=scope,\n", - " create_transaction_portfolio_request=models.CreateTransactionPortfolioRequest(\n", - " display_name=\"RuleSet_Test\",\n", - " code=portfolio_code,\n", - " base_currency=\"GBP\",\n", - " created=datetime(2024, 1, 1, 0, 0, tzinfo=pytz.utc).isoformat(),\n", - " sub_holding_keys=[],\n", - " amortisation_method='NoAmortisation',\n", - " amortisation_rule_set_id={\n", - " \"scope\": scope,\n", - " \"code\": portfolio_code\n", - " }\n", - "\n", - " ),\n", - " )\n", - "\n", - " print(\"Portfolio Created\")\n", - "\n", - "except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])\n" - ] - }, - { - "cell_type": "markdown", - "id": "5ea98c2c-7be2-4912-87bc-7735482d1f64", - "metadata": {}, - "source": [ - "# 5.1 Portfolio Property\n", - "We will create and upsert portfolio property for the relevant portfolio" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "18e40c13-2639-4ace-abfa-38f32c75d683", - "metadata": {}, - "outputs": [], - "source": [ - "#Creating Portfolio Property\n", - "try:\n", - " property_creation = property_api.create_property_definition(\n", - " create_property_definition_request=lusid.CreatePropertyDefinitionRequest(\n", - " domain = \"Portfolio\",\n", - " scope = scope,\n", - " code = portfolio_code,\n", - " data_type_id= lusid.ResourceId(\n", - " scope = \"system\",\n", - " code = \"string\",\n", - " ),\n", - " life_time= \"Perpetual\",\n", - " constraint_style = \"Property\",\n", - " display_name = \"Portfolio_property\",\n", - " property_description = \"Testing amortisation rule Property\",\n", - " )\n", - " \n", - " )\n", - " print('Portfolio Property Created')\n", - "except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5ebf29d2-a432-4bcd-9dce-7c23dadbcaf7", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "#Upserting property to Portfolio\n", - "try:\n", - " upsert_port= portfolio_api.upsert_portfolio_properties(\n", - " scope= scope,\n", - " code= portfolio_code,\n", - " request_body={\n", - " \"Portfolio/valuation-sample/EQUITY_UK_1\": {\n", - " \n", - " \"key\": \"Portfolio/valuation-sample/EQUITY_UK_1\",\n", - " \"value\": {\n", - " \"labelValue\": \"fahad\"\n", - " },\n", - " },\n", - " }\n", - " \n", - " ) \n", - " print(\"Portfolio Property Upserted\")\n", - "except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])" - ] - }, - { - "cell_type": "markdown", - "id": "974d2d19-18e1-4b17-a592-4d3db16ae125", - "metadata": {}, - "source": [ - "# 6. Setting Amortisation Rule\n", - "We will upsert rules to the Amortisation Rule Set" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a1685dc4-f641-4da3-937b-9f858ae8d5ec", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "#Set Amortisation Rules\n", - "\n", - "set_amt_rule = amortisation_api.set_amortisation_rules(\n", - " scope = scope,\n", - " code = portfolio_code,\n", - " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", - " rules_interval=models.RulesInterval(\n", - " effective_range={\n", - " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", - " },\n", - " rules=[]\n", - " )\n", - " )\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "6853d35f-8b18-43a6-bc3d-56fde9f5794c", - "metadata": {}, - "source": [ - "# 6.1 List Amortisation RuleSets\n", - "Using this endpoint we can see the list of Amortisation RuleSets and associates Rules" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5b2b1797-f5c0-46f9-96d8-baa83319d641", - "metadata": {}, - "outputs": [], - "source": [ - "#List RuleSets\n", - "\n", - "list_amt= amortisation_api.list_amortisation_rule_sets()\n", - "list_amt" - ] - }, - { - "cell_type": "markdown", - "id": "95e7d51f-227a-4834-9656-77faa317e441", - "metadata": {}, - "source": [ - "# 7. Upsert Transactions\n", - "We will upsert similar transactions for the created instruments to observe the impacts of the amortisation rules" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3c07f66c-c27a-418a-9d28-3d79faeec7cd", - "metadata": {}, - "outputs": [], - "source": [ - "transactions = pd.read_csv(\"Transaction_Data.csv\")\n", - "transactions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c83f29b0-3530-4e28-bb6f-f32aa5f46ada", - "metadata": {}, - "outputs": [], - "source": [ - "#Upserting Transactions\n", - "transaction_request = [\n", - " lusid.TransactionRequest(\n", - " transaction_id=row[\"txn_id\"],\n", - " type=row[\"type\"],\n", - " instrument_identifiers={\n", - " \"Instrument/default/ClientInternal\": row[\"client_id\"]\n", - " },\n", - " transaction_date=to_date(row[\"trade_date\"]).isoformat(),\n", - " settlement_date=to_date(row[\"settlement_date\"]).isoformat(),\n", - " units=row[\"quantity\"],\n", - " transaction_price=lusid.TransactionPrice(price=row[\"price\"], type=\"Price\"),\n", - " total_consideration=lusid.CurrencyAndAmount(\n", - " amount=row[\"total_consideration\"], currency=row[\"currency\"]\n", - " ),\n", - " )\n", - " for index, row in transactions.iterrows()\n", - "]\n", - "\n", - "response = transaction_portfolio_api.upsert_transactions(\n", - " scope=scope, code=portfolio_code, transaction_request=transaction_request\n", - ")\n", - "\n", - "print(f\"Transactions succesfully updated at time: {response.version.as_at_date}\")" - ] - }, - { - "cell_type": "markdown", - "id": "2d169207-b6ce-48eb-a506-ee8009d5c528", - "metadata": {}, - "source": [ - "# 8. Recipe Creation\n", - "\n", - "Following the initial setup, we can see to configuring how LUSID will conduct valuation. This introduces the concept of recipes, which are a set of steps we specify to the valuation engine relating to market data and model specification.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "abacf1ce-1bb8-4c54-806b-4758ef2b6dcf", - "metadata": {}, - "outputs": [], - "source": [ - "# Creating Recipe to perform a valuation\n", - "try:\n", - " configuration_recipe = models.ConfigurationRecipe(\n", - " scope=scope,\n", - " code=portfolio_code,\n", - " market=models.MarketContext(\n", - " market_rules=[\n", - " # define how to resolve the quotes\n", - " models.MarketDataKeyRule(\n", - " key=\"Quote.ClientInternal.*\",\n", - " supplier=\"Lusid\",\n", - " data_scope=scope,\n", - " quote_type=\"Price\",\n", - " field= \"bid\",\n", - " ),\n", - " ],\n", - " options=models.MarketOptions(\n", - " default_supplier=\"Lusid\",\n", - " default_instrument_code_type=\"ClientInternal\",\n", - " default_scope=scope,\n", - " ),\n", - " ),\n", - " pricing=models.PricingContext(\n", - " options={\"AllowPartiallySuccessfulEvaluation\": True},\n", - " ),\n", - " )\n", - " \n", - " upsert_configuration_recipe_response = configuration_recipe_api.upsert_configuration_recipe(\n", - " upsert_recipe_request=models.UpsertRecipeRequest(\n", - " configuration_recipe=configuration_recipe\n", - " )\n", - " )\n", - " print(\"Recipe Created Successfully\")\n", - "except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])" - ] - }, - { - "cell_type": "markdown", - "id": "fab0149c-87d9-41aa-87d8-07c7bce5dd5d", - "metadata": {}, - "source": [ - "# 9. Valuation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a3e702eb-232d-45dc-ab28-77c798d85a3e", - "metadata": {}, - "outputs": [], - "source": [ - "#Defining function for Valuation\n", - "\n", - "def generate_valuation_request(valuation_effectiveAt):\n", - "\n", - " # Create the valuation request\n", - " valuation_request = models.ValuationRequest(\n", - " recipe_id=models.ResourceId(\n", - " scope=scope, code=portfolio_code\n", - " ),\n", - " metrics=[\n", - " models.AggregateSpec(\"Instrument/default/Name\", \"Value\"),\n", - " models.AggregateSpec(\"Valuation/PvInReportCcy\", \"Proportion\"),\n", - " models.AggregateSpec(\"Valuation/PvInReportCcy\", \"Sum\"),\n", - " models.AggregateSpec(\"Holding/default/Units\", \"Sum\"),\n", - " models.AggregateSpec(\"Holding/AmortisedCost\", \"Value\"),\n", - " ],\n", - " group_by=[\"Instrument/default/Name\"],\n", - " portfolio_entity_ids=[\n", - " models.PortfolioEntityId(scope=scope, code=portfolio_code)\n", - " ],\n", - " valuation_schedule=models.ValuationSchedule(\n", - " effective_at=valuation_effectiveAt.isoformat()\n", - " ),\n", - " )\n", - "\n", - " return valuation_request" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7c16f197-ef0c-4f80-9bef-b0cd2404989e", - "metadata": {}, - "outputs": [], - "source": [ - "#Getting Valuation\n", - "\n", - "aggregation = aggregation_api.get_valuation(\n", - " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", - " )\n", - ")\n", - "\n", - "pd.DataFrame(aggregation.data)" - ] - }, - { - "cell_type": "markdown", - "id": "f1e86e68-fb62-4d08-a1fc-a99fd59f3a86", - "metadata": {}, - "source": [ - "We can see that the Amortised Cost is calculated based on No Amortisation method as set when creating the portfolio. No Amortisation Rules were defined at this point " - ] - }, - { - "cell_type": "markdown", - "id": "52fe7dbb-1912-4e14-ab4e-026a3ae4d5c3", - "metadata": {}, - "source": [ - "# 9.1 Amortisation Rules Filters" - ] - }, - { - "cell_type": "markdown", - "id": "0405558f-31bb-431a-9a10-65a9ed68b81f", - "metadata": {}, - "source": [ - "Setting Amortisation Rule filter to True eq True. This means that the amortisation method set in the rule defined will take precedence for all transactions in the relevant portfolio" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "29228d0a-6dc0-4ebb-8838-dee07397095f", - "metadata": {}, - "outputs": [], - "source": [ - "#Updating Amortisation Rule \n", - "\n", - "set_amt_rule = amortisation_api.set_amortisation_rules(\n", - " scope = scope,\n", - " code = portfolio_code,\n", - " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", - " rules_interval=models.RulesInterval(\n", - " effective_range={\n", - " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", - " },\n", - " rules=[\n", - " {\n", - " \"name\" : \"Amortisation_Rule_1\",\n", - " \"description\" : \"Test Rule setting filter to True equals True\",\n", - " \"filter\" : \"True eq True\",\n", - " \"amortisationMethod\" : \"StraightLine\",\n", - " },\n", - " ]\n", - " )\n", - " )\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fa6201bc-1eca-428a-8c30-96905d58ff64", - "metadata": {}, - "outputs": [], - "source": [ - "#Getting Valuation\n", - "\n", - "aggregation = aggregation_api.get_valuation(\n", - " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", - " )\n", - ")\n", - "\n", - "pd.DataFrame(aggregation.data)" - ] - }, - { - "cell_type": "markdown", - "id": "7ff51ca5-9075-472b-a4aa-59532eb0fc90", - "metadata": {}, - "source": [ - "We can see that the Amortised Cost is calculate based on StraightLine method as updated in the rule." - ] - }, - { - "cell_type": "markdown", - "id": "0e09aa4e-5060-42b8-b90e-61ab00808730", - "metadata": {}, - "source": [ - "Setting Amortisation Rule filter based on instrument property. This means that the amortisation method set in the rule defined will take precedence over the method set on portfolio creation, for the instrument with the relevant property that is used in the filter" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "933a83b6-cee3-435f-bc77-83c19f9a2ca2", - "metadata": {}, - "outputs": [], - "source": [ - "#Updating Amortisation Rule\n", - "\n", - "set_amt_rule = amortisation_api.set_amortisation_rules(\n", - " scope = scope,\n", - " code = portfolio_code,\n", - " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", - " rules_interval=models.RulesInterval(\n", - " effective_range={\n", - " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", - " },\n", - " rules=[\n", - " {\n", - " \"name\" : \"Amortisation_Rule_1\",\n", - " \"description\" : \"Test Rule Description\",\n", - " \"filter\" : \"properties[Instrument/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", - " \"amortisationMethod\" : \"StraightLine\",\n", - " },\n", - " ]\n", - " )\n", - " )\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cc4a8e74-fffb-465e-bf92-5eedf43328fc", - "metadata": {}, - "outputs": [], - "source": [ - "#Getting Valuation\n", - "\n", - "aggregation = aggregation_api.get_valuation(\n", - " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", - " )\n", - ")\n", - "\n", - "pd.DataFrame(aggregation.data)" - ] - }, - { - "cell_type": "markdown", - "id": "c5f251f3-72dd-49c5-99d1-c7e77054e45c", - "metadata": {}, - "source": [ - "We can see that the Amortised Cost is calculate based on StraightLine method as updated in the rule for TestBond_A as it has the relevant property whereas TestBond_B has amortised cost based on NoAmortisation method" - ] - }, - { - "cell_type": "markdown", - "id": "4da21ed6-881f-4554-9c60-354ec2b7544c", - "metadata": {}, - "source": [ - "Now, setting Amortisation Rule filter based on Portfolio property. This means that the amortisation method set in the rule defined will take precedence over the method set on portfolio creation, for the instruments belonging to that portfolio." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cc0cc542-092a-4aa7-b9ed-a70c871ae97f", - "metadata": {}, - "outputs": [], - "source": [ - "#Set Amortisation Rules\n", - "\n", - "set_amt_rule = amortisation_api.set_amortisation_rules(\n", - " scope = scope,\n", - " code = portfolio_code,\n", - " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", - " rules_interval=models.RulesInterval(\n", - " effective_range={\n", - " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", - " },\n", - " rules=[\n", - " {\n", - " \"name\" : \"Amortisation_Rule_2\",\n", - " \"description\" : \"Test Rule Description\",\n", - " \"filter\" : \"properties[Portfolio/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", - " \"amortisationMethod\" : \"EffectiveYield\",\n", - " },\n", - " \n", - " ]\n", - " )\n", - " )\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8ccd9dac-435b-46f7-9498-9c2759627b65", - "metadata": {}, - "outputs": [], - "source": [ - "#Getting Valuation\n", - "\n", - "aggregation = aggregation_api.get_valuation(\n", - " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", - " )\n", - ")\n", - "\n", - "pd.DataFrame(aggregation.data)" - ] - }, - { - "cell_type": "markdown", - "id": "d079ced5-9f85-41b4-9293-23c26197f08a", - "metadata": {}, - "source": [ - "We can see that the Amortised Cost is calculated based on EffectiveYield method as updated in the rule." - ] - }, - { - "cell_type": "markdown", - "id": "f163318a-9439-4f59-9836-7989104bfc0f", - "metadata": {}, - "source": [ - "Now, we will show that when multiple rules exist, the first one that matches is implemented." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b4ca98a9-1d46-47b1-a0d4-7bfcccfb5ad1", - "metadata": {}, - "outputs": [], - "source": [ - "#Set Amortisation Rules\n", - "\n", - "set_amt_rule = amortisation_api.set_amortisation_rules(\n", - " scope = scope,\n", - " code = portfolio_code,\n", - " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", - " rules_interval=models.RulesInterval(\n", - " effective_range={\n", - " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", - " },\n", - " rules=[\n", - " {\n", - " \"name\" : \"Amortisation_Rule_1\",\n", - " \"description\" : \"Test Rule Description\",\n", - " \"filter\" : \"properties[Instrument/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", - " \"amortisationMethod\" : \"StraightLine\",\n", - " },\n", - " {\n", - " \"name\" : \"Amortisation_Rule_2\",\n", - " \"description\" : \"Test Rule Description\",\n", - " \"filter\" : \"properties[Portfolio/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", - " \"amortisationMethod\" : \"EffectiveYield\",\n", - " },\n", - " \n", - " ]\n", - " )\n", - " )\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "33dcb965-54d8-488b-bdc6-5ecb8c8d84bc", - "metadata": {}, - "outputs": [], - "source": [ - "#Getting Valuation\n", - "\n", - "aggregation = aggregation_api.get_valuation(\n", - " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", - " )\n", - ")\n", - "\n", - "pd.DataFrame(aggregation.data)" - ] - }, - { - "cell_type": "markdown", - "id": "b7d031fa-9a9f-451c-b7f8-64b689bf4920", - "metadata": {}, - "source": [ - "We can see that the Amortised Cost is calculated based on StraightLine method for TestBond_A as it matches the first rule where as EffectiveYield method is used for TestBond_B as it matches the second rule in the list." - ] - }, - { - "cell_type": "markdown", - "id": "699637a4-e94d-492d-ae8f-5c15c370cdd7", - "metadata": {}, - "source": [ - "# 10. Data Cleaning" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7b7ae1d9-f0ff-470a-81d7-c82f414bcf3a", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "#Delete Instruments\n", - "def DeleteInstruments(luid): \n", - " try:\n", - " delete_instrument = instruments_api.delete_instrument(\n", - " identifier_type=\"LusidInstrumentId\", identifier= luid\n", - " )\n", - " \n", - " print(delete_instrument)\n", - " \n", - " except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3fbcad5e-95d8-4d7c-a869-2398b4138e37", - "metadata": {}, - "outputs": [], - "source": [ - "'''\n", - "#Deleting Instruments\n", - "\n", - "DeleteInstruments(luid)\n", - "DeleteInstruments(luid_1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "46f0ea57-113a-4a0a-bb9d-dd15bbbd77ec", - "metadata": {}, - "outputs": [], - "source": [ - "'''\n", - "#Delete Rule\n", - "\n", - "delete_amt= amortisation_api.delete_amortisation_ruleset(\n", - " scope=scope,\n", - " code=portfolio_code\n", - ")\n", - "\n", - "delete_amt" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "baee566b-9862-45bc-8ad7-a853b0b10683", - "metadata": {}, - "outputs": [], - "source": [ - "'''\n", - "#Delete Portfolio\n", - "try:\n", - " delete_portfolio = portfolio_api.delete_portfolio(scope, portfolio_code)\n", - " \n", - " print(delete_portfolio)\n", - "\n", - "except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b56cd719-5555-4ba9-b3f8-e5efd2117f71", - "metadata": {}, - "outputs": [], - "source": [ - "'''\n", - "#Delete Recipe\n", - "try:\n", - " delete_recipe = configuration_recipe_api.delete_configuration_recipe(\n", - " scope=scope,\n", - " code=portfolio_code,\n", - " )\n", - "\n", - " print(delete_recipe)\n", - "\n", - "except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3026aba6-3ad1-4695-addf-c0e591978d96", - "metadata": {}, - "outputs": [], - "source": [ - "'''\n", - "#Delete Portfolio Properties\n", - "try:\n", - " delete_portfolio_prop = property_api.delete_property_definition(\n", - " domain= 'Portfolio',\n", - " scope= scope,\n", - " code= portfolio_code,\n", - " )\n", - " print(delete_portfolio_prop)\n", - "except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f4219244-1fd4-462d-9d74-f5e1a37a4f74", - "metadata": {}, - "outputs": [], - "source": [ - "'''\n", - "#Delete Instrument Properties\n", - "try:\n", - " delete_instrument_prop = property_api.delete_property_definition(\n", - " domain= 'Instrument',\n", - " scope= scope,\n", - " code= portfolio_code,\n", - " )\n", - " print(delete_instrument_prop)\n", - "except lusid.ApiException as e:\n", - " print(json.loads(e.body)[\"title\"])" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/use-cases/amortisation-rulesets/Transaction_Data.csv b/examples/use-cases/amortisation-rulesets/Transaction_Data.csv deleted file mode 100644 index d08fd368..00000000 --- a/examples/use-cases/amortisation-rulesets/Transaction_Data.csv +++ /dev/null @@ -1,3 +0,0 @@ -txn_id,type,trade_date,settlement_date,quantity,price,currency,client_id,total_consideration -txn001,Buy,2024-01-15T00:00:00Z,2024-01-15T00:00:00Z,1,1444,GBP,Test_Bond_A,0 -txn002,Buy,2024-01-15T00:00:00Z,2024-01-15T00:00:00Z,1,1444,GBP,Test_Bond_B,0 From eba8ae18a4e4b9a95a398efeb3dc7e15f2951882 Mon Sep 17 00:00:00 2001 From: fmak404 Date: Wed, 24 Jul 2024 10:31:59 +0100 Subject: [PATCH 11/14] Create AmortisationRules.ipynb --- examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb diff --git a/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb b/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb @@ -0,0 +1 @@ + From 256a0fcb48b2382d6d9f0fe609efd1045e94a92a Mon Sep 17 00:00:00 2001 From: fmak404 Date: Wed, 24 Jul 2024 10:35:12 +0100 Subject: [PATCH 12/14] Add files via upload --- .../AmortisationRules.ipynb | 1165 ++++++++++++++++- .../amortisation-ruleset/Transaction_Data.csv | 3 + 2 files changed, 1167 insertions(+), 1 deletion(-) create mode 100644 examples/use-cases/amortisation-ruleset/Transaction_Data.csv diff --git a/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb b/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb index 8b137891..bec6a5c2 100644 --- a/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb +++ b/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb @@ -1 +1,1164 @@ - +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "61f113e0-ba28-400b-9ed8-216883e8df87", + "metadata": {}, + "outputs": [], + "source": [ + "from lusidtools.jupyter_tools import toggle_code\n", + "\n", + "\"\"\"Amortisation RuleSet\n", + "\n", + "Attributes\n", + "----------\n", + "Instruments\n", + "Transactions\n", + "Amortisation RuleSets\n", + "AmortisationRules\n", + "Recipe\n", + "Valuation\n", + "\"\"\"\n", + "\n", + "toggle_code(\"Toggle Docstring\")" + ] + }, + { + "cell_type": "markdown", + "id": "0d686878-b96e-4112-9528-974ce3ff08b3", + "metadata": {}, + "source": [ + "# Amortisation Rule Sets\n", + "\n", + "This example demonstrates how to create, update, and delete Amortisation Rule Sets and Rules, and how changes to the amortisation rules affect the amortisation valuation.\n", + "\n", + "We will create bond instruments, add properties to a specific instrument, create an amortisation ruleset, set up a portfolio, add properties to the portfolio, and configure the amortisation rules.\n", + "\n", + "Further we will upsert similar transactions to the respective bond instruments to demonstrate the effects of the amortisation rules through comparison. \n", + "\n", + "We will then create a simple recipe for valuation, including the valuation function.\n", + "\n", + "Once Done, we will then demostrate how updating the amortisation rules impacts the valuation." + ] + }, + { + "cell_type": "markdown", + "id": "3581f291-8ca2-4ef9-9cd6-03f44385abd2", + "metadata": {}, + "source": [ + "## Table of Contents:\n", + "- 1. [Imports](#1.-Imports)\n", + "- 2. [LUSID APIs](#2.-LUSID-APIs)\n", + "- 3. [Instrument Creation](#3.-Instrument-Creation)\n", + "- 4. [Amortisation RuleSet Creation](#4.-Amortisation-RuleSet-Creation)\n", + "- 5. [Portfolio Creation](#5.-Portfolio-Creation)\n", + "- 6. [Setting Amortisation Rule](#6.-Setting-Amortisation-Rule)\n", + "- 7. [Upsert Transactions](#7.-Upsert-Transactions)\n", + "- 8. [Recipe Creation](#8.-Recipe-Creation)\n", + "- 9. [Valuation](#9.-Valuation)\n", + "- 10. [Data Cleaning](#9.-Data-Cleaning)" + ] + }, + { + "cell_type": "markdown", + "id": "1ace0ed9-9c99-4db4-937e-2cfc6a80aabc", + "metadata": {}, + "source": [ + "# 1. Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60ee9804-9071-46f9-907d-3d9c19855b4b", + "metadata": {}, + "outputs": [], + "source": [ + "# Import necessary libraries\n", + "import lusid\n", + "import finbourne_access\n", + "import lusid.models as models\n", + "import openpyxl\n", + "import subprocess\n", + "from lusidtools.cocoon.cocoon import load_from_data_frame\n", + "from lusidtools.cocoon.cocoon_printer import (\n", + " format_instruments_response,\n", + " format_portfolios_response,\n", + " format_transactions_response,\n", + " format_quotes_response,\n", + ")\n", + "from lusidtools.pandas_utils.lusid_pandas import lusid_response_to_data_frame\n", + "from lusidjam import RefreshingToken\n", + "from fbnsdkutilities import ApiClientFactory\n", + "from finbourne_access.utilities import ApiClientFactory as AccessApiClientFactory\n", + "from finbourne_access import models as AccessModels\n", + "import os\n", + "import json\n", + "from IPython.display import Image\n", + "import pandas as pd\n", + "import pytz\n", + "from datetime import datetime\n", + "from lusidtools.lpt.lpt import to_date\n", + "pd.set_option(\"display.max_columns\", None)\n", + "\n", + "# Authenticate our user and create our API client\n", + "secrets_path = os.getenv(\"FBN_SECRETS_PATH\")\n", + "\n", + "lusid_api_factory = lusid.utilities.ApiClientFactory(\n", + " token=RefreshingToken(),\n", + " api_secrets_filename=secrets_path,\n", + " app_name=\"LusidJupyterNotebook\",\n", + ")\n", + "\n", + "api_client = lusid_api_factory.api_client\n", + "\n", + "lusid_api_url = api_client.configuration.host\n", + "access_api_url = lusid_api_url[: lusid_api_url.rfind(\"/\") + 1] + \"access\"\n", + "identity_api_url = lusid_api_url[: lusid_api_url.rfind(\"/\") + 1] + \"identity\"\n", + "\n", + "access_api_factory = finbourne_access.utilities.ApiClientFactory(\n", + " token=api_client.configuration.access_token,\n", + " access_url=access_api_url,\n", + " app_name=\"LusidJupyterNotebook\",\n", + ")\n", + "api_factory = ApiClientFactory(lusid, token=RefreshingToken())" + ] + }, + { + "cell_type": "markdown", + "id": "2b1c49bb-a0b7-413e-8153-2c495e00aa4a", + "metadata": {}, + "source": [ + "# 2. LUSID APIs\n", + "Firstly, we initialize the LUSID APIs required for the notebook" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25c79a41-31bc-403a-a69b-d33672a8726f", + "metadata": {}, + "outputs": [], + "source": [ + "# Initiate the LUSID APIs required for the notebook\n", + "\n", + "instruments_api = api_factory.build(lusid.api.InstrumentsApi)\n", + "amortisation_api = api_factory.build(lusid.api.AmortisationRuleSetsApi)\n", + "transaction_portfolio_api = api_factory.build(lusid.api.TransactionPortfoliosApi)\n", + "portfolio_api = api_factory.build(lusid.api.PortfoliosApi)\n", + "configuration_recipe_api = api_factory.build(lusid.api.ConfigurationRecipeApi)\n", + "aggregation_api = api_factory.build(lusid.AggregationApi)\n", + "property_api = api_factory.build(lusid.PropertyDefinitionsApi)" + ] + }, + { + "cell_type": "markdown", + "id": "1d68849e-cee3-483d-8c9b-c68a44df2888", + "metadata": {}, + "source": [ + "# 3. Instrument Creation\n", + "We will create the relevant bond instruments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74300dd0-961b-434a-be5b-6e8f88f04b0a", + "metadata": {}, + "outputs": [], + "source": [ + "#Setting Scope and Code to be used throughout the notebook\n", + "\n", + "scope = \"valuation-sample\"\n", + "portfolio_code = \"EQUITY_UK_1\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02dcb40b-9f46-48e7-b97e-7d876d1ce836", + "metadata": {}, + "outputs": [], + "source": [ + "#Defining function for bond instrument creation\n", + "\n", + "def create_bond(currency,payment_frequency,roll_convention,day_count_convention,payment_calendars,reset_calendars,settle_days,reset_days,start_date,maturity_date,dom_ccy,\n", + " principal,coupon_rate,bond_identifier,bond_name):\n", + "\n", + " flow_conventions = lusid.FlowConventions(\n", + " currency=currency,\n", + " payment_frequency=payment_frequency,\n", + " roll_convention=roll_convention,\n", + " day_count_convention=day_count_convention,\n", + " payment_calendars=payment_calendars,\n", + " reset_calendars=reset_calendars,\n", + " settle_days=settle_days,\n", + " reset_days=reset_days,\n", + " )\n", + "\n", + " bond = lusid.Bond(\n", + " start_date=start_date,\n", + " maturity_date=maturity_date,\n", + " dom_ccy=dom_ccy,\n", + " principal=principal,\n", + " coupon_rate=coupon_rate,\n", + " flow_conventions=flow_conventions,\n", + " identifiers={},\n", + " instrument_type=\"Bond\",\n", + " calculation_type=\"Standard\",\n", + " )\n", + "\n", + " # define the instrument to be upserted\n", + " bond_definition = lusid.InstrumentDefinition(\n", + " name=bond_name,\n", + " identifiers={\"ClientInternal\": lusid.InstrumentIdValue(bond_identifier)},\n", + " definition=bond,\n", + " )\n", + "\n", + " # upsert the instrument\n", + " upsert_request = {bond_identifier: bond_definition}\n", + " upsert_response = instruments_api.upsert_instruments(request_body=upsert_request)\n", + " bond_luid = upsert_response.values[bond_identifier].lusid_instrument_id\n", + " return(bond_luid)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd48d82d-ce0f-490f-b0ac-143e9712f8b5", + "metadata": {}, + "outputs": [], + "source": [ + "#Creating first Bond Instrument\n", + "\n", + "currency = \"GBP\"\n", + "payment_frequency = \"1M\"\n", + "roll_convention = \"none\"\n", + "day_count_convention = \"Actual365\"\n", + "payment_calendars = []\n", + "reset_calendars = []\n", + "settle_days = 0\n", + "reset_days = 0\n", + "start_date = datetime(2024, 1, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", + "maturity_date = datetime(2024, 8, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", + "dom_ccy = \"GBP\"\n", + "principal = 10487\n", + "coupon_rate = 0.07\n", + "bond_identifier = \"Test_Bond_A\"\n", + "bond_name = \"TestBond_A\"\n", + "\n", + "luid = create_bond(currency, payment_frequency, roll_convention, day_count_convention, payment_calendars, reset_calendars, settle_days, reset_days, start_date, maturity_date, dom_ccy, \n", + " principal, coupon_rate, bond_identifier, bond_name)\n", + "\n", + "luid" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0267add-3535-4eb6-a7c2-a57976952b5d", + "metadata": {}, + "outputs": [], + "source": [ + "#Creating second Bond Instrument\n", + "\n", + "currency = \"GBP\"\n", + "payment_frequency = \"1M\"\n", + "roll_convention = \"none\"\n", + "day_count_convention = \"Actual365\"\n", + "payment_calendars = []\n", + "reset_calendars = []\n", + "settle_days = 0\n", + "reset_days = 0\n", + "start_date = datetime(2024, 1, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", + "maturity_date = datetime(2024, 8, 15, 0, 0, tzinfo=pytz.utc).isoformat()\n", + "dom_ccy = \"GBP\"\n", + "principal = 10487\n", + "coupon_rate = 0.07\n", + "bond_identifier = \"Test_Bond_B\"\n", + "bond_name = \"TestBond_B\"\n", + "\n", + "luid_1=create_bond(currency, payment_frequency, roll_convention, day_count_convention, payment_calendars, reset_calendars, settle_days, reset_days, start_date, maturity_date, dom_ccy,\n", + " principal, coupon_rate, bond_identifier, bond_name)\n", + "\n", + "luid_1" + ] + }, + { + "cell_type": "markdown", + "id": "11bab45e-eb3e-469e-af35-747e83f53201", + "metadata": {}, + "source": [ + "# 3.1 Instrument Property\n", + "We will create and upsert instrument property for the first instrument so that it can be used when defining amortisation rules\n", + "\n", + "We will do so only for one instrument so that impact of amortisation rules on instruments can be compared based on instrument properties" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bdda65d2-d9f2-4e78-8b39-683e394a94a2", + "metadata": {}, + "outputs": [], + "source": [ + "#Creating Instrument Property\n", + "try:\n", + " instrument_prop = property_api.create_property_definition(\n", + " create_property_definition_request=lusid.CreatePropertyDefinitionRequest(\n", + " domain = \"Instrument\",\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " data_type_id= lusid.ResourceId(\n", + " scope = \"system\",\n", + " code = \"string\",\n", + " ),\n", + " life_time= \"Perpetual\",\n", + " constraint_style = \"Property\",\n", + " display_name = \"Bond_Test_Prop\",\n", + " property_description = \"Testing Bond Property\",\n", + " )\n", + " \n", + " )\n", + " print(\"Instrument Property Created\")\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "286fb7e7-c6d2-4963-86af-dd89df53b68b", + "metadata": {}, + "outputs": [], + "source": [ + "#Upserting property to first Bond Instrument\n", + "try:\n", + " upsert_instrument_prop = instruments_api.upsert_instruments_properties(\n", + " upsert_instrument_property_request=[\n", + " lusid.UpsertInstrumentPropertyRequest(\n", + " identifier_type=\"LusidInstrumentId\",\n", + " identifier=luid,\n", + " properties=[\n", + " lusid.ModelProperty(\n", + " key=\"Instrument/valuation-sample/EQUITY_UK_1\",\n", + " value=lusid.PropertyValue(\n", + " label_value='fahad'\n", + " )\n", + " )\n", + " ]\n", + " )\n", + " ]\n", + " )\n", + " print(\"Instrument Property Upserted\")\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "markdown", + "id": "c3ed4107-5074-4731-9e05-105ece1e1070", + "metadata": {}, + "source": [ + "# 4. Amortisation RuleSet Creation\n", + "We will create the relevant Amortisation Rule Set" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78b84358-17fc-43f8-9479-729ed2193348", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "#Creating Amortisation Ruleset\n", + "try:\n", + " create_amt = amortisation_api.create_amortisation_rule_set(\n", + " scope= scope,\n", + " create_amortisation_rule_set_request=models.CreateAmortisationRuleSetRequest(\n", + " code=portfolio_code,\n", + " display_name='Testing_Amortisation_Rule',\n", + " description='This is a test run'\n", + " )\n", + " )\n", + " print(\"Amortisation RuleSet Created\")\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "markdown", + "id": "84a6b0bf-8a3d-420f-8133-64e608b4c2f0", + "metadata": {}, + "source": [ + "# 5. Portfolio Creation\n", + "We proceed by creating a basic transaction portfolio" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12bdbe41-a719-4abc-b980-af6ac9b27650", + "metadata": {}, + "outputs": [], + "source": [ + "# Creating a Transaction Portfolio\n", + "\n", + "try:\n", + " create_portfolio_response = transaction_portfolio_api.create_portfolio(\n", + " scope=scope,\n", + " create_transaction_portfolio_request=models.CreateTransactionPortfolioRequest(\n", + " display_name=\"RuleSet_Test\",\n", + " code=portfolio_code,\n", + " base_currency=\"GBP\",\n", + " created=datetime(2024, 1, 1, 0, 0, tzinfo=pytz.utc).isoformat(),\n", + " sub_holding_keys=[],\n", + " amortisation_method='NoAmortisation',\n", + " amortisation_rule_set_id={\n", + " \"scope\": scope,\n", + " \"code\": portfolio_code\n", + " }\n", + "\n", + " ),\n", + " )\n", + "\n", + " print(\"Portfolio Created\")\n", + "\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])\n" + ] + }, + { + "cell_type": "markdown", + "id": "5ea98c2c-7be2-4912-87bc-7735482d1f64", + "metadata": {}, + "source": [ + "# 5.1 Portfolio Property\n", + "We will create and upsert portfolio property for the relevant portfolio" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18e40c13-2639-4ace-abfa-38f32c75d683", + "metadata": {}, + "outputs": [], + "source": [ + "#Creating Portfolio Property\n", + "try:\n", + " property_creation = property_api.create_property_definition(\n", + " create_property_definition_request=lusid.CreatePropertyDefinitionRequest(\n", + " domain = \"Portfolio\",\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " data_type_id= lusid.ResourceId(\n", + " scope = \"system\",\n", + " code = \"string\",\n", + " ),\n", + " life_time= \"Perpetual\",\n", + " constraint_style = \"Property\",\n", + " display_name = \"Portfolio_property\",\n", + " property_description = \"Testing amortisation rule Property\",\n", + " )\n", + " \n", + " )\n", + " print('Portfolio Property Created')\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ebf29d2-a432-4bcd-9dce-7c23dadbcaf7", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "#Upserting property to Portfolio\n", + "try:\n", + " upsert_port= portfolio_api.upsert_portfolio_properties(\n", + " scope= scope,\n", + " code= portfolio_code,\n", + " request_body={\n", + " \"Portfolio/valuation-sample/EQUITY_UK_1\": {\n", + " \n", + " \"key\": \"Portfolio/valuation-sample/EQUITY_UK_1\",\n", + " \"value\": {\n", + " \"labelValue\": \"fahad\"\n", + " },\n", + " },\n", + " }\n", + " \n", + " ) \n", + " print(\"Portfolio Property Upserted\")\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "markdown", + "id": "974d2d19-18e1-4b17-a592-4d3db16ae125", + "metadata": {}, + "source": [ + "# 6. Setting Amortisation Rule\n", + "We will upsert rules to the Amortisation Rule Set" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1685dc4-f641-4da3-937b-9f858ae8d5ec", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "#Set Amortisation Rules\n", + "\n", + "set_amt_rule = amortisation_api.set_amortisation_rules(\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", + " rules_interval=models.RulesInterval(\n", + " effective_range={\n", + " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", + " },\n", + " rules=[]\n", + " )\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "6853d35f-8b18-43a6-bc3d-56fde9f5794c", + "metadata": {}, + "source": [ + "# 6.1 List Amortisation RuleSets\n", + "Using this endpoint we can see the list of Amortisation RuleSets and associates Rules" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b2b1797-f5c0-46f9-96d8-baa83319d641", + "metadata": {}, + "outputs": [], + "source": [ + "#List RuleSets\n", + "\n", + "list_amt= amortisation_api.list_amortisation_rule_sets()\n", + "list_amt" + ] + }, + { + "cell_type": "markdown", + "id": "95e7d51f-227a-4834-9656-77faa317e441", + "metadata": {}, + "source": [ + "# 7. Upsert Transactions\n", + "We will upsert similar transactions for the created instruments to observe the impacts of the amortisation rules" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c07f66c-c27a-418a-9d28-3d79faeec7cd", + "metadata": {}, + "outputs": [], + "source": [ + "transactions = pd.read_csv(\"Transaction_Data.csv\")\n", + "transactions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c83f29b0-3530-4e28-bb6f-f32aa5f46ada", + "metadata": {}, + "outputs": [], + "source": [ + "#Upserting Transactions\n", + "transaction_request = [\n", + " lusid.TransactionRequest(\n", + " transaction_id=row[\"txn_id\"],\n", + " type=row[\"type\"],\n", + " instrument_identifiers={\n", + " \"Instrument/default/ClientInternal\": row[\"client_id\"]\n", + " },\n", + " transaction_date=to_date(row[\"trade_date\"]).isoformat(),\n", + " settlement_date=to_date(row[\"settlement_date\"]).isoformat(),\n", + " units=row[\"quantity\"],\n", + " transaction_price=lusid.TransactionPrice(price=row[\"price\"], type=\"Price\"),\n", + " total_consideration=lusid.CurrencyAndAmount(\n", + " amount=row[\"total_consideration\"], currency=row[\"currency\"]\n", + " ),\n", + " )\n", + " for index, row in transactions.iterrows()\n", + "]\n", + "\n", + "response = transaction_portfolio_api.upsert_transactions(\n", + " scope=scope, code=portfolio_code, transaction_request=transaction_request\n", + ")\n", + "\n", + "print(f\"Transactions succesfully updated at time: {response.version.as_at_date}\")" + ] + }, + { + "cell_type": "markdown", + "id": "2d169207-b6ce-48eb-a506-ee8009d5c528", + "metadata": {}, + "source": [ + "# 8. Recipe Creation\n", + "\n", + "Following the initial setup, we can see to configuring how LUSID will conduct valuation. This introduces the concept of recipes, which are a set of steps we specify to the valuation engine relating to market data and model specification.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "abacf1ce-1bb8-4c54-806b-4758ef2b6dcf", + "metadata": {}, + "outputs": [], + "source": [ + "# Creating Recipe to perform a valuation\n", + "try:\n", + " configuration_recipe = models.ConfigurationRecipe(\n", + " scope=scope,\n", + " code=portfolio_code,\n", + " market=models.MarketContext(\n", + " market_rules=[\n", + " # define how to resolve the quotes\n", + " models.MarketDataKeyRule(\n", + " key=\"Quote.ClientInternal.*\",\n", + " supplier=\"Lusid\",\n", + " data_scope=scope,\n", + " quote_type=\"Price\",\n", + " field= \"bid\",\n", + " ),\n", + " ],\n", + " options=models.MarketOptions(\n", + " default_supplier=\"Lusid\",\n", + " default_instrument_code_type=\"ClientInternal\",\n", + " default_scope=scope,\n", + " ),\n", + " ),\n", + " pricing=models.PricingContext(\n", + " options={\"AllowPartiallySuccessfulEvaluation\": True},\n", + " ),\n", + " )\n", + " \n", + " upsert_configuration_recipe_response = configuration_recipe_api.upsert_configuration_recipe(\n", + " upsert_recipe_request=models.UpsertRecipeRequest(\n", + " configuration_recipe=configuration_recipe\n", + " )\n", + " )\n", + " print(\"Recipe Created Successfully\")\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "markdown", + "id": "fab0149c-87d9-41aa-87d8-07c7bce5dd5d", + "metadata": {}, + "source": [ + "# 9. Valuation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3e702eb-232d-45dc-ab28-77c798d85a3e", + "metadata": {}, + "outputs": [], + "source": [ + "#Defining function for Valuation\n", + "\n", + "def generate_valuation_request(valuation_effectiveAt):\n", + "\n", + " # Create the valuation request\n", + " valuation_request = models.ValuationRequest(\n", + " recipe_id=models.ResourceId(\n", + " scope=scope, code=portfolio_code\n", + " ),\n", + " metrics=[\n", + " models.AggregateSpec(\"Instrument/default/Name\", \"Value\"),\n", + " models.AggregateSpec(\"Valuation/PvInReportCcy\", \"Proportion\"),\n", + " models.AggregateSpec(\"Valuation/PvInReportCcy\", \"Sum\"),\n", + " models.AggregateSpec(\"Holding/default/Units\", \"Sum\"),\n", + " models.AggregateSpec(\"Holding/AmortisedCost\", \"Value\"),\n", + " ],\n", + " group_by=[\"Instrument/default/Name\"],\n", + " portfolio_entity_ids=[\n", + " models.PortfolioEntityId(scope=scope, code=portfolio_code)\n", + " ],\n", + " valuation_schedule=models.ValuationSchedule(\n", + " effective_at=valuation_effectiveAt.isoformat()\n", + " ),\n", + " )\n", + "\n", + " return valuation_request" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c16f197-ef0c-4f80-9bef-b0cd2404989e", + "metadata": {}, + "outputs": [], + "source": [ + "#Getting Valuation\n", + "\n", + "aggregation = aggregation_api.get_valuation(\n", + " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", + " )\n", + ")\n", + "\n", + "pd.DataFrame(aggregation.data)" + ] + }, + { + "cell_type": "markdown", + "id": "f1e86e68-fb62-4d08-a1fc-a99fd59f3a86", + "metadata": {}, + "source": [ + "We can see that the Amortised Cost is calculated based on No Amortisation method as set when creating the portfolio. No Amortisation Rules were defined at this point " + ] + }, + { + "cell_type": "markdown", + "id": "52fe7dbb-1912-4e14-ab4e-026a3ae4d5c3", + "metadata": {}, + "source": [ + "# 9.1 Amortisation Rules Filters" + ] + }, + { + "cell_type": "markdown", + "id": "0405558f-31bb-431a-9a10-65a9ed68b81f", + "metadata": {}, + "source": [ + "Setting Amortisation Rule filter to True eq True. This means that the amortisation method set in the rule defined will take precedence for all transactions in the relevant portfolio" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29228d0a-6dc0-4ebb-8838-dee07397095f", + "metadata": {}, + "outputs": [], + "source": [ + "#Updating Amortisation Rule \n", + "\n", + "set_amt_rule = amortisation_api.set_amortisation_rules(\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", + " rules_interval=models.RulesInterval(\n", + " effective_range={\n", + " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", + " },\n", + " rules=[\n", + " {\n", + " \"name\" : \"Amortisation_Rule_1\",\n", + " \"description\" : \"Test Rule setting filter to True equals True\",\n", + " \"filter\" : \"True eq True\",\n", + " \"amortisationMethod\" : \"StraightLine\",\n", + " },\n", + " ]\n", + " )\n", + " )\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa6201bc-1eca-428a-8c30-96905d58ff64", + "metadata": {}, + "outputs": [], + "source": [ + "#Getting Valuation\n", + "\n", + "aggregation = aggregation_api.get_valuation(\n", + " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", + " )\n", + ")\n", + "\n", + "pd.DataFrame(aggregation.data)" + ] + }, + { + "cell_type": "markdown", + "id": "7ff51ca5-9075-472b-a4aa-59532eb0fc90", + "metadata": {}, + "source": [ + "We can see that the Amortised Cost is calculate based on StraightLine method as updated in the rule." + ] + }, + { + "cell_type": "markdown", + "id": "0e09aa4e-5060-42b8-b90e-61ab00808730", + "metadata": {}, + "source": [ + "Setting Amortisation Rule filter based on instrument property. This means that the amortisation method set in the rule defined will take precedence over the method set on portfolio creation, for the instrument with the relevant property that is used in the filter" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "933a83b6-cee3-435f-bc77-83c19f9a2ca2", + "metadata": {}, + "outputs": [], + "source": [ + "#Updating Amortisation Rule\n", + "\n", + "set_amt_rule = amortisation_api.set_amortisation_rules(\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", + " rules_interval=models.RulesInterval(\n", + " effective_range={\n", + " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", + " },\n", + " rules=[\n", + " {\n", + " \"name\" : \"Amortisation_Rule_1\",\n", + " \"description\" : \"Test Rule Description\",\n", + " \"filter\" : \"properties[Instrument/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"amortisationMethod\" : \"StraightLine\",\n", + " },\n", + " ]\n", + " )\n", + " )\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc4a8e74-fffb-465e-bf92-5eedf43328fc", + "metadata": {}, + "outputs": [], + "source": [ + "#Getting Valuation\n", + "\n", + "aggregation = aggregation_api.get_valuation(\n", + " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", + " )\n", + ")\n", + "\n", + "pd.DataFrame(aggregation.data)" + ] + }, + { + "cell_type": "markdown", + "id": "c5f251f3-72dd-49c5-99d1-c7e77054e45c", + "metadata": {}, + "source": [ + "We can see that the Amortised Cost is calculate based on StraightLine method as updated in the rule for TestBond_A as it has the relevant property whereas TestBond_B has amortised cost based on NoAmortisation method" + ] + }, + { + "cell_type": "markdown", + "id": "4da21ed6-881f-4554-9c60-354ec2b7544c", + "metadata": {}, + "source": [ + "Now, setting Amortisation Rule filter based on Portfolio property. This means that the amortisation method set in the rule defined will take precedence over the method set on portfolio creation, for the instruments belonging to that portfolio." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc0cc542-092a-4aa7-b9ed-a70c871ae97f", + "metadata": {}, + "outputs": [], + "source": [ + "#Set Amortisation Rules\n", + "\n", + "set_amt_rule = amortisation_api.set_amortisation_rules(\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", + " rules_interval=models.RulesInterval(\n", + " effective_range={\n", + " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", + " },\n", + " rules=[\n", + " {\n", + " \"name\" : \"Amortisation_Rule_2\",\n", + " \"description\" : \"Test Rule Description\",\n", + " \"filter\" : \"properties[Portfolio/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"amortisationMethod\" : \"EffectiveYield\",\n", + " },\n", + " \n", + " ]\n", + " )\n", + " )\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ccd9dac-435b-46f7-9498-9c2759627b65", + "metadata": {}, + "outputs": [], + "source": [ + "#Getting Valuation\n", + "\n", + "aggregation = aggregation_api.get_valuation(\n", + " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", + " )\n", + ")\n", + "\n", + "pd.DataFrame(aggregation.data)" + ] + }, + { + "cell_type": "markdown", + "id": "d079ced5-9f85-41b4-9293-23c26197f08a", + "metadata": {}, + "source": [ + "We can see that the Amortised Cost is calculated based on EffectiveYield method as updated in the rule." + ] + }, + { + "cell_type": "markdown", + "id": "f163318a-9439-4f59-9836-7989104bfc0f", + "metadata": {}, + "source": [ + "Now, we will show that when multiple rules exist, the first one that matches is implemented." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4ca98a9-1d46-47b1-a0d4-7bfcccfb5ad1", + "metadata": {}, + "outputs": [], + "source": [ + "#Set Amortisation Rules\n", + "\n", + "set_amt_rule = amortisation_api.set_amortisation_rules(\n", + " scope = scope,\n", + " code = portfolio_code,\n", + " set_amortisation_rules_request=models.SetAmortisationRulesRequest(\n", + " rules_interval=models.RulesInterval(\n", + " effective_range={\n", + " \"fromDate\": \"0001-01-01T00:00:00.0000000+00:00\",\n", + " },\n", + " rules=[\n", + " {\n", + " \"name\" : \"Amortisation_Rule_1\",\n", + " \"description\" : \"Test Rule Description\",\n", + " \"filter\" : \"properties[Instrument/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"amortisationMethod\" : \"StraightLine\",\n", + " },\n", + " {\n", + " \"name\" : \"Amortisation_Rule_2\",\n", + " \"description\" : \"Test Rule Description\",\n", + " \"filter\" : \"properties[Portfolio/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"amortisationMethod\" : \"EffectiveYield\",\n", + " },\n", + " \n", + " ]\n", + " )\n", + " )\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33dcb965-54d8-488b-bdc6-5ecb8c8d84bc", + "metadata": {}, + "outputs": [], + "source": [ + "#Getting Valuation\n", + "\n", + "aggregation = aggregation_api.get_valuation(\n", + " valuation_request=generate_valuation_request(datetime(year=2024, month=4, day=25, tzinfo=pytz.UTC)\n", + " )\n", + ")\n", + "\n", + "pd.DataFrame(aggregation.data)" + ] + }, + { + "cell_type": "markdown", + "id": "b7d031fa-9a9f-451c-b7f8-64b689bf4920", + "metadata": {}, + "source": [ + "We can see that the Amortised Cost is calculated based on StraightLine method for TestBond_A as it matches the first rule where as EffectiveYield method is used for TestBond_B as it matches the second rule in the list." + ] + }, + { + "cell_type": "markdown", + "id": "699637a4-e94d-492d-ae8f-5c15c370cdd7", + "metadata": {}, + "source": [ + "# 10. Data Cleaning" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b7ae1d9-f0ff-470a-81d7-c82f414bcf3a", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "#Delete Instruments\n", + "def DeleteInstruments(luid): \n", + " try:\n", + " delete_instrument = instruments_api.delete_instrument(\n", + " identifier_type=\"LusidInstrumentId\", identifier= luid\n", + " )\n", + " \n", + " print(delete_instrument)\n", + " \n", + " except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3fbcad5e-95d8-4d7c-a869-2398b4138e37", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Deleting Instruments\n", + "\n", + "DeleteInstruments(luid)\n", + "DeleteInstruments(luid_1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46f0ea57-113a-4a0a-bb9d-dd15bbbd77ec", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Rule\n", + "\n", + "delete_amt= amortisation_api.delete_amortisation_ruleset(\n", + " scope=scope,\n", + " code=portfolio_code\n", + ")\n", + "\n", + "delete_amt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "baee566b-9862-45bc-8ad7-a853b0b10683", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Portfolio\n", + "try:\n", + " delete_portfolio = portfolio_api.delete_portfolio(scope, portfolio_code)\n", + " \n", + " print(delete_portfolio)\n", + "\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b56cd719-5555-4ba9-b3f8-e5efd2117f71", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Recipe\n", + "try:\n", + " delete_recipe = configuration_recipe_api.delete_configuration_recipe(\n", + " scope=scope,\n", + " code=portfolio_code,\n", + " )\n", + "\n", + " print(delete_recipe)\n", + "\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3026aba6-3ad1-4695-addf-c0e591978d96", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Portfolio Properties\n", + "try:\n", + " delete_portfolio_prop = property_api.delete_property_definition(\n", + " domain= 'Portfolio',\n", + " scope= scope,\n", + " code= portfolio_code,\n", + " )\n", + " print(delete_portfolio_prop)\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4219244-1fd4-462d-9d74-f5e1a37a4f74", + "metadata": {}, + "outputs": [], + "source": [ + "'''\n", + "#Delete Instrument Properties\n", + "try:\n", + " delete_instrument_prop = property_api.delete_property_definition(\n", + " domain= 'Instrument',\n", + " scope= scope,\n", + " code= portfolio_code,\n", + " )\n", + " print(delete_instrument_prop)\n", + "except lusid.ApiException as e:\n", + " print(json.loads(e.body)[\"title\"])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/use-cases/amortisation-ruleset/Transaction_Data.csv b/examples/use-cases/amortisation-ruleset/Transaction_Data.csv new file mode 100644 index 00000000..d08fd368 --- /dev/null +++ b/examples/use-cases/amortisation-ruleset/Transaction_Data.csv @@ -0,0 +1,3 @@ +txn_id,type,trade_date,settlement_date,quantity,price,currency,client_id,total_consideration +txn001,Buy,2024-01-15T00:00:00Z,2024-01-15T00:00:00Z,1,1444,GBP,Test_Bond_A,0 +txn002,Buy,2024-01-15T00:00:00Z,2024-01-15T00:00:00Z,1,1444,GBP,Test_Bond_B,0 From 4cba74d193e47a8e6e5ec20672860a374017a0c6 Mon Sep 17 00:00:00 2001 From: fmak404 Date: Fri, 2 Aug 2024 15:57:35 +0100 Subject: [PATCH 13/14] Add files via upload --- .../AmortisationRules.ipynb | 413 +++++++++++++++--- .../amortisation-ruleset/Transaction_Data.csv | 4 +- 2 files changed, 361 insertions(+), 56 deletions(-) diff --git a/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb b/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb index bec6a5c2..14a06e8e 100644 --- a/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb +++ b/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb @@ -2,10 +2,36 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "61f113e0-ba28-400b-9ed8-216883e8df87", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " \n", + " \n", + "\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from lusidtools.jupyter_tools import toggle_code\n", "\n", @@ -70,7 +96,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "60ee9804-9071-46f9-907d-3d9c19855b4b", "metadata": {}, "outputs": [], @@ -136,7 +162,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "25c79a41-31bc-403a-a69b-d33672a8726f", "metadata": {}, "outputs": [], @@ -163,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "74300dd0-961b-434a-be5b-6e8f88f04b0a", "metadata": {}, "outputs": [], @@ -176,7 +202,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "02dcb40b-9f46-48e7-b97e-7d876d1ce836", "metadata": {}, "outputs": [], @@ -225,10 +251,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "bd48d82d-ce0f-490f-b0ac-143e9712f8b5", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'LUID_00003E7E'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "#Creating first Bond Instrument\n", "\n", @@ -245,8 +282,8 @@ "dom_ccy = \"GBP\"\n", "principal = 10487\n", "coupon_rate = 0.07\n", - "bond_identifier = \"Test_Bond_A\"\n", - "bond_name = \"TestBond_A\"\n", + "bond_identifier = \"Example_Bond_A\"\n", + "bond_name = \"ExampleBond_A\"\n", "\n", "luid = create_bond(currency, payment_frequency, roll_convention, day_count_convention, payment_calendars, reset_calendars, settle_days, reset_days, start_date, maturity_date, dom_ccy, \n", " principal, coupon_rate, bond_identifier, bond_name)\n", @@ -256,10 +293,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "c0267add-3535-4eb6-a7c2-a57976952b5d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'LUID_00003E7F'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "#Creating second Bond Instrument\n", "\n", @@ -276,8 +324,8 @@ "dom_ccy = \"GBP\"\n", "principal = 10487\n", "coupon_rate = 0.07\n", - "bond_identifier = \"Test_Bond_B\"\n", - "bond_name = \"TestBond_B\"\n", + "bond_identifier = \"Example_Bond_B\"\n", + "bond_name = \"ExampleBond_B\"\n", "\n", "luid_1=create_bond(currency, payment_frequency, roll_convention, day_count_convention, payment_calendars, reset_calendars, settle_days, reset_days, start_date, maturity_date, dom_ccy,\n", " principal, coupon_rate, bond_identifier, bond_name)\n", @@ -298,10 +346,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "bdda65d2-d9f2-4e78-8b39-683e394a94a2", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Instrument Property Created\n" + ] + } + ], "source": [ "#Creating Instrument Property\n", "try:\n", @@ -316,8 +372,8 @@ " ),\n", " life_time= \"Perpetual\",\n", " constraint_style = \"Property\",\n", - " display_name = \"Bond_Test_Prop\",\n", - " property_description = \"Testing Bond Property\",\n", + " display_name = \"Bond_Prop_1\",\n", + " property_description = \"Example Bond Property\",\n", " )\n", " \n", " )\n", @@ -328,10 +384,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "286fb7e7-c6d2-4963-86af-dd89df53b68b", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Instrument Property Upserted\n" + ] + } + ], "source": [ "#Upserting property to first Bond Instrument\n", "try:\n", @@ -344,7 +408,7 @@ " lusid.ModelProperty(\n", " key=\"Instrument/valuation-sample/EQUITY_UK_1\",\n", " value=lusid.PropertyValue(\n", - " label_value='fahad'\n", + " label_value='alpha1'\n", " )\n", " )\n", " ]\n", @@ -367,12 +431,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "78b84358-17fc-43f8-9479-729ed2193348", "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Amortisation RuleSet Created\n" + ] + } + ], "source": [ "#Creating Amortisation Ruleset\n", "try:\n", @@ -380,8 +452,8 @@ " scope= scope,\n", " create_amortisation_rule_set_request=models.CreateAmortisationRuleSetRequest(\n", " code=portfolio_code,\n", - " display_name='Testing_Amortisation_Rule',\n", - " description='This is a test run'\n", + " display_name='Example_Amortisation_Rule',\n", + " description='This is an example run'\n", " )\n", " )\n", " print(\"Amortisation RuleSet Created\")\n", @@ -400,10 +472,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "12bdbe41-a719-4abc-b980-af6ac9b27650", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Portfolio Created\n" + ] + } + ], "source": [ "# Creating a Transaction Portfolio\n", "\n", @@ -442,10 +522,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "18e40c13-2639-4ace-abfa-38f32c75d683", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Portfolio Property Created\n" + ] + } + ], "source": [ "#Creating Portfolio Property\n", "try:\n", @@ -461,7 +549,7 @@ " life_time= \"Perpetual\",\n", " constraint_style = \"Property\",\n", " display_name = \"Portfolio_property\",\n", - " property_description = \"Testing amortisation rule Property\",\n", + " property_description = \"Example amortisation rule Property\",\n", " )\n", " \n", " )\n", @@ -472,12 +560,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "5ebf29d2-a432-4bcd-9dce-7c23dadbcaf7", "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Portfolio Property Upserted\n" + ] + } + ], "source": [ "#Upserting property to Portfolio\n", "try:\n", @@ -489,7 +585,7 @@ " \n", " \"key\": \"Portfolio/valuation-sample/EQUITY_UK_1\",\n", " \"value\": {\n", - " \"labelValue\": \"fahad\"\n", + " \"labelValue\": \"alpha1\"\n", " },\n", " },\n", " }\n", @@ -511,7 +607,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "a1685dc4-f641-4da3-937b-9f858ae8d5ec", "metadata": { "scrolled": true @@ -545,10 +641,51 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "5b2b1797-f5c0-46f9-96d8-baa83319d641", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'href': 'https://fbn-fmak.lusid.com/api/api/amortisation/rulesets?effectiveAt=2024-08-02T14%3A55%3A53.3795360%2B00%3A00&asAt=2024-08-02T14%3A55%3A53.3144060%2B00%3A00&page=ZAAAAPwb4DQDs9wIAAAAAAAAAAAgDOo0A7PcCAAAAAAAAAAAAAAAAAAAAAA%3D',\n", + " 'links': [{'description': None,\n", + " 'href': 'https://fbn-fmak.lusid.com/api/api/schemas/entities/AmortisationRuleSet',\n", + " 'method': 'GET',\n", + " 'relation': 'EntitySchema'},\n", + " {'description': 'A link to the LUSID Insights website showing all '\n", + " 'logs related to this request',\n", + " 'href': 'https://fbn-fmak.lusid.com/app/insights/logs/0HN5IQM9D266E:0000020B',\n", + " 'method': 'GET',\n", + " 'relation': 'RequestLogs'}],\n", + " 'next_page': None,\n", + " 'previous_page': None,\n", + " 'values': [{'description': 'This is an example run',\n", + " 'display_name': 'Example_Amortisation_Rule',\n", + " 'href': None,\n", + " 'id': {'code': 'EQUITY_UK_1', 'scope': 'valuation-sample'},\n", + " 'links': None,\n", + " 'rules_interval': {'effective_range': {'from_date': datetime.datetime(1, 1, 1, 0, 0, tzinfo=tzlocal()),\n", + " 'until_date': None},\n", + " 'rules': []},\n", + " 'version': {'as_at_created': datetime.datetime(2024, 8, 2, 14, 55, 51, 636771, tzinfo=tzlocal()),\n", + " 'as_at_date': datetime.datetime(2024, 8, 2, 14, 55, 53, 220352, tzinfo=tzlocal()),\n", + " 'as_at_modified': datetime.datetime(2024, 8, 2, 14, 55, 53, 220352, tzinfo=tzlocal()),\n", + " 'as_at_version_number': 2,\n", + " 'effective_from': datetime.datetime(1, 1, 1, 0, 0, tzinfo=tzlocal()),\n", + " 'entity_unique_id': '779369d0-ac4e-487f-b69a-f74b9f9222b1',\n", + " 'request_id_created': '0HN5IQM9D266E:00000209',\n", + " 'request_id_modified': '0HN5IQM9D25VL:000000F1',\n", + " 'staged_modification_id_modified': None,\n", + " 'user_id_created': '00ut7mpz1tQhlU4722p7',\n", + " 'user_id_modified': '00ut7mpz1tQhlU4722p7'}}]}" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "#List RuleSets\n", "\n", @@ -567,10 +704,86 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "3c07f66c-c27a-418a-9d28-3d79faeec7cd", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
txn_idtypetrade_datesettlement_datequantitypricecurrencyclient_idtotal_consideration
0txn001Buy2024-01-15T00:00:00Z2024-01-15T00:00:00Z11444GBPExample_Bond_A0
1txn002Buy2024-01-15T00:00:00Z2024-01-15T00:00:00Z11444GBPExample_Bond_B0
\n", + "
" + ], + "text/plain": [ + " txn_id type trade_date settlement_date quantity price \\\n", + "0 txn001 Buy 2024-01-15T00:00:00Z 2024-01-15T00:00:00Z 1 1444 \n", + "1 txn002 Buy 2024-01-15T00:00:00Z 2024-01-15T00:00:00Z 1 1444 \n", + "\n", + " currency client_id total_consideration \n", + "0 GBP Example_Bond_A 0 \n", + "1 GBP Example_Bond_B 0 " + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "transactions = pd.read_csv(\"Transaction_Data.csv\")\n", "transactions" @@ -578,10 +791,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "c83f29b0-3530-4e28-bb6f-f32aa5f46ada", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Transactions succesfully updated at time: 2024-08-02 14:55:54.246466+00:00\n" + ] + } + ], "source": [ "#Upserting Transactions\n", "transaction_request = [\n", @@ -621,10 +842,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "abacf1ce-1bb8-4c54-806b-4758ef2b6dcf", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Recipe Created Successfully\n" + ] + } + ], "source": [ "# Creating Recipe to perform a valuation\n", "try:\n", @@ -673,7 +902,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "id": "a3e702eb-232d-45dc-ab28-77c798d85a3e", "metadata": {}, "outputs": [], @@ -708,10 +937,78 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "7c16f197-ef0c-4f80-9bef-b0cd2404989e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Instrument/default/NameProportion(Valuation/PvInReportCcy)Sum(Valuation/PvInReportCcy)Sum(Holding/default/Units)Holding/AmortisedCost
0ExampleBond_A0.510731.6966671.01444.0
1ExampleBond_B0.510731.6966671.01444.0
\n", + "
" + ], + "text/plain": [ + " Instrument/default/Name Proportion(Valuation/PvInReportCcy) \\\n", + "0 ExampleBond_A 0.5 \n", + "1 ExampleBond_B 0.5 \n", + "\n", + " Sum(Valuation/PvInReportCcy) Sum(Holding/default/Units) \\\n", + "0 10731.696667 1.0 \n", + "1 10731.696667 1.0 \n", + "\n", + " Holding/AmortisedCost \n", + "0 1444.0 \n", + "1 1444.0 " + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "#Getting Valuation\n", "\n", @@ -767,7 +1064,7 @@ " rules=[\n", " {\n", " \"name\" : \"Amortisation_Rule_1\",\n", - " \"description\" : \"Test Rule setting filter to True equals True\",\n", + " \"description\" : \"Rule setting filter to True equals True\",\n", " \"filter\" : \"True eq True\",\n", " \"amortisationMethod\" : \"StraightLine\",\n", " },\n", @@ -830,8 +1127,8 @@ " rules=[\n", " {\n", " \"name\" : \"Amortisation_Rule_1\",\n", - " \"description\" : \"Test Rule Description\",\n", - " \"filter\" : \"properties[Instrument/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"description\" : \"Rule Description\",\n", + " \"filter\" : \"properties[Instrument/valuation-sample/EQUITY_UK_1] eq 'alpha1'\",\n", " \"amortisationMethod\" : \"StraightLine\",\n", " },\n", " ]\n", @@ -893,8 +1190,8 @@ " rules=[\n", " {\n", " \"name\" : \"Amortisation_Rule_2\",\n", - " \"description\" : \"Test Rule Description\",\n", - " \"filter\" : \"properties[Portfolio/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"description\" : \"Rule Description\",\n", + " \"filter\" : \"properties[Portfolio/valuation-sample/EQUITY_UK_1] eq 'alpha1'\",\n", " \"amortisationMethod\" : \"EffectiveYield\",\n", " },\n", " \n", @@ -957,14 +1254,14 @@ " rules=[\n", " {\n", " \"name\" : \"Amortisation_Rule_1\",\n", - " \"description\" : \"Test Rule Description\",\n", - " \"filter\" : \"properties[Instrument/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"description\" : \"Rule Description\",\n", + " \"filter\" : \"properties[Instrument/valuation-sample/EQUITY_UK_1] eq 'alpha1'\",\n", " \"amortisationMethod\" : \"StraightLine\",\n", " },\n", " {\n", " \"name\" : \"Amortisation_Rule_2\",\n", - " \"description\" : \"Test Rule Description\",\n", - " \"filter\" : \"properties[Portfolio/valuation-sample/EQUITY_UK_1] eq 'fahad'\",\n", + " \"description\" : \"Rule Description\",\n", + " \"filter\" : \"properties[Portfolio/valuation-sample/EQUITY_UK_1] eq 'alpha1'\",\n", " \"amortisationMethod\" : \"EffectiveYield\",\n", " },\n", " \n", @@ -1138,6 +1435,14 @@ "except lusid.ApiException as e:\n", " print(json.loads(e.body)[\"title\"])" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07ebcd02-59ef-4678-bbb4-2e0830744f6b", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/examples/use-cases/amortisation-ruleset/Transaction_Data.csv b/examples/use-cases/amortisation-ruleset/Transaction_Data.csv index d08fd368..ba503fe9 100644 --- a/examples/use-cases/amortisation-ruleset/Transaction_Data.csv +++ b/examples/use-cases/amortisation-ruleset/Transaction_Data.csv @@ -1,3 +1,3 @@ txn_id,type,trade_date,settlement_date,quantity,price,currency,client_id,total_consideration -txn001,Buy,2024-01-15T00:00:00Z,2024-01-15T00:00:00Z,1,1444,GBP,Test_Bond_A,0 -txn002,Buy,2024-01-15T00:00:00Z,2024-01-15T00:00:00Z,1,1444,GBP,Test_Bond_B,0 +txn001,Buy,2024-01-15T00:00:00Z,2024-01-15T00:00:00Z,1,1444,GBP,Example_Bond_A,0 +txn002,Buy,2024-01-15T00:00:00Z,2024-01-15T00:00:00Z,1,1444,GBP,Example_Bond_B,0 From aecc22146e36758b6cd9c8d7cc26fda4d71b3d39 Mon Sep 17 00:00:00 2001 From: fmak404 Date: Fri, 2 Aug 2024 16:07:28 +0100 Subject: [PATCH 14/14] Add files via upload --- .../AmortisationRules.ipynb | 375 ++---------------- 1 file changed, 39 insertions(+), 336 deletions(-) diff --git a/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb b/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb index 14a06e8e..34d01c53 100644 --- a/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb +++ b/examples/use-cases/amortisation-ruleset/AmortisationRules.ipynb @@ -2,36 +2,10 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "61f113e0-ba28-400b-9ed8-216883e8df87", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "
\n", - " \n", - " \n", - "\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "from lusidtools.jupyter_tools import toggle_code\n", "\n", @@ -96,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "60ee9804-9071-46f9-907d-3d9c19855b4b", "metadata": {}, "outputs": [], @@ -162,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "25c79a41-31bc-403a-a69b-d33672a8726f", "metadata": {}, "outputs": [], @@ -189,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "74300dd0-961b-434a-be5b-6e8f88f04b0a", "metadata": {}, "outputs": [], @@ -202,7 +176,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "02dcb40b-9f46-48e7-b97e-7d876d1ce836", "metadata": {}, "outputs": [], @@ -251,21 +225,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "bd48d82d-ce0f-490f-b0ac-143e9712f8b5", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'LUID_00003E7E'" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "#Creating first Bond Instrument\n", "\n", @@ -293,21 +256,10 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "c0267add-3535-4eb6-a7c2-a57976952b5d", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'LUID_00003E7F'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "#Creating second Bond Instrument\n", "\n", @@ -346,18 +298,10 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "bdda65d2-d9f2-4e78-8b39-683e394a94a2", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Instrument Property Created\n" - ] - } - ], + "outputs": [], "source": [ "#Creating Instrument Property\n", "try:\n", @@ -384,18 +328,10 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "286fb7e7-c6d2-4963-86af-dd89df53b68b", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Instrument Property Upserted\n" - ] - } - ], + "outputs": [], "source": [ "#Upserting property to first Bond Instrument\n", "try:\n", @@ -431,20 +367,12 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "78b84358-17fc-43f8-9479-729ed2193348", "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Amortisation RuleSet Created\n" - ] - } - ], + "outputs": [], "source": [ "#Creating Amortisation Ruleset\n", "try:\n", @@ -472,18 +400,10 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "12bdbe41-a719-4abc-b980-af6ac9b27650", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Portfolio Created\n" - ] - } - ], + "outputs": [], "source": [ "# Creating a Transaction Portfolio\n", "\n", @@ -491,7 +411,7 @@ " create_portfolio_response = transaction_portfolio_api.create_portfolio(\n", " scope=scope,\n", " create_transaction_portfolio_request=models.CreateTransactionPortfolioRequest(\n", - " display_name=\"RuleSet_Test\",\n", + " display_name=\"RuleSet_Portfolio\",\n", " code=portfolio_code,\n", " base_currency=\"GBP\",\n", " created=datetime(2024, 1, 1, 0, 0, tzinfo=pytz.utc).isoformat(),\n", @@ -522,18 +442,10 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "18e40c13-2639-4ace-abfa-38f32c75d683", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Portfolio Property Created\n" - ] - } - ], + "outputs": [], "source": [ "#Creating Portfolio Property\n", "try:\n", @@ -560,20 +472,12 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "5ebf29d2-a432-4bcd-9dce-7c23dadbcaf7", "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Portfolio Property Upserted\n" - ] - } - ], + "outputs": [], "source": [ "#Upserting property to Portfolio\n", "try:\n", @@ -607,7 +511,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "a1685dc4-f641-4da3-937b-9f858ae8d5ec", "metadata": { "scrolled": true @@ -641,51 +545,10 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "5b2b1797-f5c0-46f9-96d8-baa83319d641", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'href': 'https://fbn-fmak.lusid.com/api/api/amortisation/rulesets?effectiveAt=2024-08-02T14%3A55%3A53.3795360%2B00%3A00&asAt=2024-08-02T14%3A55%3A53.3144060%2B00%3A00&page=ZAAAAPwb4DQDs9wIAAAAAAAAAAAgDOo0A7PcCAAAAAAAAAAAAAAAAAAAAAA%3D',\n", - " 'links': [{'description': None,\n", - " 'href': 'https://fbn-fmak.lusid.com/api/api/schemas/entities/AmortisationRuleSet',\n", - " 'method': 'GET',\n", - " 'relation': 'EntitySchema'},\n", - " {'description': 'A link to the LUSID Insights website showing all '\n", - " 'logs related to this request',\n", - " 'href': 'https://fbn-fmak.lusid.com/app/insights/logs/0HN5IQM9D266E:0000020B',\n", - " 'method': 'GET',\n", - " 'relation': 'RequestLogs'}],\n", - " 'next_page': None,\n", - " 'previous_page': None,\n", - " 'values': [{'description': 'This is an example run',\n", - " 'display_name': 'Example_Amortisation_Rule',\n", - " 'href': None,\n", - " 'id': {'code': 'EQUITY_UK_1', 'scope': 'valuation-sample'},\n", - " 'links': None,\n", - " 'rules_interval': {'effective_range': {'from_date': datetime.datetime(1, 1, 1, 0, 0, tzinfo=tzlocal()),\n", - " 'until_date': None},\n", - " 'rules': []},\n", - " 'version': {'as_at_created': datetime.datetime(2024, 8, 2, 14, 55, 51, 636771, tzinfo=tzlocal()),\n", - " 'as_at_date': datetime.datetime(2024, 8, 2, 14, 55, 53, 220352, tzinfo=tzlocal()),\n", - " 'as_at_modified': datetime.datetime(2024, 8, 2, 14, 55, 53, 220352, tzinfo=tzlocal()),\n", - " 'as_at_version_number': 2,\n", - " 'effective_from': datetime.datetime(1, 1, 1, 0, 0, tzinfo=tzlocal()),\n", - " 'entity_unique_id': '779369d0-ac4e-487f-b69a-f74b9f9222b1',\n", - " 'request_id_created': '0HN5IQM9D266E:00000209',\n", - " 'request_id_modified': '0HN5IQM9D25VL:000000F1',\n", - " 'staged_modification_id_modified': None,\n", - " 'user_id_created': '00ut7mpz1tQhlU4722p7',\n", - " 'user_id_modified': '00ut7mpz1tQhlU4722p7'}}]}" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "#List RuleSets\n", "\n", @@ -704,86 +567,10 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "3c07f66c-c27a-418a-9d28-3d79faeec7cd", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
txn_idtypetrade_datesettlement_datequantitypricecurrencyclient_idtotal_consideration
0txn001Buy2024-01-15T00:00:00Z2024-01-15T00:00:00Z11444GBPExample_Bond_A0
1txn002Buy2024-01-15T00:00:00Z2024-01-15T00:00:00Z11444GBPExample_Bond_B0
\n", - "
" - ], - "text/plain": [ - " txn_id type trade_date settlement_date quantity price \\\n", - "0 txn001 Buy 2024-01-15T00:00:00Z 2024-01-15T00:00:00Z 1 1444 \n", - "1 txn002 Buy 2024-01-15T00:00:00Z 2024-01-15T00:00:00Z 1 1444 \n", - "\n", - " currency client_id total_consideration \n", - "0 GBP Example_Bond_A 0 \n", - "1 GBP Example_Bond_B 0 " - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "transactions = pd.read_csv(\"Transaction_Data.csv\")\n", "transactions" @@ -791,18 +578,10 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "c83f29b0-3530-4e28-bb6f-f32aa5f46ada", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Transactions succesfully updated at time: 2024-08-02 14:55:54.246466+00:00\n" - ] - } - ], + "outputs": [], "source": [ "#Upserting Transactions\n", "transaction_request = [\n", @@ -842,18 +621,10 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "abacf1ce-1bb8-4c54-806b-4758ef2b6dcf", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Recipe Created Successfully\n" - ] - } - ], + "outputs": [], "source": [ "# Creating Recipe to perform a valuation\n", "try:\n", @@ -902,7 +673,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "a3e702eb-232d-45dc-ab28-77c798d85a3e", "metadata": {}, "outputs": [], @@ -937,78 +708,10 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "7c16f197-ef0c-4f80-9bef-b0cd2404989e", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Instrument/default/NameProportion(Valuation/PvInReportCcy)Sum(Valuation/PvInReportCcy)Sum(Holding/default/Units)Holding/AmortisedCost
0ExampleBond_A0.510731.6966671.01444.0
1ExampleBond_B0.510731.6966671.01444.0
\n", - "
" - ], - "text/plain": [ - " Instrument/default/Name Proportion(Valuation/PvInReportCcy) \\\n", - "0 ExampleBond_A 0.5 \n", - "1 ExampleBond_B 0.5 \n", - "\n", - " Sum(Valuation/PvInReportCcy) Sum(Holding/default/Units) \\\n", - "0 10731.696667 1.0 \n", - "1 10731.696667 1.0 \n", - "\n", - " Holding/AmortisedCost \n", - "0 1444.0 \n", - "1 1444.0 " - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "#Getting Valuation\n", "\n", @@ -1033,7 +736,7 @@ "id": "52fe7dbb-1912-4e14-ab4e-026a3ae4d5c3", "metadata": {}, "source": [ - "# 9.1 Amortisation Rules Filters" + "# 9.1 Defining and Setting Amortisation Rules" ] }, { @@ -1041,7 +744,7 @@ "id": "0405558f-31bb-431a-9a10-65a9ed68b81f", "metadata": {}, "source": [ - "Setting Amortisation Rule filter to True eq True. This means that the amortisation method set in the rule defined will take precedence for all transactions in the relevant portfolio" + "We can change/update Amortisation Rules based on the filters we provide. Following are a few examples of how we can use filters to set amortisation rules, along with imSetting Amortisation Rule filter to True eq True. This means that the amortisation method set in the rule defined will take precedence for all transactions in the relevant portfolio" ] }, { @@ -1159,7 +862,7 @@ "id": "c5f251f3-72dd-49c5-99d1-c7e77054e45c", "metadata": {}, "source": [ - "We can see that the Amortised Cost is calculate based on StraightLine method as updated in the rule for TestBond_A as it has the relevant property whereas TestBond_B has amortised cost based on NoAmortisation method" + "We can see that the Amortised Cost is calculate based on StraightLine method as updated in the rule for ExampleBond_A as it has the relevant property whereas ExampleBond_B has amortised cost based on NoAmortisation method" ] }, { @@ -1293,7 +996,7 @@ "id": "b7d031fa-9a9f-451c-b7f8-64b689bf4920", "metadata": {}, "source": [ - "We can see that the Amortised Cost is calculated based on StraightLine method for TestBond_A as it matches the first rule where as EffectiveYield method is used for TestBond_B as it matches the second rule in the list." + "We can see that the Amortised Cost is calculated based on StraightLine method for ExampleBond_A as it matches the first rule where as EffectiveYield method is used for ExampleBond_B as it matches the second rule in the list." ] }, {