beacon.portfolio¶
The Portfolio accounting primitive: holdings, cash, transactions,
valuation and weights, plus Excel reporting helpers. It has no dependency on
assets or data sources — callers pass identifiers and prices directly.
portfolio ¶
The init.py for the 'portfolio' module.
This module defines and manages investment portfolios, tracks holdings, transactions, and calculates portfolio values.
Holding
dataclass
¶
Holding(
asset_id: str,
quantity: float,
average_cost_price: float,
current_price: float | None = None,
market_value: float | None = None,
)
Represents a holding of a specific asset in the portfolio. Mutable as quantity and market value change.
update_market_data ¶
Updates the holding with the latest market price and recalculates market value.
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/base.py
Portfolio ¶
Portfolio(
portfolio_id: str,
initial_cash: float = 0.0,
inception: Timestamp | None = None,
source: DataFetcher | None = None,
)
Manages a collection of asset holdings, cash balance, and transaction history.
Holdings are keyed by string asset identifiers. The Portfolio has no dependency on Asset objects or DataFetcher — callers pass simple strings and prices.
It is also the store of record (BN-152): what it started with, and
what the books said on every date something changed them, are kept here
rather than flattened onto whatever ran it. See :attr:positions,
:attr:cash and :attr:nav.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
portfolio_id
|
str
|
Identifier for these books. |
required |
initial_cash
|
float
|
Opening cash balance. Retained as
:attr: |
0.0
|
inception
|
Timestamp | None
|
Optional day zero. When given, the books open on that date with NAV and cash equal to the initial capital, before anything trades. A backtest passes its start date; a hand-built portfolio can leave it out, and its history then starts at its first event. |
None
|
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/base.py
positions
property
¶
What was held, long-form: DATE, ASSET_ID, QUANTITY, PRICE, MARKET_VALUE, WEIGHT.
A row per held asset per date on which the books changed — a trade or
a mark. WEIGHT was computed and stored at write time, so it records
what the portfolio believed then rather than what recomputing it now
would say.
weights
property
¶
The stored weights, wide: dates by asset.
A pivot of the positions panel's WEIGHT column — the same recorded
numbers, shaped for cross-book arithmetic: portfolio.weights
subtracts cleanly against an index book's weights because both are
date-by-asset frames (decision 5).
nav
property
¶
Total value on every recorded date.
Starts at :attr:initial_capital on the inception date when one was
given.
asset ¶
One asset's position and market data, read live.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
asset_id
|
str
|
An asset this portfolio holds or has ever held. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
PortfolioAssetView |
PortfolioAssetView
|
Position numbers from these books, market |
PortfolioAssetView
|
facts from the resolved data source. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the books have never seen asset_id — matching
|
DataSourceError
|
If no source is bound and the process has none. |
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/base.py
freeze ¶
Close the books: from here on, any write raises.
Called by the backtest engine when a run ends. The portfolio is then the record of that run, and a later trade against it would restate a result somebody may already have read. Idempotent.
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/base.py
execute_buy ¶
execute_buy(
asset_id: str,
quantity: float,
price: float,
cost: float = 0.0,
date: Timestamp | None = None,
) -> None
Buy an asset: deduct cash, create/update holding, record transaction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
asset_id
|
str
|
String identifier for the asset. |
required |
quantity
|
float
|
Number of units to buy (must be positive). |
required |
price
|
float
|
Execution price per unit. |
required |
cost
|
float
|
Optional transaction cost (brokerage, taxes, etc.). |
0.0
|
date
|
Timestamp | None
|
Optional execution date. Defaults to now, and the history row is written under that same timestamp. |
None
|
Raises:
| Type | Description |
|---|---|
FrozenPortfolioError
|
If the books have been frozen. |
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/base.py
execute_sell ¶
execute_sell(
asset_id: str,
quantity: float,
price: float,
cost: float = 0.0,
date: Timestamp | None = None,
) -> None
Sell an asset: add cash proceeds, reduce/remove holding, record transaction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
asset_id
|
str
|
String identifier for the asset. |
required |
quantity
|
float
|
Number of units to sell (must be positive). |
required |
price
|
float
|
Execution price per unit. |
required |
cost
|
float
|
Optional transaction cost (brokerage, taxes, etc.). |
0.0
|
date
|
Timestamp | None
|
Optional execution date. Defaults to now, and the history row is written under that same timestamp. |
None
|
Raises:
| Type | Description |
|---|---|
FrozenPortfolioError
|
If the books have been frozen. |
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/base.py
apply ¶
Record one trade in the books.
The entry point for anything that has already decided a trade — the engine, after sizing and pricing it. Dispatches to the buy/sell accounting, which stays on the portfolio: weighted average cost, closing at ~zero quantity, refusing entries that would push cash or holdings negative are what make a ledger a ledger, wherever the decision came from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trade
|
TradeInstruction
|
The instruction, as the decider issued it. |
required |
date
|
Timestamp | None
|
Execution date. Defaults to now, as the underlying accounting does. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the side is neither |
FrozenPortfolioError
|
If the books have been frozen. Checked here as well as in the accounting, so the message names the entry point the caller actually used. |
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/base.py
update_prices ¶
Update current prices for holdings from a dictionary.
A mark changes what the books say the portfolio is worth, so it is recorded like a trade is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prices
|
dict[str, float]
|
Mapping of asset_id to current price. Holdings whose asset_id is not in the dict are left unchanged with a warning. |
required |
date
|
Timestamp | None
|
Optional date to mark as of. Defaults to now. |
None
|
Raises:
| Type | Description |
|---|---|
FrozenPortfolioError
|
If the books have been frozen. |
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/base.py
get_total_value ¶
Calculates the total current market value of the portfolio (holdings + cash).
Relies on prices having been set via :meth:update_prices,
:meth:execute_buy, or :meth:execute_sell beforehand.
Returns:
| Type | Description |
|---|---|
float
|
The total portfolio value as a float. |
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/base.py
get_weights ¶
Calculates the current weight of each asset in the portfolio. Weights are based on last-updated market values.
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
A dictionary mapping asset_id strings to weight floats. |
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/base.py
get_holdings_summary ¶
Returns a DataFrame summarizing current holdings.
Returns:
| Type | Description |
|---|---|
DataFrame
|
A pandas DataFrame with columns: AssetID, Quantity, |
DataFrame
|
AvgCostPrice, CurrentPrice, MarketValue, Weight. |
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/base.py
Transaction
dataclass
¶
Transaction(
asset_id: str,
quantity: float,
price: float,
transaction_type: str,
transaction_date: Timestamp,
transaction_cost: float = 0.0,
)
Represents a single transaction (buy or sell) of an asset.
ReportGenerator ¶
Generates various reports for a portfolio or backtest results.
Excel output needs the 'excel' extra; the dependency is checked when a report is generated, so the class itself is always constructible.
generate_holdings_report_excel ¶
generate_holdings_report_excel(
portfolio: Portfolio,
report_path: str,
valuation_date: Timestamp,
) -> None
Generates an Excel report summarizing the current portfolio holdings.
The report is built from the portfolio's own state, so the caller must
have called portfolio.update_prices(...) beforehand for the holdings
to carry current prices (and therefore current market values/weights).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
portfolio
|
Portfolio
|
The Portfolio object to report on. |
required |
report_path
|
str
|
The file path (including .xlsx extension) where the Excel report will be saved. |
required |
valuation_date
|
Timestamp
|
The date the holdings are reported as of; used for logging and report labelling only. |
required |
Raises:
| Type | Description |
|---|---|
MissingDependencyError
|
If openpyxl is not installed. |
ReportingError
|
If there's an issue writing the file. |
ValueError
|
If portfolio is None. |
Source code in build/cache/py-beacon-2c9c3936c65abdb6b8403c50a023f58355be30ee/src/beacon/portfolio/reporting.py
generate_performance_report_excel ¶
generate_performance_report_excel(
performance_data: DataFrame,
report_path: str,
report_title: str | None = "Performance Report",
) -> None
Generates an Excel report from a DataFrame of performance data. The performance_data DataFrame is typically the output of a backtest (e.g., daily portfolio values, returns) or specific analysis results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
performance_data
|
DataFrame
|
A pandas DataFrame containing performance metrics over time. Expected to have a DatetimeIndex. |
required |
report_path
|
str
|
The file path (including .xlsx extension) for the report. |
required |
report_title
|
str | None
|
An optional title for the report (used as sheet name or in header). |
'Performance Report'
|
Raises:
| Type | Description |
|---|---|
MissingDependencyError
|
If openpyxl is not installed. |
ReportingError
|
If there's an issue writing the file. |
ValueError
|
If performance_data is not a non-empty DataFrame. |