Portfolio¶
Portfolio is Fundcloud's shared post-simulation object. Every entry path — Simulator.run_strategy, run_weights, run_signals, run_orders, and the skfolio round-trip via from_skfolio / to_skfolio — produces one. Metrics (sharpe, max_drawdown, turnover, attribution), the full summary() bundle, and the tear-sheet renderers all read from the same object, which keeps notebook exploration and production reporting numerically identical. Population holds a set of portfolios for cross-strategy comparison; Position is the per-asset record.
fundcloud.portfolio
¶
Unified position + analytics container.
One :class:Portfolio class handles both live simulation state (via
:meth:Portfolio.apply / :meth:Portfolio.mark_to_market) and post-run
analytics (Sharpe, drawdowns, CVaR, attribution, …). :class:Population
compares several portfolios side-by-side.
Portfolio
¶
Portfolio(
*,
returns: DataFrame | Series | None = None,
weights: DataFrame | Series | None = None,
benchmark: Series | None = None,
cash: float = 0.0,
positions: dict[str, float] | None = None,
name: str | None = None,
)
Unified position + analytics container.
Source code in python/fundcloud/portfolio/portfolio.py
equity_curve
property
¶
Running equity. For analytics-mode portfolios, cumulates returns.
apply
¶
Apply a fill to live state — cash, positions, average cost, log.
Mutates the portfolio in place: subtracts notional + fee from cash, adds the signed quantity to the position, updates the volume-weighted average cost on adds, and appends to the internal trade log.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trade
|
Any
|
Anything with the duck-typed attributes |
required |
Notes
Average cost is recomputed only when the trade adds to an
existing direction (or opens a new position). Trades that close
or partially close a position leave avg_cost unchanged so
downstream reporting can compute realised P&L on the original
basis.
Bracket-order bookkeeping (when the trade's underlying
:class:~fundcloud.sim.Order carries sl_stop / tp_stop
/ tsl_stop):
- Fixed
sl_level/tp_levelre-anchor to this fill's price on every accumulating add — tightens the bracket as the position scales up. - The trailing stop is initialised on the first entry that
carries
tsl_stopand held thereafter — accumulating adds do not reset :attr:Position.tsl_pctor :attr:Position.tsl_anchor. The high-water mark continues to ratchet from the original entry's price. - All four bracket fields are cleared when the position closes
(
qty == 0).
Source code in python/fundcloud/portfolio/portfolio.py
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | |
attribution
¶
Asset-level return contribution = weights × returns (shifted).
Requires a weights frame. Uses the current-bar weight × current-bar asset return, which is the standard backward-looking decomposition.
Source code in python/fundcloud/portfolio/portfolio.py
contribution
¶
Average per-asset contribution to total return.
drawdown_details
¶
One row per drawdown episode: start / valley / recovery + durations.
See :func:fundcloud.metrics.drawdown_details for the column
definitions.
Source code in python/fundcloud/portfolio/portfolio.py
from_nav
classmethod
¶
from_nav(
nav: Series | DataFrame,
*,
distributions: Series | None = None,
capital_flows: Series | None = None,
method: ReturnMethod = "total_return",
trades: DataFrame | None = None,
positions: DataFrame | None = None,
benchmark: Series | None = None,
name: str | None = None,
) -> Portfolio
Analytics-mode Portfolio built from a NAV series.
Return computation is delegated to
:func:fundcloud.metrics.returns_from_nav — see there for the
four-method menu. The default (total_return on per-share
NAV with distributions added back) matches how public funds
report performance: injections and withdrawals are
NAV-per-share-invariant, and only DISTRIBUTION flows need
a per-share add-back.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nav
|
Series | DataFrame
|
NAV timeseries. A :class: |
required |
distributions
|
Series | None
|
Forwarded to :func: |
None
|
capital_flows
|
Series | None
|
Forwarded to :func: |
None
|
method
|
Series | None
|
Forwarded to :func: |
None
|
trades
|
DataFrame | None
|
Stashed on the returned Portfolio as |
None
|
positions
|
DataFrame | None
|
Stashed on the returned Portfolio as |
None
|
benchmark
|
Series | None
|
Forwarded to :meth: |
None
|
name
|
Series | None
|
Forwarded to :meth: |
None
|
Returns:
| Type | Description |
|---|---|
Portfolio
|
Analytics-mode portfolio with |
Source code in python/fundcloud/portfolio/portfolio.py
from_skfolio
classmethod
¶
Lift a skfolio Portfolio into a Fundcloud Portfolio.
Copies the returns series and the (per-period) weight vector if one is exposed. Compatible with skfolio >= 0.6.
Source code in python/fundcloud/portfolio/portfolio.py
mark_to_market
¶
Compute and record equity at timestamp ts.
Walks every open position, marks it at the current bar's price
(with fallbacks for missing quotes — see Notes), sums into cash,
appends the equity snapshot and resulting weights to the
portfolio's history. The simulator calls this once per bar
after :meth:apply-ing any fills.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prices
|
Series
|
Asset → price at this bar. May contain |
required |
ts
|
Timestamp
|
Bar timestamp; used as the index value when recording the snapshot. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Total equity at |
Notes
Missing-price fallback chain (in order): prices[asset] →
the last finite price seen for asset (cached across calls)
→ the position's avg_cost. If none is positive and finite,
the position contributes zero to equity for this bar.
Cash-only positions (qty == 0) are skipped.
Source code in python/fundcloud/portfolio/portfolio.py
metrics
¶
metrics(
*,
benchmark: Series | None = None,
risk_free: float | None = None,
periods_per_year: int | None = None,
cvar_alpha: float = 0.95,
) -> pd.Series
Full portfolio-metrics bundle.
Delegates to :func:fundcloud.metrics.metrics. When this Portfolio
was constructed with benchmark=, that benchmark is used by
default; pass an explicit benchmark= to override.
Source code in python/fundcloud/portfolio/portfolio.py
period_returns
¶
period_returns(
*,
benchmark: Series | None = None,
periods_per_year: int | None = None,
) -> pd.Series | pd.DataFrame
MTD / 3M / 6M / YTD / 1Y / 3Y / 5Y / 10Y / All-time bundle.
When a benchmark is not passed and :attr:benchmark was set on
construction, it's used as the default. See
:func:fundcloud.metrics.period_returns.
Source code in python/fundcloud/portfolio/portfolio.py
position
¶
Return the live :class:Position for asset (creating one if missing).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
asset
|
str
|
Asset ticker. |
required |
Returns:
| Type | Description |
|---|---|
Position
|
The mutable :class: |
Source code in python/fundcloud/portfolio/portfolio.py
rename
¶
Rename in place and return self for chaining.
Renames the underlying returns Series too when present, so
downstream concatenations into a panel pick up the new name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
New label. |
required |
Returns:
| Type | Description |
|---|---|
Portfolio
|
|
Source code in python/fundcloud/portfolio/portfolio.py
runup_details
¶
One row per runup (rally) episode between drawdowns.
See :func:fundcloud.metrics.runup_details for the column
definitions.
snapshot
¶
Freeze live state into an analytics-mode copy.
Builds returns from the equity curve and weights from
the recorded weights history, then detaches live state so the
returned instance behaves immutably for analytics. Used by
:class:~fundcloud.sim.Simulator to produce the
:class:~fundcloud.sim.SimResult.portfolio field.
Returns:
| Type | Description |
|---|---|
Portfolio
|
Analytics-mode copy with |
Source code in python/fundcloud/portfolio/portfolio.py
summary
¶
summary(
*,
risk_free: float | None = None,
periods_per_year: int | None = None,
cvar_alpha: float = 0.95,
) -> pd.Series
Single-column summary of key metrics (rows = metric names).
Compact 11-metric view. For the full ~55-metric bundle use
:meth:metrics.
Source code in python/fundcloud/portfolio/portfolio.py
to_skfolio
¶
Build a skfolio Portfolio mirror of this object.
Requires the [pf] extra. The resulting object is an instance of
:class:skfolio.Portfolio.
Source code in python/fundcloud/portfolio/portfolio.py
turnover
¶
Average one-way turnover across rebalance boundaries.
Returns 0.0 when weights are constant or unknown.
Source code in python/fundcloud/portfolio/portfolio.py
worst_drawdowns
¶
Top-top drawdown episodes, display-formatted.
Columns: Started / Recovered / Drawdown / Days.
Episodes are sorted by depth (worst first).
Source code in python/fundcloud/portfolio/portfolio.py
worst_runups
¶
Top-top runup episodes, display-formatted.
Columns: Started / Peaked / Runup / Days.
Episodes are sorted by magnitude (biggest first).
Source code in python/fundcloud/portfolio/portfolio.py
yearly_returns
¶
End-of-year returns.
Returns a :class:pd.Series when no benchmark is available, or a
two-column :class:pd.DataFrame (benchmark, strategy)
when one is supplied (or set on construction).
Source code in python/fundcloud/portfolio/portfolio.py
Population
¶
A named bag of :class:Portfolio objects.
Source code in python/fundcloud/portfolio/population.py
composition
¶
Latest weights per portfolio, as rows-per-portfolio × asset columns.
Source code in python/fundcloud/portfolio/population.py
cumulative_returns
¶
Wide frame of cumulative (compounded) returns per portfolio.
Source code in python/fundcloud/portfolio/population.py
summary
¶
summary(
*,
risk_free: float | None = None,
periods_per_year: int | None = None,
cvar_alpha: float = 0.95,
) -> pd.DataFrame
Metric-by-portfolio comparison table (rows = metrics, cols = portfolios).
Source code in python/fundcloud/portfolio/population.py
Position
dataclass
¶
Position(
qty: float = 0.0,
avg_cost: float = 0.0,
sl_level: float | None = None,
tp_level: float | None = None,
tsl_pct: float | None = None,
tsl_anchor: float | None = None,
)
Live position for a single asset.
Attributes:
| Name | Type | Description |
|---|---|---|
qty |
float
|
Signed share count. Positive for long, negative for short, zero for closed. |
avg_cost |
float
|
Volume-weighted average cost per share. Updated only when adding
to an existing direction (or opening a new one); closes leave
|
sl_level |
float | None
|
Absolute stop-loss price for this position, or |
tp_level |
float | None
|
Absolute take-profit price for this position, or |
tsl_pct |
float | None
|
Trailing-stop fraction in |
tsl_anchor |
float | None
|
Running high-water mark for the trailing stop (long: peak price seen since the first entry, ratchets up only; short: trough price, ratchets down only). Initially the first entry's fill price. Updated by the simulator's intra-bar exit check via a two-step ratchet around the trigger:
Splitting the ratchet means a single wide-range bar can't tighten the level mid-bar to something the open never traded against — the trigger uses the level that was in force when the bar started. Accumulating entries do not reset the anchor; the trail tracks the high-water mark from the original entry. Cleared on close. |