Getting Started 5: From Data to Dashboards I
Introduction
Once your data is unified, you can start doing more interesting things with it. Let’s build your first dashboard.
To help you build and deploy dashboards quickly, the Main Sequence platform integrates Streamlit, an open‑source Python library for interactive apps.
Streamlit on the Main Sequence platform lets you turn data into interactive tools. Coupled with your project’s compute engine, you can ship a production‑ready dashboard in minutes.
In this example, we’ll build a simple app to stress‑test a fixed‑income portfolio against movements in the benchmark yield curve.
We’ll use the mainsequence.instrument library, which is a template wrapper you can use to integrate any instrument‑pricing engine. For high‑quality pricing, we wrap the excellent QuantLib project: https://www.quantlib.org/.
QuantLib is a free, open‑source library for modeling, trading, and risk management. It’s written in C++ and exported to languages like C#, Java, Python, and R.
We’ll use mainsequence.instrument to price a portfolio of floating‑ and fixed‑rate bonds and use QuantLib to estimate KPIs such as expected carry and mark‑to‑market impact.
If you want to see the end result, check the example repository: https://github.com/mainsequence-sdk/ExampleDashboards. We still recommend that you continue building in your isolated tutorial project.
Building a Dashboard
For the platform to detect your dashboards, place them inside the dashboards/ folder in your repo, and name the file that initializes the app app.py. The platform will discover and deploy it automatically. You already may find an example dashboard in your tutorial project under dashboards/sample_app/app.py. So you can create a new folder for this tutorial dashboard: dashboards/tutorial/ and add your app.py file there.
Interest‑Rate Portfolio Exposure Example
We’ll build a dashboard that shows how changes in the yield curve affect a portfolio. It will include:
1) A search input to look up the portfolio. 2) Controls to choose how the yield curve should shift. 3) A chart comparing the original vs. shifted curve. 4) A table showing overall and per‑instrument impact.
Pre‑work Making A Portfolio
We don’t yet have portfolios in the platform, so let’s create one. Any portfolio in Main Sequence has the following properties (see the examples under Markets for more context: https://github.com/mainsequence-sdk/mainsequence-sdk/tree/main/examples/markets).
class PortfolioMixin:
id: Optional[int] = None
is_active: bool = False
data_node_update: Optional['DataNodeUpdate']
signal_data_node_update: Optional['DataNodeUpdate']
follow_account_rebalance: bool = False
comparable_portfolios: Optional[List[int]] = None
backtest_table_price_column_name: Optional[str] = Field(None, max_length=20)
tags: Optional[List['PortfolioTags']] = None
calendar: Optional['Calendar']
index_asset: PortfolioIndexAsset
The most important fields are data_node_update and signal_data_node_update. On Main Sequence, a portfolio is typically composed of a signal (which generates weights) and a portfolio process (which represents the historical performance and configuration). For instance, a market‑cap strategy might compute weights daily, but the portfolio may only rebalance quarterly.
For this tutorial dashboard, we’ll build a mock portfolio with no signal—just a data_node_update.
You can find the code under dashboards/helpers/mock.py:
import mainsequence.client as msc
import mainsequence.instruments as msi
import pytz
from mainsequence.virtualfundbuilder.data_nodes import PortfolioFromDF
from mainsequence.virtualfundbuilder.portfolio_interface import PortfolioInterface
import pandas as pd
import json
import datetime
import QuantLib as ql
UTC = pytz.utc
SECURITY_TYPE_MOCK="MOCK_ASSET"
SIMULATED_PRICES_TABLE="simulated_daily_closes_tutorial"
TRANSLATION_TABLE_IDENTIFIER = "prices_translation_table_1d"
class TestFixedIncomePortfolio(PortfolioFromDF):
def get_portfolio_df(self):
time_idx = datetime.datetime.now()
time_idx = datetime.datetime(time_idx.year, time_idx.month, time_idx.day, time_idx.hour,
time_idx.minute, tzinfo=pytz.utc, )
unique_identifiers = ["TEST_FLOATING_BOND_UST","TEST_FIXED_BOND_USD"]
existing_assets = msc.Asset.query(unique_identifier__in=unique_identifiers)
existing_uids={a.unique_identifier:a for a in existing_assets}
for uid in unique_identifiers:
build_uid=False
if uid not in existing_uids.keys():
build_uid=True
else:
if existing_uids[uid].current_pricing_detail is None:
build_uid=True
if build_uid:
common_kwargs = {
"face_value": 100,
"coupon_frequency": ql.Period(6, ql.Months),
"day_count": ql.Actual365Fixed(),
"calendar": ql.UnitedStates(ql.UnitedStates.GovernmentBond),
"business_day_convention": ql.Unadjusted,
"settlement_days": 0,
"maturity_date" : time_idx.date() + datetime.timedelta(days=365 * 10),
"issue_date": time_idx.date()
}
# --- Conditionally create the bond, adding only the specific arguments ---
if "FLOATING" in uid:
bond = msi.FloatingRateBond(
**common_kwargs, # Unpack the common arguments
floating_rate_index_name="UST",
)
else: # Implies a FixedRateBond
bond = msi.FixedRateBond(
**common_kwargs, # Unpack the common arguments
coupon_rate=0.05
)
snapshot = {
"name": uid,
"ticker": uid
}
payload_item = {
"unique_identifier": uid,
"snapshot": snapshot,
}
assets = msc.Asset.batch_get_or_register_custom_assets([payload_item])
asset=assets[0]
#registed the instrument pricing details
asset.add_instrument_pricing_details_from_ms_instrument(
instrument=bond,pricing_details_date=time_idx
)
# ----- build dict-valued columns -----
keys = unique_identifiers
n = len(keys)
# random weights that sum to 1
import numpy as np
w = np.random.rand(n)
w = w / w.sum()
weights_dict = json.dumps({k: float(v) for k, v in zip(keys, w)})
# everything else set to 1 per asset
ones_dict = json.dumps({k: 1 for k in keys})
# Map logical fields to actual DF columns
col_weights_current = "rebalance_weights" # "weights_current"
col_price_current = "rebalance_price" # "price_current"
col_vol_current = "volume" # "volume_current"
col_weights_before = "weights_at_last_rebalance" # "weights_before"
col_price_before = "price_at_last_rebalance" # "price_before"
col_vol_before = "volume_at_last_rebalance" # "volume_before"
row = {
"time_index": time_idx,
"close": 1,
"return": 0,
"last_rebalance_date": time_idx.timestamp(),
col_weights_current: weights_dict,
col_weights_before: weights_dict, # same as current
col_price_current: ones_dict,
col_price_before: ones_dict,
col_vol_current: ones_dict,
col_vol_before: ones_dict,
}
# one-row DataFrame
portoflio_df = pd.DataFrame([row])
portoflio_df = portoflio_df.set_index("time_index")
if self.update_statistics.max_time_index_value is not None:
portoflio_df = portoflio_df[portoflio_df.index > self.update_statistics.max_time_index_value]
return portoflio_df
def build_test_portfolio(portfolio_name:str):
node = TestFixedIncomePortfolio(
portfolio_name=portfolio_name,
calendar_name="24/7",
target_portfolio_about="Test"
)
PortfolioInterface.build_and_run_portfolio_from_df(
portfolio_node=node,
add_portfolio_to_markets_backend=True,
)
if __name__ == "__main__":
portfolio_name="TestFixedIncomePortfolio"
build_test_portfolio(portfolio_name=portfolio_name)
Key pieces to notice:
assets = msc.Asset.batch_get_or_register_custom_assets([payload_item])
This ensures the custom assets you want are registered on the platform.
asset.add_instrument_pricing_details_from_ms_instrument(
instrument=bond, pricing_details_date=time_idx
)
This attaches instrument pricing details to the asset.
node = TestFixedIncomePortfolio(
portfolio_name=portfolio_name,
calendar_name="24/7",
target_portfolio_about="Test",
)
PortfolioInterface.build_and_run_portfolio_from_df(
portfolio_node=node,
add_portfolio_to_markets_backend=True
)
Here we use the PortfolioInterface to build and run the portfolio. This differs slightly from running a plain DataNode: the interface populates portfolio‑specific objects in the platform (for example, creating a PortfolioIndexAsset linked to this portfolio).
Now you can run this script to create the portfolio and assets in the platform. Make sure you have correct environment variables set to connect to your tutorial project’s platform instance. Open .env file and look on values inside to run next commands in your terminal:
(Windows)
$env:MAINSEQUENCE_TOKEN="your_api_key"
$env:TDAG_ENDPOINT="your_tdag_endpoint"
$env:VFB_PROJECT_PATH="your_vfb_project_path"
.\.venv\Scripts\Activate
python .\dashboards\helpers\mock.py
export MAINSEQUENCE_TOKEN="your_api_key"
export TDAG_ENDPOINT="your_tdag_endpoint"
export VFB_PROJECT_PATH="your_vfb_project_path"
source .venv/bin/activate
python dashboards/helpers/mock.py
After running the node through the PortfolioInterface, you’ll see a few things in the platform:
1) Target Portfolios:
https://main-sequence.app/target-portfolios/ — you should see the new portfolio with the name you provided TestFixedIncomePortfolio.
2) Tables (portfolio details stored by the data node): https://main-sequence.app/dynamic-table-metadatas/?search=testfixed&storage_hash=&identifier=
3) Assets (the two newly created assets): https://main-sequence.app/asset/?search=TEST_&unique_identifier=&figi=&security_type=&security_market_sector=&ticker= Click either asset to explore the current pricing details created from the snapshot.
Pre‑work Simulating Asset Prices Closes,
For our dashboards we want also to include the latest price for each asset. We will need a data_node containing closing prices. You can reuse your price simulator from the previoys tutorial
Now you have pricing details for these assets that you can use to rebuild the instruments with QuantLib—unlocking deeper analysis and richer dashboards.
With the platform hydrated (portfolio + assets), you’re ready to move on and build the dashboard UI.