{ "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Create a Marquee Portfolio with GS Quant\n", "\n", "The Marquee Portfolio Service provides a powerful framework for uploading portfolio positions and retrieving analytics including historical performance, factor risk exposure, ESG analytics, and more. GS Quant makes operating the suite of Portfolio Service API's intuitive and fast." ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from gs_quant.entities.entitlements import Entitlements, EntitlementBlock, User\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.position_set import PositionSet\n", "from gs_quant.markets.report import FactorRiskReport, ThematicReport, CustomAUMDataPoint\n", "from gs_quant.markets.securities import SecurityMaster, AssetIdentifier\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.target.portfolios import RiskAumSource\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "\n", "#client = 'ENTER CLIENT ID'\n", "#secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD,\n", " client_id=client,\n", " client_secret=secret\n", ")\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "source": [ "## Step 2: Create the Portfolio\n", "\n", "The first step is to create a new, empty portfolio in Marquee." ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "portfolio = Portfolio(name='My New Portfolio')\n", "portfolio.save()\n", "\n", "print(f\"Created portfolio '{portfolio.name}' with ID: {portfolio.id}\")" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "Once your portfolio has been saved to Marquee, the `PortfolioManager` class allows users to interact with their Marquee portfolios directly from GS Quant. We will be using `PortfolioManager` to update portfolio positions, entitlements, update custom AUM, and run reports." ] }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "pm = PortfolioManager(portfolio.id)" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "## Step 3: Define Portfolio Entitlements (Optional)\n", "\n", "By default, an application will have all entitlement permissions to a portfolio it makes.\n", "However, if you would like to share the portfolio with others, either Marquee users or other\n", "applications, you will need to specify them in the entitlements parameter of the portfolio.\n", "Let's walk through how we convert a list of admin and viewer emails into an `Entitlements` object:\n", "\n", "*Note: If you would like to see this portfolio on your Marquee webpage, you'll need to add your account\n", "email address into the `portfolio_admin_emails` list*" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" }, "scrolled": false }, "outputs": [], "source": [ "portfolio_admin_emails = ['LIST OF ADMIN EMAILS']\n", "portfolio_viewer_emails = ['LIST OF VIEWER EMAILS']\n", "\n", "admin_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_admin_emails))\n", "view_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_viewer_emails))\n", "\n", "entitlements = Entitlements(\n", " view=view_entitlements,\n", " admin=admin_entitlements\n", ")\n", "\n", "print(f'Entitlements:\\n{entitlements.to_dict()}')\n", "\n", "pm.set_entitlements(entitlements)\n", "\n", "print(f\"Updated entitlements for '{portfolio.name}'\")" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 4: Define Portfolio Positions\n", "\n", "Portfolio positions in Marquee are stored on a holding basis, when means you only upload positions for days where you are rebalancing your portfolio. Take the following set of positions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio_position_sets = [\n", " PositionSet.from_dicts(\n", " date=dt.date(day=3, month=5, year=2021),\n", " positions=[\n", " {\n", " 'identifier': 'AAPL UW',\n", " 'quantity': 25,\n", " 'tags': {'Analyst': 'Jane Doe'}\n", " }, {\n", " 'identifier': 'GS UN',\n", " 'quantity': 50,\n", " 'tags': {'Analyst': 'John Doe'}\n", " }],\n", " add_tags=True\n", " ),\n", " PositionSet.from_dicts(\n", " date=dt.date(day=1, month=7, year=2021),\n", " positions=[\n", " {\n", " 'identifier': 'AAPL UW',\n", " 'quantity': 26,\n", " 'tags': {'Analyst': 'Jane Doe'}\n", " }, {\n", " 'identifier': 'GS UN',\n", " 'quantity': 51,\n", " 'tags': {'Analyst': 'John Doe'}\n", " }],\n", " add_tags=True\n", " )\n", "]\n", "\n", "for pos_set in portfolio_position_sets:\n", " pos_set.resolve()\n", " if len(pos_set.unresolved_positions) > 0:\n", " print(f'The following positions on {pos_set.date} could not be resolved: {[p.identifier for p in pos_set.unresolved_positions]}')" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "If these positions were to be uploaded correctly, this portfolio would hold 50 shares of GS UN and 25 shares of AAPL UW from May 3, 2021 to June 30, 2021, and it would hold 51 shares of GS UN and 26 shares of AAPL UW from July 1, 2021 to today.\n", "\n", "#### Have your positions as a dataframe?\n", "\n", "If you have a day's positions in a dataframe with columns `identifer` (string values), `quantity` (float values),\n", "and optionally `tags` (dictionary values), you can turn them into a `PositionSet` object by using the\n", "`PositionSet.from_frame()` function:\n", "\n", "`position_set = PositionSet.from_frame(positions_df, datetime_date)`" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pm.update_positions(portfolio_position_sets)\n", "\n", "print(f\"Updated positions for '{portfolio.name}'\")" ] }, { "cell_type": "markdown", "source": [ "## Step 5: Create Factor Risk and/or Thematic Reports (Optional)\n", "\n", "By default, creating a portfolio will automatically create a corresponding performance report for it as well.\n", "If you would like to create a factor risk and/or thematic report (more documentation on reports found [here](https://developer.gs.com/p/docs/services/portfolio/programmatic-access/reports/))\n", "for it as well, run the following:" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "risk_model_id = 'RISK_MODEL_ID_HERE'\n", "benchmark = SecurityMaster.get_asset(id_value='BENCHMARK TICKER HERE', id_type=AssetIdentifier.TICKER)\n", "\n", "# Add a factor risk report\n", "risk_report = FactorRiskReport(risk_model_id=risk_model_id)\n", "risk_report.set_position_source(portfolio.id)\n", "risk_report.save()\n", "\n", "# Add an active factor risk report with a benchmark of your choice\n", "active_risk_report = FactorRiskReport(risk_model_id=risk_model_id, benchmark_id=benchmark.get_marquee_id())\n", "active_risk_report.set_position_source(portfolio.id)\n", "active_risk_report.save()\n", "\n", "# Add a thematic report\n", "thematic_report = ThematicReport()\n", "thematic_report.set_position_source(portfolio.id)\n", "thematic_report.save()\n", "\n", "print('All portfolio reports created.')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "#### Quick Tip!\n", "\n", "*Explore the different factor risk models available in Marquee in our [Data Catalog](https://marquee.gs.com/s/discover/data-services/catalog?Category=Factor+Risk+Model).*\n", "\n", "\n", "## Step 6: Schedule Reports\n", "\n", "Now, it's schedule all the portfolio reports. Once this is done and reports are completed, you can programmatically retrieve factor risk and attribution data for your portfolio.\n", "\n", "When scheduling reports, you have two options:\n", "- Backcast the report: Take the earliest date with positions in the portfolio / basket and run the report on the positions held then with a start date before the earliest position date and an end date\n", " of the earliest position date. This option is ideal for snapshot portfolios.\n", "- Do not backcast the report: Set the start date as a date that has positions in the portfolio or basket and an end date after that (best practice is to set it to T-1). In this case the\n", " report will run on positions held as of each day in the date range. This option is ideal for historical portfolios." ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "pm.schedule_reports(backcast=False)\n", "\n", "print('All portfolio reports scheduled.')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 7: Update Custom AUM/NAV (Optional)\n", "The `CustomAUMDataPoint` class is used to represent custom AUM data for a specific date. A list of them can be posted to Marquee using our initialized `PortfolioManager`. If you do not upload custom AUM data for your portfolio and change your portfolio's AUM Source to `Custom AUM`, by default the \"AUM\" (which is used for calculating risk as percent values) will be your portfolio's long exposure." ] }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "performance_report = pm.get_performance_report()\n", "performance_report.set_aum_source(RiskAumSource.Custom_AUM)\n", "custom_aum = [\n", " CustomAUMDataPoint(date=dt.date(2021, 5, 1), aum=100000),\n", " CustomAUMDataPoint(date=dt.date(2021, 7, 1), aum=200000)\n", "]\n", "performance_report.upload_custom_aum(custom_aum, clear_existing_data=False)\n", "\n", "print(f\"Custom AUM for '{portfolio.name} successfully uploaded'\")" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*\n" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.7.4" } }, "nbformat": 4, "nbformat_minor": 1 }