Accounts¶
The fundcloud.accounts package wraps account-level data sources —
historical NAV, positions, trades, and capital flows from platforms
like FundCloud and Interactive Brokers. Every provider satisfies the
same AccountProvider
protocol, so the analysis surface is identical regardless of source.
For task-first walkthroughs, start with Analysing a FundCloud
fund or Analysing an Interactive
Brokers account.
Provider protocol¶
fundcloud.accounts._base.AccountProvider
¶
Bases: Protocol
Unified protocol for account-level data providers.
Concrete implementations: :class:fundcloud.accounts.FundCloud
and :class:fundcloud.accounts.IB. Future providers (e.g., Plaid)
follow the same contract.
Every method returns a :class:pd.DataFrame (or :class:pd.Series
where natural) — typed entities stay in docstrings as documented
schemas, not in code, to preserve the ecosystem feel of the rest
of the library.
capital_flows
¶
capital_flows(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
) -> pd.DataFrame
Capital flow events (injections, withdrawals, distributions).
DatetimeIndex (flow_date). Columns: flow_type
(INJECTION / WITHDRAWAL / DISTRIBUTION), amount
(always positive — direction is encoded in flow_type, not in
sign), currency, account_id, notes.
Source code in python/fundcloud/accounts/_base.py
list_accounts
¶
One row per linked account, optionally filtered to one fund.
Columns (minimum): account_id, account_name, fund_id,
fund_name, currency, external_account_id (broker-side
id, when available), latest_nav, latest_aum.
Source code in python/fundcloud/accounts/_base.py
list_funds
¶
One row per fund visible to this credential.
Columns (minimum): fund_id, name, short_name
(where available), currency, inception_date, status,
aum, total_shares. Extra provider-specific fields may
appear in a trailing info column.
Source code in python/fundcloud/accounts/_base.py
nav
¶
nav(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
adjust_for_flows: bool = True,
) -> pd.DataFrame
Historical NAV timeseries.
DatetimeIndex. Columns: nav (per-share), aum (total),
shares, daily_return (if reported by the provider).
When account_id=None the provider returns the fund-level
aggregate; when set, a single-account curve.
adjust_for_flows=True (default) requests a flow-smoothed
NAV — implementation-defined per provider (server-side query
flag for FundCloud; client-side fallback for IB / Plaid
when those land). Pass False for the raw, unadjusted
NAV series.
Source code in python/fundcloud/accounts/_base.py
positions
¶
positions(
fund_id: str | None = None,
*,
account_id: str | None = None,
asof: Timestamp | str | None = None,
) -> pd.DataFrame
Current open positions.
Columns: symbol, name, asset_type, quantity,
avg_cost, current_price, market_value, currency,
weight, unrealized_pnl, unrealized_pnl_percent,
account_id.
Source code in python/fundcloud/accounts/_base.py
to_portfolio
¶
to_portfolio(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
basis: Basis = "nav_per_share",
method: ReturnMethod = "total_return",
benchmark: Series | None = None,
name: str | None = None,
) -> Portfolio
Fetch NAV (+ flows) and return a ready-to-analyse Portfolio.
basis + method pick the return-computation convention:
basis='nav_per_share'+method='total_return'(default): per-share NAV withDISTRIBUTIONflows added back; injections and withdrawals are ignored. Matches how funds report investor return.basis='aum'+method='modified_dietz'(or'daily_twr'): AUM TWR, all flow types signed and aggregated.
Delegates return computation to
:func:fundcloud.metrics.returns_from_nav.
Source code in python/fundcloud/accounts/_base.py
trades
¶
trades(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
) -> pd.DataFrame
Executed trades, one row per fill.
DatetimeIndex (trade_date). Columns: symbol, side,
quantity, price, amount, currency, fee,
broker, status, account_id.
Source code in python/fundcloud/accounts/_base.py
fundcloud.accounts._base.BaseAccountProvider
¶
Bases: ABC
Default implementations shared by every concrete provider.
Subclasses must implement all six fact methods
(:meth:list_funds, :meth:list_accounts, :meth:nav,
:meth:capital_flows, :meth:positions, :meth:trades).
:meth:to_portfolio is provided here and composes them with
:meth:Portfolio.from_nav using the correct sign convention for
each basis.
_display_name
¶
Default Portfolio name for to_portfolio; subclasses can override.
Source code in python/fundcloud/accounts/_base.py
to_portfolio
¶
to_portfolio(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
basis: Basis = "nav_per_share",
method: ReturnMethod = "total_return",
benchmark: Series | None = None,
name: str | None = None,
) -> Portfolio
Fetch NAV (+ flows) and build a Portfolio.
See :class:AccountProvider.to_portfolio for the parameter
semantics. The implementation:
- Fetches NAV via :meth:
nav. - Fetches capital flows via :meth:
capital_flows(skipped whenmethod='none'). - Converts flows to the shape expected by
:func:
fundcloud.metrics.returns_from_navfor the chosenbasis(per-share distributions fornav_per_share; signed AUM flows foraum). - Delegates to :meth:
Portfolio.from_nav.
Source code in python/fundcloud/accounts/_base.py
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 | |
FundCloud provider¶
fundcloud.accounts.fundcloud.FundCloud
¶
FundCloud(
fund_id: str | None = None,
*,
api_key: str | None = None,
base_url: str = FUNDCLOUD_BASE_URL,
timeout: float = 30.0,
)
Bases: BaseAccountProvider
NAV / positions / trades / capital-flows source for FundCloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fund_id
|
str | None
|
Default fund id to use when callers don't pass one. Convenient
for the single-fund case. If omitted, each call that needs a
fund id auto-resolves: an explicit |
None
|
api_key
|
str | None
|
Falls back to the |
None
|
base_url
|
str
|
Override the API base URL (useful in tests). |
FUNDCLOUD_BASE_URL
|
timeout
|
float
|
Per-request timeout in seconds. |
30.0
|
Notes
Every method accepts fund_id and account_id as keywords. With
account_id=None the provider returns the fund-level aggregate;
with account_id set it drills into a single linked account. You
can pass account_id without fund_id — the provider resolves
the parent fund automatically (lazy, cached for the provider's
lifetime).
:meth:nav requests server-side flow-adjusted NAV by default
(adjust_for_flows=True); :meth:to_portfolio always opts out
and applies the canonical client-side TWR in
:func:fundcloud.metrics.returns_from_nav, which keeps results
comparable across providers (IB, Plaid) that don't offer the same
server-side flag.
Source code in python/fundcloud/accounts/fundcloud.py
_account_to_fund_map
¶
Lazy-built account_id → fund_id lookup, cached for life of
the provider. First call iterates :meth:list_accounts across
every visible fund; subsequent calls hit the cache.
Source code in python/fundcloud/accounts/fundcloud.py
_display_name
¶
Use fund + account name when available; fall back to ids.
Source code in python/fundcloud/accounts/fundcloud.py
_resolve_fund
¶
Resolve fund_id via explicit arg → constructor default →
account_id lookup → single-fund auto-pick.
When account_id is supplied without an explicit fund_id,
we look up the parent fund via :meth:_account_to_fund_map (which
lazily fetches and caches :meth:list_accounts). This lets users
say src.nav(account_id=X) directly even with multiple funds
visible — no need to pass fund_id= for each call.
Source code in python/fundcloud/accounts/fundcloud.py
capital_flows
¶
capital_flows(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
) -> pd.DataFrame
Capital flow events — amount is positive; direction is in flow_type.
start defaults to one year before end (today − 1 year
when end is also None).
Source code in python/fundcloud/accounts/fundcloud.py
list_accounts
¶
Linked accounts under fund_id (or all funds if None).
Discovered via NAVEntry.account_breakdown on one recent
aggregated NAV entry per fund — the FundCloud public API has
no dedicated /accounts endpoint.
Source code in python/fundcloud/accounts/fundcloud.py
list_funds
¶
All funds visible to this credential, as a DataFrame.
Source code in python/fundcloud/accounts/fundcloud.py
nav
¶
nav(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
adjust_for_flows: bool = True,
) -> pd.DataFrame
Historical NAV, fund-aggregated (default) or per-account.
start defaults to one year before end (or today − 1 year
when end is also None) — same convention as the network
market-data backends. Pass start= explicitly for longer
history. adjust_for_flows=True (default) requests the
server-side flow-smoothed NAV; pass False for raw values.
Both modes always use aggregation=daily (one row per date),
which is the only mode compatible with the API's
adjust_for_flows=true query.
Source code in python/fundcloud/accounts/fundcloud.py
positions
¶
positions(
fund_id: str | None = None,
*,
account_id: str | None = None,
asof: Timestamp | str | None = None,
) -> pd.DataFrame
Current open positions.
Source code in python/fundcloud/accounts/fundcloud.py
trades
¶
trades(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
) -> pd.DataFrame
Executed trades, one row per fill.
start defaults to one year before end (today − 1 year
when end is also None).
Source code in python/fundcloud/accounts/fundcloud.py
Interactive Brokers provider¶
fundcloud.accounts.ib.IB
¶
IB(
path: str | Path | None = None,
*,
files: Sequence[str | Path] | None = None,
text: str | None = None,
account_id: str | None = None,
)
Bases: BaseAccountProvider
NAV / capital-flow source backed by an IB Flex Query CSV.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path | None
|
Filesystem path to a single Flex Query CSV. Mutually exclusive
with |
None
|
files
|
Sequence[str | Path] | None
|
A sequence of paths to concatenate (e.g., one file per year, or
per sub-account). Each file is parsed independently and the
per-section frames are concatenated. Mutually exclusive with
|
None
|
text
|
str | None
|
Inline CSV content (useful in tests, notebooks, or when piping
from another tool). Mutually exclusive with |
None
|
account_id
|
str | None
|
Default |
None
|
Notes
IB has no concept of "fund" — a Flex Query CSV is keyed by
ClientAccountID directly. We surface the same id as both
fund_id and account_id on the protocol surface, so single-
account users don't need to think about the distinction
(IB("export.csv").to_portfolio() works zero-arg).
Brokerage accounts also have no shares_outstanding, so the
"NAV-per-share + total-return" path FundCloud defaults to is
inappropriate for IB. The :meth:to_portfolio default flips to
basis="aum" + method="modified_dietz" — the GIPS-standard
AUM TWR that's the natural analogue of investor return for a
brokerage account.
The :meth:positions and :meth:trades methods return empty
frames: the Cash Transactions section is event data, not trades,
and the NAV section gives only asset-class subtotals. To populate
them, configure separate Open Positions / Trades Flex sections in
Interactive Brokers.
Source code in python/fundcloud/accounts/ib.py
_resolve_account
¶
Resolve which ClientAccountID to operate on.
Priority: explicit account_id → explicit fund_id →
constructor default → unique account in the parsed CSV.
Raises :class:AmbiguousError if none are set and the CSV
contains multiple accounts.
Source code in python/fundcloud/accounts/ib.py
_signed_base_flows
¶
Return signed base-currency flow per NAV date for one account.
Used by the synthetic adjust_for_flows=True path of
:meth:nav. Flows after the last NAV date or before the first
are dropped — they cannot be applied within the visible NAV
window.
Source code in python/fundcloud/accounts/ib.py
capital_flows
¶
capital_flows(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
) -> pd.DataFrame
Capital flow events (deposits / withdrawals only).
IB's Flex export ships signed amounts (positive = deposit,
negative = withdrawal); we translate to the protocol's
flow_type + always-positive amount convention and
multiply by FXRateToBase so amounts are denominated in the
account's base currency, ready to reconcile against
base-currency NAV.
Returns every flow row in the parsed CSV by default — no
date-based default filter, since the export window is already
fixed by what the user asked IB for. Pass start= /
end= to narrow further.
Source code in python/fundcloud/accounts/ib.py
from_string
classmethod
¶
Construct an :class:IB provider from inline CSV text.
list_accounts
¶
One row per linked account.
For IB, there is one account per ClientAccountID and the
fund id is the same id; the result matches :meth:list_funds
with the column names normalized to the AccountProvider
account schema.
Source code in python/fundcloud/accounts/ib.py
list_funds
¶
One row per unique ClientAccountID in the parsed export.
For IB, fund and account are the same id (single-tier
hierarchy). The columns match the AccountProvider protocol
so generic UI / report code that walks list_funds() works
identically across providers.
Source code in python/fundcloud/accounts/ib.py
nav
¶
nav(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
adjust_for_flows: bool = True,
) -> pd.DataFrame
Daily NAV / AUM rows from the Flex Query NAV section.
Unlike network providers (e.g.,
:class:fundcloud.accounts.fundcloud.FundCloud) this method
does not apply a 1-year-back default to start. The
Flex Query CSV is already bounded by whatever period was
requested at export time — returning the full file is
almost always the right behaviour. Pass start= / end=
to narrow the window further.
adjust_for_flows=True (default) returns a synthetic
flow-adjusted AUM curve computed client-side via
:func:fundcloud.metrics.returns_from_nav, replaying the
return series as if no deposits or withdrawals had occurred.
Pass False for the raw Total column straight from the
CSV.
Returns:
| Type | Description |
|---|---|
DataFrame
|
DatetimeIndex named |
Source code in python/fundcloud/accounts/ib.py
201 202 203 204 205 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 | |
positions
¶
positions(
fund_id: str | None = None,
*,
account_id: str | None = None,
asof: Timestamp | str | None = None,
) -> pd.DataFrame
Returns an empty frame.
The Flex Query "Net Asset Value (NAV) in Base" section provides
asset-class subtotals (Stock, Bonds, Crypto, …) but not
per-symbol position rows. Configure a separate "Open Positions"
Flex section in Interactive Brokers to populate this.
Source code in python/fundcloud/accounts/ib.py
to_portfolio
¶
to_portfolio(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
basis: Basis = "aum",
method: ReturnMethod = "modified_dietz",
benchmark: Series | None = None,
name: str | None = None,
) -> Portfolio
Build a :class:Portfolio from the IB account.
Defaults differ from the FundCloud provider: basis="aum" +
method="modified_dietz". Brokerage accounts have no
shares_outstanding, so the "NAV-per-share + total-return"
path doesn't apply — AUM-basis Modified Dietz is the
GIPS-standard analogue of investor return for a brokerage book.
Override either kwarg to switch (e.g., method="daily_twr"
for daily-precision flow timing).
Source code in python/fundcloud/accounts/ib.py
trades
¶
trades(
fund_id: str | None = None,
*,
account_id: str | None = None,
start: Timestamp | str | None = None,
end: Timestamp | str | None = None,
) -> pd.DataFrame
Returns an empty frame.
The Flex Query "Cash Transactions" section is event data, not trades. Configure a separate "Trades" Flex section in Interactive Brokers to populate this.
Source code in python/fundcloud/accounts/ib.py
Errors¶
Provider errors are rooted at fundcloud.errors.FundcloudError — see the
errors reference for the full hierarchy.