Assets and Categories
Start with assets before building anything else. Asset identity is the stable contract consumed by holdings, target positions, pricing details, and portfolio workflows. This chapter covers runtime setup, asset types and constants, categories, currency assets, bond assets, and asset snapshots.
For the runtime model behind these row APIs, see Core Concepts.
Schema migrations before runtime
msm.start_engine(...) is runtime attachment. It resolves finalized
MetaTable and TimeIndexMetaTable resources directly by each selected
model's SQLAlchemy table name, then binds those backend objects for row APIs.
It does not create or evolve schema.
Run admin migrations before application startup:
mainsequence migrations current --provider migrations:migration --json
mainsequence migrations upgrade --provider migrations:migration head
See Migrations for the package registry, SDK Alembic provider, upgrade flow, schema finalization, and runtime attachment lifecycle.
Runtime setup
Production code normally initializes the required market MetaTables during
application startup. A row call such as Asset.upsert(...) uses the active
runtime and then runs the operation. If the runtime is missing, or if the
runtime was initialized without the required table set, the error tells the
caller to run explicit preflight.
Explicit preflight remains available for applications that want startup-time attachment or verification:
import msm
runtime = msm.start_engine(models=["Asset"])
Runtime attachment does not take labels. Use runtime.meta_tables and
runtime.meta_table_models after bootstrap when a specific returned MetaTable
needs follow-up labeling or handling. Import DataNode classes from their owning
package modules. The preflight uses the Main Sequence logger at info level to
report each MetaTable model being attached, what context was created, and when a
cached runtime is reused.
For externally managed tables, create/migrate the tables through the admin
migration flow and use the explicit startup/bootstrap path with
management_mode="external_registered". Do not call model .register()
methods or local registration helpers from application code.
Examples that use the platform namespace mainsequence.examples set
MSM_AUTO_REGISTER_NAMESPACE before importing msm.api, then call
msm.start_engine(...) before row operations:
import os
from examples.platform.bootstrap import (
EXAMPLE_NAMESPACE_ENV,
EXAMPLE_METATABLE_NAMESPACE,
)
os.environ.setdefault(EXAMPLE_NAMESPACE_ENV, EXAMPLE_METATABLE_NAMESPACE)
import msm
msm.start_engine(models=["Asset"])
The returned runtime from explicit preflight still exposes table handles and context for lower-level repository/service internals, but examples should not pass those handles around for normal row CRUD.
Asset identity and provider rows
Use this workflow when ingesting external asset metadata:
- Resolve or normalize provider data through a service module, for example
msm.services.assets.openfigi. - Register the asset type through
msm.api.assets.AssetTypewhen the type is new to the project or namespace. Usemsm.constantsfor built-in type keys such asASSET_TYPE_BOND,ASSET_TYPE_CRYPTO,ASSET_TYPE_CURRENCY,ASSET_TYPE_CURRENCY_SPOT,ASSET_TYPE_EQUITY, andASSET_TYPE_FUTURE. - Persist canonical identity through the user-facing
msm.api.assets.Assetrow API. Row operations attach to registered MetaTables lazily. - Store timestamped asset facts through DataNode schemas in
msm.data_nodes.assets.
The package boundary is deliberate: msm.api owns user row operations,
msm.models.*Table owns SQLAlchemy schema declarations, DataNodes own
time-indexed market facts, and services own external provider integration.
from msm.data_nodes.assets import AssetSnapshot
from msm.services.assets.openfigi import (
query_by_figi,
)
from msm.constants import ASSET_TYPE_BOND
See examples/msm/assets/asset_crud_workflow.py for the asset workflow covering
OpenFIGI resolution, Asset registration, OpenFigiDetails, and
AssetSnapshot writes. The OpenFIGI helpers read the API key from the Main
Sequence secret OPEN_FIGI_API_KEY.
See examples/msm/assets/asset_type_constants.py for a small import-only example
that prints the built-in constants and AssetType.upsert(...) payloads.
Assets and categories
Asset identity is the stable contract consumed by holdings, target positions, pricing details, and portfolio workflows.
from msm.api.assets import Asset
from msm.constants import ASSET_TYPE_CRYPTO
btc = Asset.upsert(
unique_identifier="example-asset-btc",
asset_type=ASSET_TYPE_CRYPTO,
)
btc_by_identifier = Asset.get_by_unique_identifier(
unique_identifier="example-asset-btc",
)
assets_by_identifier = Asset.get_many_by_unique_identifier(
["example-asset-btc", "example-asset-eth"],
)
btc_by_uid = Asset.get_by_uid(btc.uid)
crypto_assets = Asset.filter(
unique_identifier_contains="example-asset-",
asset_type=ASSET_TYPE_CRYPTO,
limit=20,
)
# Optional cleanup for temporary custom assets only:
# Asset.delete(btc.uid)
Do not delete assets during normal setup. Use cleanup only for temporary or organization-owned custom assets. Shared public/mastered assets should remain stable so downstream workflows do not lose their canonical identity.
See examples/msm/assets/asset_crud_workflow.py for the focused workflow. It uses
explicit example-namespace bootstrap for the required AssetType, Asset, and
OpenFigiDetails MetaTables, creates temporary custom assets, resolves
BBG00FNFPQH4 through OpenFIGI, writes an AssetSnapshot frame from the returned
provider details, and lists the created assets. Set the
Main Sequence secret OPEN_FIGI_API_KEY in
www.main-sequence.app/app/main_sequence_workbench/secrets before running the
workflow. Cleanup is disabled by default.
The local FastAPI surface under apps/v1 now exposes the migrated asset and
asset-category registry routes. Keep the route layer thin and put reusable
catalog/category logic under src/msm/services. The implemented category detail
flow uses GET /api/v1/asset-category/{uid}/ plus the nested asset list route
GET /api/v1/asset/?categories__uid=<uid>. The category detail response includes
the membership-backed asset count and an assets_list.default_filters object
with the same categories__uid value, so clients can render the category member
table without inventing a second membership lookup. The same local API surface
now also exposes the simple index registry routes:
GET /api/v1/index/GET /api/v1/index/{uid}/DELETE /api/v1/index/{uid}/
When MSM_AUTO_REGISTER_NAMESPACE is set for this local API, startup now
pre-registers the full apps/v1 table set against the real project/session
data source already configured for the Main Sequence client. If the session
cannot resolve a valid DynamicTable data source, startup should fail and that
platform/data-source issue should be fixed directly.
See FastAPI v1 for the current route inventory and contract notes.
Currency assets
Single currencies and currency spot pairs are separate assets. Create or resolve
the component currency assets first, then let CurrencySpot.upsert(...) own the
spot-pair asset and CurrencySpotAssetDetailsTable write:
from msm.api.assets import Asset, CurrencySpot
from msm.constants import ASSET_TYPE_CURRENCY
USD = {"code": "USD", "currency_name": "US Dollar"}
EUR = {"code": "EUR", "currency_name": "Euro"}
usd = Asset.upsert(unique_identifier=USD["code"], asset_type=ASSET_TYPE_CURRENCY)
eur = Asset.upsert(unique_identifier=EUR["code"], asset_type=ASSET_TYPE_CURRENCY)
eur_usd = CurrencySpot.upsert(
unique_identifier="BBG0013HGRV5",
base_currency_uid=eur.uid,
quote_currency_uid=usd.uid,
)
Typed asset APIs normalize asset type keys before writing them, so "Currency"
is stored as currency, "Currency Spot" is stored as currency_spot, and
"Future" is stored as future.
See examples/msm/assets/currency_spot_workflow.py and
Currency Assets for the detailed schema and
workflow.
Bond assets
Bond setup uses reference issuers plus canonical asset rows. Create the issuer
and denomination currency first, then let Bond.upsert(...) own the bond asset
and BondAssetDetailsTable write:
import datetime as dt
from msm.api.assets import Asset, AssetType, Bond
from msm.api.issuers import Issuer
from msm.constants import (
ASSET_TYPE_BOND_DEFINITION,
ASSET_TYPE_CURRENCY,
ASSET_TYPE_CURRENCY_DEFINITION,
)
AssetType.upsert(**ASSET_TYPE_CURRENCY_DEFINITION.as_payload())
AssetType.upsert(**ASSET_TYPE_BOND_DEFINITION.as_payload())
issuer = Issuer.upsert(
unique_identifier="example-issuer",
display_name="Example Issuer",
)
usd = Asset.upsert(unique_identifier="USD", asset_type=ASSET_TYPE_CURRENCY)
bond = Bond.upsert(
unique_identifier="example-usd-bond-2031",
issuer_uid=issuer.uid,
currency_asset_uid=usd.uid,
issue_date=dt.date(2026, 5, 27),
maturity_date=dt.date(2031, 5, 27),
status="ACTIVE",
)
See examples/msm/assets/bond_workflow.py and
Bond Assets for the detailed schema and
workflow. examples/msm/assets/us_treasury_bond_workflow.py shows the same API on
a US Treasury note where CUSIP maps to canonical asset identity, FIGI maps to
provider details, and coupon/tenor fields stay outside the minimal bond detail
table.
Asset snapshots
AssetSnapshot is a DataNode, not a MetaTable. Build validated snapshot frames
or a configured node through AssetSnapshot methods. Construct the node first,
then bind rows whose payloads each carry their own time_index:
from datetime import datetime, UTC
from msm.data_nodes.assets import AssetSnapshot
snapshot_node = AssetSnapshot().set_snapshots(
{
"time_index": datetime.now(UTC),
"asset_identifier": "example-asset-btc",
"ticker": "BTC",
},
)
snapshot_frame = snapshot_node.run(debug_mode=True, force_update=True)
Markets DataNodes use the same identifier rule as MetaTables. With the default
markets namespace, logical identifiers stay bare, such as Asset and
AssetSnapshotsTS. With MSM_AUTO_REGISTER_NAMESPACE=mainsequence.examples,
the published identifiers become mainsequence.examples.Asset and
mainsequence.examples.AssetSnapshotsTS; the default DataNode hash_namespace
is also mainsequence.examples. Pass explicit identifier or hash_namespace
only when a test or experiment needs isolation.
Asset snapshot source tables have a canonical foreign key from
asset_identifier to AssetTable.unique_identifier, so the Asset MetaTable
must exist before a snapshot DataNode initializes its source table. In examples
that perform source-table initialization, run msm.start_engine(models=["Asset"])
with the intended namespace before creating the DataNode.
Before the write path persists rows, AssetSnapshot checks the backend for the
incoming (time_index, asset_identifier) tuples and fails if any tuple already
exists. Publish corrections as a new timestamped snapshot instead of overwriting
the existing row.
See examples/msm/assets/asset_crud_workflow.py for the workflow that also writes
an AssetSnapshot frame. See
Asset-Indexed DataNodes for
the detailed AssetIndexedDataNode contract and how AssetSnapshot implements
it.
When the universe itself should be a reusable platform object, create an asset category and manage memberships separately:
from msm.api.assets import Asset, AssetCategory, AssetType
from msm.constants import ASSET_TYPE_CRYPTO, ASSET_TYPE_CRYPTO_DEFINITION
AssetType.upsert(**ASSET_TYPE_CRYPTO_DEFINITION.as_payload())
btc = Asset.upsert(unique_identifier="BTC", asset_type=ASSET_TYPE_CRYPTO)
eth = Asset.upsert(unique_identifier="ETH", asset_type=ASSET_TYPE_CRYPTO)
category = AssetCategory.upsert(
unique_identifier="crypto-majors",
display_name="Crypto Majors",
)
memberships = AssetCategory.replace_memberships(
category_uid=category.uid,
asset_uids=[btc.uid, eth.uid],
)
For a step-by-step membership lifecycle, run
examples/msm/assets/asset_category_workflow.py. It creates a category, adds
assets, removes assets, and prints the category contents after each change. The
normal run leaves assets in the category unless the cleanup flag is used. Asset
examples reuse shared identifiers and FIGI constants from
examples/msm/assets/utils/reference_data.py.
Command Center asset monitor
Use the Command Center helpers when a project wants to publish ms-markets asset data into a Command Center workspace:
- Load or resolve asset rows in the project API or application layer.
- Pass already-loaded rows into
command_center.widgets.asset_monitor.build_asset_monitor_frame(...). - Return the resulting
TabularFrameResponsefrom a provider API operation such asgetAssetMonitorFrame. - Expose that operation through Adapter from API discovery.
- Bind
connection-query.datasetintomain-sequence-markets__asset-screener.seedData.
See Command Center Asset Monitor for the
full frame contract and workspace binding rules. See
examples/msm/command_center/asset_monitor_frame.py for an import-only example
that builds the canonical frame from sample asset rows.
For timestamped facts keyed to index reference rows, use the same stamped
DataNode workflow with msm.data_nodes.indices.IndexTimestampedDataNode and an
IndexDataNodeConfiguration subclass. The frame contract is
["time_index", "index_identifier"], with a canonical source-table foreign key
from index_identifier to IndexTable.unique_identifier. Keep index identity
on uid and unique_identifier; do not add legacy platform Constant-name
fields.
Next → Calendars