Implementation Patterns
This page focuses on the practical patterns that matter when you build or extend VFB code.
It covers:
- writing a new signal strategy
- writing a new rebalance strategy
- importing a portfolio from an external DataFrame
- syncing portfolios to the Markets backend
- keeping strategy configs serializable
Authoring a new signal strategy
Most signal strategies inherit:
WeightsBaseDataNode
Your class needs to do three things well:
- expose or resolve its asset universe
- implement
maximum_forward_fill() - return the canonical
signal_weightschema
Minimal template
from datetime import timedelta
import pandas as pd
from mainsequence.tdag import DataNode, DataNodeConfiguration
from mainsequence.virtualfundbuilder.resource_factory.signal_factory import WeightsBase
from mainsequence.virtualfundbuilder.utils import TIMEDELTA
class MySignalConfig(DataNodeConfiguration):
my_param: int = 10
class MySignal(WeightsBase, DataNode):
def __init__(self, config: MySignalConfig):
self.my_param = config.my_param
super().__init__(config=config)
def get_asset_list(self):
...
def maximum_forward_fill(self):
return timedelta(days=1) - TIMEDELTA
def update(self) -> pd.DataFrame:
# return index ["time_index", "unique_identifier"]
# with a column named "signal_weight"
...
Practical rules
- use UTC-aware timestamps
- make sure
unique_identifiermatches the asset master - do not invent a custom output column name
- keep the forward-fill window economically meaningful
Authoring a new rebalance strategy
Rebalance strategies are model objects, not DataNodes.
They inherit:
RebalanceStrategyBase
Their job is to take a wide matrix of signal weights and a price table and return the wide rebalance schema VFB expects.
Minimal template
import pandas as pd
from mainsequence.virtualfundbuilder.enums import PriceTypeNames
from mainsequence.virtualfundbuilder.resource_factory.rebalance_factory import RebalanceStrategyBase
class MyRebalancer(RebalanceStrategyBase):
def apply_rebalance_logic(
self,
last_rebalance_weights: pd.DataFrame | None,
start_date,
end_date,
signal_weights: pd.DataFrame,
prices_df: pd.DataFrame,
price_type: PriceTypeNames,
) -> pd.DataFrame:
...
Practical rules
- start by copying
ImmediateSignalif you need a new rebalance style - return the full required wide schema, not a partial subset
- keep calendar assumptions explicit
Choosing PortfolioStrategy vs PortfolioFromDF
This is one of the most important design decisions in VFB.
Use PortfolioStrategy when
- you want VFB to compute the portfolio
- you have a signal and a price pipeline
- you want the full rebalance logic inside the SDK
Use PortfolioFromDF when
- the portfolio path already exists
- another system computed
closeandreturn - you want to ingest and sync the series without recreating the full strategy logic
PortfolioFromDF in practice
To use PortfolioFromDF, subclass it and implement:
def get_portfolio_df(self) -> pd.DataFrame:
...
The returned DataFrame must match either the weights schema or the positions schema expected by VFB.
Minimal example
import pandas as pd
from mainsequence.virtualfundbuilder.portfolio_nodes import PortfolioFromDF
class MyExternalPortfolio(PortfolioFromDF):
def get_portfolio_df(self) -> pd.DataFrame:
idx = pd.to_datetime(
["2025-01-31 23:59:59+00:00", "2025-02-28 23:59:59+00:00"]
)
return pd.DataFrame(
index=idx,
data={
"close": [100.0, 101.5],
"return": [0.0, 0.015],
"last_rebalance_date": ["2025-01-31", "2025-02-28"],
"rebalance_weights": [{"BTC": 0.6}, {"BTC": 0.6}],
"rebalance_price": [{"BTC": 42000}, {"BTC": 43000}],
"volume": [{"BTC": 0}, {"BTC": 0}],
"weights_at_last_rebalance": [{"BTC": 0.6}, {"BTC": 0.6}],
"price_at_last_rebalance": [{"BTC": 42000}, {"BTC": 43000}],
"volume_at_last_rebalance": [{"BTC": 0}, {"BTC": 0}],
},
).rename_axis("time_index")
What VFB does for you
PortfolioFromDF will normalize dict-like metadata columns into canonical JSON strings.
That means you can pass:
- Python dicts
- JSON strings
- Python-literal dict strings
and VFB will standardize them before storage.
Backend sync behavior
VFB can sync a computed or imported portfolio into the Markets backend.
This happens when you run:
node.run(add_portfolio_to_markets_backend=True)
What VFB creates or updates
Depending on the case, VFB can create or patch:
- a
Portfolio - a
PortfolioIndexAsset
What metadata it carries
For PortfolioStrategy, backend sync can include:
- the portfolio name
- the rebalance calendar
- tags
- a description
- signal strategy name and explanation
- rebalance strategy name
Why this linkage matters
VFB links the backend portfolio to the DataNodeUpdate id.
That is what lets the backend trace which exact computation produced the portfolio.
Practical rule:
- prefer patching an existing synced portfolio rather than deleting and recreating it unnecessarily
Serialization matters because strategies are injected as instances
VFB chooses direct injection for signal and rebalance strategies.
That is good for clarity, but it creates a configuration challenge:
- raw Python objects are not a good portable config format
Current VFB approach
- rebalance strategies serialize naturally because they are Pydantic models
- signal strategies are serialized through
build_configuration_json_schema()
What this means for authors
If you want your signal strategy to serialize well:
- use a real typed constructor signature
- avoid hiding everything behind
**kwargs - use docstrings that describe arguments clearly
- prefer Pydantic models for nested config
That improves both schema quality and tooling quality.
Common mistakes
Returning the wrong signal schema
This is still the most common custom-strategy mistake.
Using PortfolioFromDF as if it were a generic free-form importer
It still has a contract. The metadata columns must exist and must be meaningful.
Confusing backend sync with portfolio computation
Backend sync publishes the portfolio object. It does not replace the need to compute the time series correctly.
Hiding too much in untyped constructors
That makes strategy serialization and tooling much worse.
Related Reading
Next: Examples