Getting Started 5: From Data to Dashboards II
Build the Fixed‑Income “Curve, Stats & Positions” dashboard on the Main Sequence platform
Updated to reflect your current repository state for the Fixed Income Position Dashboard (multipage Streamlit app with curve bumps, portfolio/group selection, z‑spreads, and per‑asset drill‑downs).
What you’ll build (now aligned to your latest code)
A production‑ready, multi‑page Streamlit app that runs on the Main Sequence platform and lets you:
1) Load one or more platform portfolios and convert their weights into priced Positions.
2) Build and bump yield curves (parallel & key‑rate) and reuse the same curves across selected portfolios.
3) Compute z‑spreads from dirty prices so ΔNPV reflects only the curve move.
4) View a par‑curve chart and a paginated NPV table with base/bumped metrics per instrument.
5) Inspect data‑node dependencies and per‑asset cashflows (rebuilt from the platform’s instrument dump).
Everything below uses the Main Sequence SDK/API against objects in your tenant.
Repository layout (current)
You need to recreate this structure under your project’s dashboards/ folder:
dashboards/
└─ bond_portfolio_analysis/
├─ .streamlit/config.toml
├─ app.py
├─ settings.py
└─ pages/
├─ 01_curve_and_positions.py # “Curve, Stats & Positions”
├─ 02_data_nodes_dependencies.py # Data‑node graph (Mermaid)
└─ 99_asset_detail.py # Per‑asset JSON + cashflows
IMPORTANT Beside this - you need to bring other required folders from dashboards/ folder in repo to dashboards/ folder in your project: components/, core/, plots/, services/ to recreate same structure in your project. Best way is to clone whole repo somewhere on your computer and copy required folders to your project.
Quick‑link note:
app.pycreates buttons linking topages/01_Curve_Stats_Positionsandpages/02_Data_Nodes_Graph. Your actual files are01_curve_and_positions.pyand02_data_nodes_dependencies.py. Either rename the files to match the links or adjust the two link targets inapp.py. Streamlit will still show the pages in the sidebar either way.
Code walkthrough
Explore the key parts of each file below and print this code to your dashboard app from example repo: https://github.com/mainsequence-sdk/ExampleDashboards/tree/main/dashboards/apps/bond_portfolio_analysis
Let’s dive in!
1) Theme & app shell
.streamlit/config.tomlsets your dark theme (primary/background/text colors, font) and enables headless/XSFR protection.app.pyregisters the theme, sets the page config (page_title="Fixed Income Position Dashboard"), and provides quick links to the app pages. It also explains that all pages live underpages/and that no registry is required (native Streamlit multipage routing). This file has custom theme injection fromdashboards/core/theme.py.
2) Tell the app which prices table to read
File: settings.py
from dashboards.helpers.mock import SIMULATED_PRICES_TABLE
PRICES_TABLE_NAME=SIMULATED_PRICES_TABLE
The page 01_curve_and_positions.py calls a small bootstrap (_ensure_data_nodes()) that registers this table under the app’s data‑node registry so the services can retrieve each asset’s dirty price from the platform.
3) Page “Curve, Stats & Positions” (end‑to‑end flow)
File: pages/01_curve_and_positions.py
3.1 Sidebar: valuation date & notional
- A valuation date control (
date_selector) writes the date tost.session_stateand sets QuantLib’s evaluation date. - A portfolio notional is used to translate weights into integer units later on.
The page uses
run_page(PageConfig(...))from the platform’s Streamlit scaffold and clears any stale “render once” flags.
3.2 Optional: Build the mock portfolio in‑app
- Click “Build mock portfolios” to run
dashboards.helpers.mock.build_test_portfolio("mock_portfolio_floating_dashboard"). - This populates a demo target portfolio, two assets, and platform instrument pricing details so downstream pages and drill‑downs work.
3.3 Pick portfolios (and groups), then dedupe
- Use
sidebar_portfolio_multi_selectto search/select portfolios from the platform. - Optionally toggle “Include portfolio groups”; group selections are flattened into portfolios.
- The list is deduplicated by id before building positions.
3.4 Build positions from platform data
When the selection or notional changes, the page:
1) Invokes PortfoliosOperations(...).get_all_portfolios_as_positions(portfolio_notional=...) to build a Position per portfolio.
2) Persists to session state:
- instrument_hash_to_asset (for UID display/links),
- position_templates_mem ({portfolio_id → Position template}),
- _merged_template (all lines merged) to drive a single shared curve control,
- _active_portfolio_ids, _active_notional, and a mapping of ids → portfolio objects.
3) Calls st.rerun() to refresh the page with the new state.
3.5 Curves: one control & chart for all portfolios
st_curve_bumps(...)(fromdashboards.components.curve_bumps_and_stats) takes the merged position and the valuation date, builds base curves and bumped curves by family, and renders the par‑curve chart.- The function returns
base_curvesandbumped_curvesso we can reuse the same curves across all selected portfolios.
3.6 Per‑portfolio tabs: instantiate, z‑spreads, metrics
For each selected portfolio (one tab per id):
- Instantiate
PositionOperationsfrom the position template and set the base/bumped curves. - Compute z‑spreads from platform dirty prices so that PV(base+z) matches each asset’s price; mirror the same
zto the bumped position. - Render a paginated NPV table via
PortfoliosOperations.st_position_npv_table_paginated(...), with optional search and CSV download. The table shows base/bumped NPV per line and summary stats (carry to cutoff, etc.).
4) Page “Data Nodes — Dependencies”
File: pages/02_data_nodes_dependencies.py
- Builds a Mermaid graph from a dependency payload (currently
_mock_fetch_dependencies()in the file). - Lets users switch layout (TD/LR/BT/RL), view the raw payload, and download the
.mmdsnapshot. - To connect it to live metadata, replace the mock with a call to your platform’s TDAG/data‑catalog endpoint.
5) Page “Asset Detail”
File: pages/99_asset_detail.py
- Accepts
?id=<int>or?unique_identifier=<str>as query parameters. - Loads the
msc.Assetfrom the platform; shows the asset JSON and instrument dump. - Rebuilds the instrument with
mainsequence.instrumentsand displays cashflows, with a CSV download button.
6) Run it on the platform
1) Commit and push to the branch your project deploys from using mainsequence project open-signed-terminal <PROJECT_ID> and run:
git add .
git commit -m "Add Fixed Income Position Dashboard"
git push
4) In the sidebar pick a Valuation date, click on Build mock portfolios, and in the Portfolio groups search by
Mock Bond Portfolio with Signals Group then in dropdown select it and click "Load Selected".5) Bump the curve and inspect ΔNPV/carry; drill down into assets as needed.
7) Troubleshooting
- Quick links don’t open → Fix the slug mismatch between
app.pylinks and page filenames. - No prices for assets → Confirm
settings.PRICES_TABLE_NAMEmatches your simulator’s table and that it has recent closes. - No instrument dump → Ensure the assets’
current_pricing_detail.instrument_dumpexists (the mock builder sets this up).
Appendix A — Minimal interface sketches (copy/paste shells)
Only if you need quick stubs to unblock the app locally. In production, use the real implementations that read/write to the Main Sequence platform.
# dashboards/core/theme.py
def register_theme():
# Optional: set Plotly templates, etc. Safe no-op.
return None
# dashboards/core/ql.py
import QuantLib as ql
def qld(d):
return ql.Date(d.day, d.month, d.year)
# dashboards/core/formatters.py
def fmt_ccy(x):
try: return f"{float(x):,.2f}"
except: return "—"
# dashboards/core/data_nodes.py
class _Deps:
def __init__(self): self._m = {}
def get(self, k): return self._m[k]
def register(self, **kwargs): self._m.update(kwargs)
_deps = _Deps()
def get_app_data_nodes(): return _deps
(Build your actual Main Sequence integrations as needed.)
You now have a complete, multipage, curve‑aware fixed‑income dashboard running on the Main Sequence platform.
Push your repo, open the dashboard, and try a few curve bump scenarios!