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",
+ " success | \n",
+ " failed | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 1 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " instrument_name | \n",
+ " client_internal | \n",
+ " currency | \n",
+ " figi | \n",
+ " exchange_code | \n",
+ " ticker | \n",
+ " market_sector | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Microsoft_41 | \n",
+ " MSFT_41 | \n",
+ " USD | \n",
+ " BBG000BVPXP1 | \n",
+ " UN | \n",
+ " msft_41 | \n",
+ " equity | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " success | \n",
+ " failed | \n",
+ " errors | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 1 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " portfolio_code | \n",
+ " portfolio_name | \n",
+ " portfolio_base_currency | \n",
+ " instrument_id | \n",
+ " client_internal | \n",
+ " txn_id | \n",
+ " txn_type | \n",
+ " txn_trade_date | \n",
+ " txn_settle_date | \n",
+ " txn_units | \n",
+ " txn_price | \n",
+ " txn_consideration | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " IRR-Notebook-Equity1 | \n",
+ " IRR-Notebook-Equity1 | \n",
+ " USD | \n",
+ " CCY_USD | \n",
+ " NaN | \n",
+ " TXN_IRRSample_1 | \n",
+ " FundsIn | \n",
+ " 01/03/2024 | \n",
+ " 01/03/2024 | \n",
+ " 40000 | \n",
+ " 0 | \n",
+ " 40000 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " IRR-Notebook-Equity1 | \n",
+ " IRR-Notebook-Equity1 | \n",
+ " USD | \n",
+ " NaN | \n",
+ " MSFT_41 | \n",
+ " TXN_IRRSample_2 | \n",
+ " Buy | \n",
+ " 01/03/2024 | \n",
+ " 01/03/2024 | \n",
+ " 100 | \n",
+ " 400 | \n",
+ " 40000 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " success | \n",
+ " failed | \n",
+ " errors | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 1 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " ClientInternal | \n",
+ " date | \n",
+ " price | \n",
+ " currency | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " MSFT_41 | \n",
+ " 2024-03-01 | \n",
+ " 410 | \n",
+ " USD | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " MSFT_41 | \n",
+ " 2024-03-27 | \n",
+ " 420 | \n",
+ " USD | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " MSFT_41 | \n",
+ " 2024-04-01 | \n",
+ " 420 | \n",
+ " USD | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " MSFT_41 | \n",
+ " 2024-04-27 | \n",
+ " 430 | \n",
+ " USD | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " MSFT_41 | \n",
+ " 2024-05-01 | \n",
+ " 430 | \n",
+ " USD | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " MSFT_41 | \n",
+ " 2024-05-27 | \n",
+ " 440 | \n",
+ " USD | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " success | \n",
+ " failed | \n",
+ " errors | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 6 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " Instrument/default/Name | \n",
+ " Instrument/default/ClientInternal | \n",
+ " Valuation/PV | \n",
+ " ProfitAndLoss/PortfolioInternalRateOfReturn(Window=\"MTD\") | \n",
+ " Holding/default/Units | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " USD | \n",
+ " None | \n",
+ " 0.0000 | \n",
+ " 0.3809 | \n",
+ " 0.0000 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " Microsoft_41 | \n",
+ " MSFT_41 | \n",
+ " 44,000.0000 | \n",
+ " 0.3809 | \n",
+ " 100.0000 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " Instrument/default/Name | \n",
+ " Valuation/PV | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Microsoft_41 | \n",
+ " 43,000.0000 | \n",
+ "
\n",
+ " \n",
+ "
\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