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..148ee3e6 --- /dev/null +++ b/examples/use-cases/irr-calculation/IRR_Sample_Notebook.ipynb @@ -0,0 +1,1400 @@ +{ + "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": 2, + "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": 3, + "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": 4, + "id": "3174573e-6a2f-4299-bfda-174eb6ac7ae0", + "metadata": {}, + "outputs": [], + "source": [ + "portfolio_scope= \"IRR-Examples1\"\n", + "portfolio_code=\"IRR-Notebook-Equity1\"\n", + "portfolio_name=\"IRR-Notebook-Equity1\"\n", + "instrument_scope= \"Example_IRR1\"\n", + "effective_at = datetime(2024, 5, 27, 0, 0, tzinfo=pytz.utc)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "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": 6, + "id": "7abe69ac-d122-42f9-9dbd-f3ae6d519491", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
successfailed
010
\n", + "
" + ], + "text/plain": [ + " success failed\n", + "0 1 0" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "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": 7, + "id": "e6a363e6-bfea-4b71-8d1c-2fff5cd36a62", + "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", + "
instrument_nameclient_internalcurrencyfigiexchange_codetickermarket_sector
0Microsoft_41MSFT_41USDBBG000BVPXP1UNmsft_41equity
\n", + "
" + ], + "text/plain": [ + " instrument_name client_internal currency figi exchange_code \\\n", + "0 Microsoft_41 MSFT_41 USD BBG000BVPXP1 UN \n", + "\n", + " ticker market_sector \n", + "0 msft_41 equity " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "instr_df = pd.read_csv(\"IRR_instruments_upsert.csv\")\n", + "display(instr_df)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "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": 9, + "id": "c05224e4-753c-4da0-a52f-8921ef4d023e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
successfailederrors
0100
\n", + "
" + ], + "text/plain": [ + " success failed errors\n", + "0 1 0 0" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 10, + "id": "68f12af7-42cd-474d-9a08-d0ee2985d194", + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
portfolio_codeportfolio_nameportfolio_base_currencyinstrument_idclient_internaltxn_idtxn_typetxn_trade_datetxn_settle_datetxn_unitstxn_pricetxn_consideration
0IRR-Notebook-Equity1IRR-Notebook-Equity1USDCCY_USDNaNTXN_IRRSample_1FundsIn01/03/202401/03/202440000040000
1IRR-Notebook-Equity1IRR-Notebook-Equity1USDNaNMSFT_41TXN_IRRSample_2Buy01/03/202401/03/202410040040000
\n", + "
" + ], + "text/plain": [ + " portfolio_code portfolio_name portfolio_base_currency \\\n", + "0 IRR-Notebook-Equity1 IRR-Notebook-Equity1 USD \n", + "1 IRR-Notebook-Equity1 IRR-Notebook-Equity1 USD \n", + "\n", + " instrument_id client_internal txn_id txn_type txn_trade_date \\\n", + "0 CCY_USD NaN TXN_IRRSample_1 FundsIn 01/03/2024 \n", + "1 NaN MSFT_41 TXN_IRRSample_2 Buy 01/03/2024 \n", + "\n", + " txn_settle_date txn_units txn_price txn_consideration \n", + "0 01/03/2024 40000 0 40000 \n", + "1 01/03/2024 100 400 40000 " + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_transac = pd.read_csv(\"IRR_transactions.csv\")\n", + "df_transac" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "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": 12, + "id": "f4a30187-a2ca-4dfa-b51e-b65e14bee49f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
successfailederrors
0100
\n", + "
" + ], + "text/plain": [ + " success failed errors\n", + "0 1 0 0" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = load_from_data_frame(\n", + " api_factory=api_factory,\n", + " scope=portfolio_scope,\n", + " data_frame=df_transac,\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": 13, + "id": "5b4732d8-9acf-4126-8886-3dfac7ea81f8", + "metadata": {}, + "outputs": [], + "source": [ + "recipe_code = \"IRR_RecipeCode1\"\n", + "recipe_scope = \"IRR-Examples1\"\n", + "model_name = \"SimpleStatic\"" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "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": 15, + "id": "4088901d-1d21-4370-8d44-d64bc1c62c04", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Recipe IRR_RecipeCode1 Upserted Successfully!\n" + ] + } + ], + "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": 16, + "id": "64677cb1-ca93-414b-9095-27451cf2ac77", + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ClientInternaldatepricecurrency
0MSFT_412024-03-01410USD
1MSFT_412024-03-27420USD
2MSFT_412024-04-01420USD
3MSFT_412024-04-27430USD
4MSFT_412024-05-01430USD
5MSFT_412024-05-27440USD
\n", + "
" + ], + "text/plain": [ + " ClientInternal date price currency\n", + "0 MSFT_41 2024-03-01 410 USD\n", + "1 MSFT_41 2024-03-27 420 USD\n", + "2 MSFT_41 2024-04-01 420 USD\n", + "3 MSFT_41 2024-04-27 430 USD\n", + "4 MSFT_41 2024-05-01 430 USD\n", + "5 MSFT_41 2024-05-27 440 USD" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 17, + "id": "5b85924c-22f2-4076-aa26-854cd60ef6de", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
successfailederrors
0600
\n", + "
" + ], + "text/plain": [ + " success failed errors\n", + "0 6 0 0" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "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": 18, + "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": 19, + "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": 20, + "id": "67840aee-06ae-43d9-9de5-7feaf23f46be", + "metadata": { + "scrolled": true + }, + "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/NameInstrument/default/ClientInternalValuation/PVProfitAndLoss/PortfolioInternalRateOfReturn(Window=\"MTD\")Holding/default/Units
0USDNone0.00000.38090.0000
1Microsoft_41MSFT_4144,000.00000.3809100.0000
\n", + "
" + ], + "text/plain": [ + " Instrument/default/Name Instrument/default/ClientInternal Valuation/PV \\\n", + "0 USD None 0.0000 \n", + "1 Microsoft_41 MSFT_41 44,000.0000 \n", + "\n", + " ProfitAndLoss/PortfolioInternalRateOfReturn(Window=\"MTD\") \\\n", + "0 0.3809 \n", + "1 0.3809 \n", + "\n", + " Holding/default/Units \n", + "0 0.0000 \n", + "1 100.0000 " + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 21, + "id": "76600bd9-a1ca-469d-ad5f-647192179a7e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Instrument/default/NameValuation/PV
0Microsoft_4143,000.0000
\n", + "
" + ], + "text/plain": [ + " Instrument/default/Name Valuation/PV\n", + "0 Microsoft_41 43,000.0000" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 22, + "id": "2e104f68-a231-4e85-b054-638382084cdb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-6.853952072560787e-09" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 24, + "id": "613418d8-a1d0-4e3b-bca5-553ed1f41b9b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'href': None,\n", + " 'links': [{'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/0HN5LUA79COK7:0000001C',\n", + " 'method': 'GET',\n", + " 'relation': 'RequestLogs'}],\n", + " 'value': datetime.datetime(2024, 8, 6, 8, 35, 51, 181399, tzinfo=tzlocal())}\n" + ] + } + ], + "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": 25, + "id": "a61fd2e1-00e2-4ff6-ad11-ee34d86ae8cb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'failed': {},\n", + " 'href': None,\n", + " 'links': [{'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/0HN5LRFVBP6PS:00000034',\n", + " 'method': 'GET',\n", + " 'relation': 'RequestLogs'}],\n", + " 'values': {'request_1': datetime.datetime(2024, 8, 6, 8, 35, 56, 558455, tzinfo=tzlocal()),\n", + " 'request_2': datetime.datetime(2024, 8, 6, 8, 35, 56, 558455, tzinfo=tzlocal()),\n", + " 'request_3': datetime.datetime(2024, 8, 6, 8, 35, 56, 558455, tzinfo=tzlocal()),\n", + " 'request_4': datetime.datetime(2024, 8, 6, 8, 35, 56, 558455, tzinfo=tzlocal()),\n", + " 'request_5': datetime.datetime(2024, 8, 6, 8, 35, 56, 558455, tzinfo=tzlocal()),\n", + " 'request_6': datetime.datetime(2024, 8, 6, 8, 35, 56, 558455, tzinfo=tzlocal())}}\n" + ] + } + ], + "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