Server¶
The local API server: create_app, ServerConfig and the command line. The endpoints themselves are documented in the Server API reference.
server ¶
The local Beacon API server.
Requires the server extra:
pip install "py-beacon-kit[server]"
Importing this subpackage pulls in FastAPI, so the guard fires here and names the extra rather than letting a bare ImportError surface. The rest of Beacon stays importable without it.
ServerConfig
dataclass
¶
ServerConfig(
auth_token: str,
host: str = "127.0.0.1",
port: int = 0,
data_fetcher: DataFetcher | None = None,
market_downloader: Downloader | None = None,
cors_origins: tuple[str, ...] = DEFAULT_CORS_ORIGINS,
storage_root: Path | None = None,
data_store_id: str | None = None,
data_store_name: str | None = None,
)
Settings for a single server process.
Attributes:
| Name | Type | Description |
|---|---|---|
auth_token |
str
|
Bearer token required on every request. Never empty: a server with no token would be open to any process on the machine. |
host |
str
|
Interface to bind. Defaults to loopback and should stay there: the server has no transport security and trusts its bearer token alone. |
port |
int
|
Port to bind. 0 asks the OS for a free one, which the launcher then reads back and prints. |
data_fetcher |
DataFetcher | None
|
The data source to serve, or None to run without one. |
cors_origins |
tuple[str, ...]
|
Exact origins allowed, in addition to the localhost pattern. |
market_downloader |
Downloader | None
|
Where a store refresh fetches market data from, for a folder store set to refresh from Yahoo Finance. None builds the yfinance-backed downloader on first use, which is the real deployment; tests and offline runs inject their own so the download path is exercisable without a network. |
storage_root |
Path | None
|
Base directory for persisted documents. None uses the platform app-data location; tests point it at a temporary path. |
data_store_id |
str | None
|
The registered data store |
data_store_name |
str | None
|
A name to show for the data being served: the store's name, or where unregistered data came from. |
from_environment
classmethod
¶
from_environment(
token: str | None = None,
host: str = "127.0.0.1",
port: int = 0,
data_fetcher: DataFetcher | None = None,
cors_origins: tuple[str, ...] | None = None,
storage_root: Path | None = None,
data_store_id: str | None = None,
data_store_name: str | None = None,
) -> ServerConfig
Build a config, taking the token from the environment if not given.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str | None
|
Explicit token, typically from the command line. When None,
the value of |
None
|
host
|
str
|
Interface to bind. |
'127.0.0.1'
|
port
|
int
|
Port to bind; 0 asks the OS for a free one. |
0
|
data_fetcher
|
DataFetcher | None
|
Data source to serve, or None. |
None
|
cors_origins
|
tuple[str, ...] | None
|
Exact origins to allow. None resolves them from the environment and the defaults. |
None
|
storage_root
|
Path | None
|
Where saved documents live. None uses the platform app-data location. |
None
|
data_store_id
|
str | None
|
The registered store the data came from, if any. |
None
|
data_store_name
|
str | None
|
A name to show for the data being served. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
ServerConfig |
ServerConfig
|
The assembled configuration. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no token is available from either source. |
BacktestResultSummary ¶
Bases: BaseModel
Serialised view of a BacktestResult, in the shape of its books.
The nested shape mirrors the library object, one home per fact. Books the
run did not have (no benchmark given, no index calculated) are null rather
than empty, so a client can tell "not measured" from "measured and empty".
index is a container of two books, {target, optimised}, matching the
library's IndexBooks.
from_result
classmethod
¶
Build from a library BacktestResult.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
BacktestResult
|
The finished run. |
required |
cap
|
float | None
|
The maximum constituent weight declared by the definition whose rules produced the target book: the document's own on a passive run, its parent's on an optimised one. Stamped on that book's rebalance snapshots only: a solved index has no cap of its own, since its constraints are what shaped its weights. |
None
|
ErrorEnvelope ¶
Bases: BaseModel
Every non-2xx response uses this shape.
HealthResponse ¶
Bases: BaseModel
Response of GET /health.
IndexResultSummary ¶
Bases: BaseModel
Serialised view of an IndexResult.
from_result
classmethod
¶
Build from a library IndexResult.
Money ¶
Bases: BaseModel
An amount with its denomination.
A bare float would leave the currency implicit, which breaks as soon as a response mixes denominations.
SeriesPayload ¶
Bases: BaseModel
A pandas Series on the wire.
TableFrame ¶
Bases: BaseModel
A pandas DataFrame on the wire.
Row-oriented so column order is preserved and the payload stays compact.
from_dataframe
classmethod
¶
Build from a pandas DataFrame.
create_app ¶
Build the ASGI application for a given configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
ServerConfig
|
Settings for this process, including the bearer token every route will require and the data source to serve. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
FastAPI |
FastAPI
|
The configured application. Nothing is bound or started |
FastAPI
|
here; see beacon.server.main for the launcher. |
classify ¶
Map a library exception to its HTTP status and stable code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exc
|
BeaconError
|
The raised library exception. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
int
|
|
str
|
fall through to the catch-all rather than escaping as a bare 500. |
register_exception_handlers ¶
Attach the handlers that put every error into the envelope.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
FastAPI
|
The application to register on. |
required |
dataframe_to_payload ¶
Serialise a DataFrame as {index, columns, data}.
Row-oriented data keeps the payload compact and preserves column
order, which a dict-of-columns would not guarantee.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
DataFrame
|
The frame to serialise. An empty frame yields empty lists. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict[str, Any]
|
|
dict[str, Any]
|
names), and |
|
dict[str, Any]
|
NaN, NaT and infinities all become None. |
series_to_payload ¶
Serialise a Series as {index, name, data}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
series
|
Series
|
The series to serialise. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict[str, Any]
|
|
dict[str, Any]
|
and |
active_data ¶
The data the engine is serving right now, and the one rule for needing it.
A data store can be loaded, or switched, while the engine runs, so the current data lives here, on app state, and every reader asks this module.
A route that needs data and finds none answers 409: no data being loaded is a state the caller can change by loading a store, not a server fault.
ActiveData
dataclass
¶
ActiveData(
fetcher: DataFetcher | None = None,
store_id: str | None = None,
store_name: str | None = None,
loading: bool = False,
data_version: str = new_data_version(),
)
What the engine serves: the loaded data and the store it came from.
Replaced whole when a store is loaded, never edited in place, so a request
that has already read fetcher keeps a consistent dataset for its whole
run even if another store is loaded meanwhile.
Attributes:
| Name | Type | Description |
|---|---|---|
fetcher |
DataFetcher | None
|
The loaded data, or None when nothing is loaded. |
store_id |
str | None
|
The registered store it came from. None for data given on
the command line or by |
store_name |
str | None
|
A name to show for it: the store's name, or where the unregistered data came from. |
loading |
bool
|
Whether a store is being loaded right now. One load at a time: a second would race the first to replace the data. |
data_version |
str
|
An opaque token that changes whenever the data being served changes: at startup, and on every load (the same store loaded again included, since its files may have changed, and a refresh of the served store, which reloads it). A client compares it for equality only, to know whether what it cached is still current. Random rather than a counter, so an engine restart can never bring an old value back. |
new_data_version ¶
A token no earlier data, in this process or any before it, can have.
current_data ¶
The loaded data, or None, for a reader that works either way.
require_data ¶
The loaded data, or a refusal saying what could not be done.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
The incoming request. |
required |
purpose
|
str
|
What needs the data, completing "No data is loaded, so ...", e.g. "a backtest cannot be run". |
required |
Raises:
| Type | Description |
|---|---|
NoDataLoadedError
|
When nothing is loaded. Maps to 409 NO_DATA_LOADED. |
app ¶
Application factory for the Beacon API server.
The server is a local process owned by a desktop client: it binds loopback, authenticates every route with a bearer token the client generated, and holds no state of its own beyond the data source it was handed.
build_router ¶
Build the router carrying the engine's own routes.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router with /health and /changelog, guarded by the bearer |
APIRouter
|
dependency. |
create_app ¶
Build the ASGI application for a given configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
ServerConfig
|
Settings for this process, including the bearer token every route will require and the data source to serve. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
FastAPI |
FastAPI
|
The configured application. Nothing is bound or started |
FastAPI
|
here; see beacon.server.main for the launcher. |
backtests ¶
Backtest job body and result assembly.
Everything reported here derives from a single canonical series (the portfolio NAV from its initial capital), so the payload is internally consistent by construction rather than by coincidence. A client that recomputes drawdown from the level series, or compounds the annual returns, must land back on the numbers the server sent; if those were computed independently they would drift apart at the last decimal and nobody would know which to trust.
annual_returns ¶
Calendar-year returns that compound exactly to the total.
Each year runs from the previous year's closing level to its own, so the
product of (1 + r) telescopes to last / first - 1. Defining them any
other way (from the first observation within each year, say) leaves a
gap over each year boundary and the compounded total no longer matches.
The first observation is the starting point, not a year of its own: a
run that starts on 2 January begins from the capital held on the eve,
which may fall in the year before.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
level
|
Series
|
The level series, indexed by date. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict[str, float]
|
Year (as a string) -> return for that year. |
assemble_result ¶
assemble_result(
result: BacktestResult,
index_result: IndexResult,
benchmark: RelativeMetricsPayload | None = None,
cap: float | None = None,
) -> BacktestRunResult
Build the wire payload from a completed backtest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
BacktestResult
|
The finished backtest. |
required |
index_result
|
IndexResult
|
The index it tracked, reported alongside as the replication reference. |
required |
benchmark
|
RelativeMetricsPayload | None
|
Optional comparison against an external benchmark. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
BacktestRunResult |
BacktestRunResult
|
Level, returns, drawdown, annual returns, the |
BacktestRunResult
|
tracked index and metrics, all derived from the same NAV series. |
target_cap ¶
The cap that applies to the run's index.target book, if any.
Read off the built definition rather than the document because an optimised document has no pipeline of its own to read one from, while its target book is its parent's calculation and the parent's cap is the one that shaped those weights. A chain whose immediate parent is itself derived has no cap at that level, and says so.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
definition
|
AnyIndexDefinition
|
The definition the run calculated. |
required |
compare_against_benchmark ¶
compare_against_benchmark(
nav: Series,
reference: BenchmarkRef,
fetcher: DataFetcher,
index_store: DocumentStore,
start: str,
end: str,
) -> RelativeMetricsPayload
Resolve a benchmark and measure the portfolio against it.
The benchmark series is rebased on the aligned window rather than its own full history, so both lines start at 100 on the same date and can be read off one axis. Rebasing before alignment would leave the benchmark starting somewhere other than 100 once trimmed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nav
|
Series
|
Portfolio NAV series. |
required |
reference
|
BenchmarkRef
|
What to compare against. |
required |
fetcher
|
DataFetcher
|
Data source. |
required |
index_store
|
DocumentStore
|
Where stored index definitions live. |
required |
start
|
str
|
Window start, YYYY-MM-DD. |
required |
end
|
str
|
Window end, YYYY-MM-DD. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
RelativeMetricsPayload |
RelativeMetricsPayload
|
The comparison and the rebased benchmark. |
build_backtest_job ¶
build_backtest_job(
document: IndexDocument,
fetcher: DataFetcher,
request: BacktestRequest,
index_store: DocumentStore,
record_store: DocumentStore | None = None,
) -> JobBody
Build the job body that runs one backtest.
Returned as a closure rather than run inline: the caller submits it to the job registry, which owns scheduling and progress publication.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
IndexDocument
|
The index definition to calculate and then track. |
required |
fetcher
|
DataFetcher
|
Data source for both the index and the simulation. |
required |
request
|
BacktestRequest
|
Period, capital and cost settings for this run. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
JobBody |
JobBody
|
A coroutine function taking a progress reporter. |
benchmarks ¶
Resolving a benchmark reference to a level series.
A benchmark arrives as a reference, not data: either the id of a stored index definition (which has to be calculated before it can be compared against) or a market-data identifier whose price series is the benchmark directly.
A stored index of either face qualifies: a benchmark needs only a level series to compare against, and an optimised index has one exactly as a rule-driven one does. It is calculated through whichever path its definition requires, which is the only difference the two faces make here.
resolve_benchmark ¶
resolve_benchmark(
reference: BenchmarkRef,
fetcher: DataFetcher,
index_store: DocumentStore,
start: str,
end: str,
) -> pd.Series
Turn a benchmark reference into a date-indexed level series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reference
|
BenchmarkRef
|
What to compare against. |
required |
fetcher
|
DataFetcher
|
Data source. |
required |
index_store
|
DocumentStore
|
Where stored index definitions live. |
required |
start
|
str
|
Start date, YYYY-MM-DD. |
required |
end
|
str
|
End date, YYYY-MM-DD. |
required |
Returns:
| Type | Description |
|---|---|
Series
|
pd.Series: Levels indexed by date. Not rebased: the caller decides, |
Series
|
and returns are scale-invariant anyway. |
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If the referenced index or identifier does not exist, or carries no data over the window. |
config ¶
Configuration for the Beacon API server.
The server is spawned and owned by a desktop client, so its configuration arrives from the command line and the environment rather than from a file.
ServerConfig
dataclass
¶
ServerConfig(
auth_token: str,
host: str = "127.0.0.1",
port: int = 0,
data_fetcher: DataFetcher | None = None,
market_downloader: Downloader | None = None,
cors_origins: tuple[str, ...] = DEFAULT_CORS_ORIGINS,
storage_root: Path | None = None,
data_store_id: str | None = None,
data_store_name: str | None = None,
)
Settings for a single server process.
Attributes:
| Name | Type | Description |
|---|---|---|
auth_token |
str
|
Bearer token required on every request. Never empty: a server with no token would be open to any process on the machine. |
host |
str
|
Interface to bind. Defaults to loopback and should stay there: the server has no transport security and trusts its bearer token alone. |
port |
int
|
Port to bind. 0 asks the OS for a free one, which the launcher then reads back and prints. |
data_fetcher |
DataFetcher | None
|
The data source to serve, or None to run without one. |
cors_origins |
tuple[str, ...]
|
Exact origins allowed, in addition to the localhost pattern. |
market_downloader |
Downloader | None
|
Where a store refresh fetches market data from, for a folder store set to refresh from Yahoo Finance. None builds the yfinance-backed downloader on first use, which is the real deployment; tests and offline runs inject their own so the download path is exercisable without a network. |
storage_root |
Path | None
|
Base directory for persisted documents. None uses the platform app-data location; tests point it at a temporary path. |
data_store_id |
str | None
|
The registered data store |
data_store_name |
str | None
|
A name to show for the data being served: the store's name, or where unregistered data came from. |
from_environment
classmethod
¶
from_environment(
token: str | None = None,
host: str = "127.0.0.1",
port: int = 0,
data_fetcher: DataFetcher | None = None,
cors_origins: tuple[str, ...] | None = None,
storage_root: Path | None = None,
data_store_id: str | None = None,
data_store_name: str | None = None,
) -> ServerConfig
Build a config, taking the token from the environment if not given.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str | None
|
Explicit token, typically from the command line. When None,
the value of |
None
|
host
|
str
|
Interface to bind. |
'127.0.0.1'
|
port
|
int
|
Port to bind; 0 asks the OS for a free one. |
0
|
data_fetcher
|
DataFetcher | None
|
Data source to serve, or None. |
None
|
cors_origins
|
tuple[str, ...] | None
|
Exact origins to allow. None resolves them from the environment and the defaults. |
None
|
storage_root
|
Path | None
|
Where saved documents live. None uses the platform app-data location. |
None
|
data_store_id
|
str | None
|
The registered store the data came from, if any. |
None
|
data_store_name
|
str | None
|
A name to show for the data being served. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
ServerConfig |
ServerConfig
|
The assembled configuration. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no token is available from either source. |
resolve_cors_origins ¶
Find the exact origins this server should allow.
In order: --cors-origin (repeatable), then $BEACON_CORS_ORIGINS
as a comma-separated list, then the defaults.
Explicit origins replace the defaults rather than adding to them. An operator narrowing what may call the server should not find two extra origins still permitted: that is the opposite of what configuring it means. The localhost pattern is applied separately by the middleware and is unaffected either way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
explicit
|
list[str] | None
|
Origins from the command line, or None. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple[str, ...]
|
Origins, in the order given, with duplicates removed. |
resolve_data_source ¶
Find the data source a spawned server should serve.
In order:
--data <path>, passed here asexplicit$BEACON_DATA_PATH- the app-data store, if one has been written there
- nothing: the server starts data-less
The two explicit branches fail loudly: asking for a store that cannot be
read is a mistake worth stopping for, and starting data-less instead would
turn it into a puzzle about why every endpoint that needs data answers
409 NO_DATA_LOADED. The auto-load branch does the opposite and only
warns, because a corrupt app-data store must not leave the client unable to
start the server that would let it write a new one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
explicit
|
Path | None
|
Path from the command line, or None. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
DataFetcher | None
|
The fetcher (or None), and a sentence naming the branch that |
str
|
ran, for the caller to log. Which branch ran is the first thing anyone |
|
tuple[DataFetcher | None, str]
|
debugging an empty client will want to know. |
constraints ¶
Constraint sets: the stored form of what a portfolio is allowed to be.
One stored row maps to exactly one class in beacon.optimise.constraints. That
correspondence is the whole design: a client's constraint editor, the JSON it
saves, and the objects the solver receives are the same list in three
representations, so there is no translation layer where a rule can quietly
change meaning.
Validation happens before the job, not inside it¶
A malformed constraint set is a bad request, and the client should learn that from the save or the submission rather than from a job that fails a moment later. So the document is validated up front and reports every problem it finds, addressed to the row that caused it. A user fixing a constraint editor needs all the errors, not the first one.
What cannot be checked here is feasibility: whether a set of individually valid constraints can be satisfied together depends on the universe and the data, and the optimiser answers that when it runs. It refuses rather than fudging, so the failure still reaches the client with a message naming what is impossible.
constraint_types_registered ¶
The constraint types a row may name.
Read off the catalogue rather than a hand-kept table: importing
beacon.optimise above is what registers the classes, and adding a
constraint means decorating it there and nowhere else.
constraint_params ¶
Constraint type -> the parameters it accepts.
Read off the constraint classes themselves, so a parameter a class accepts is always accepted here too.
validate_constraint_set ¶
Check a constraint set, reporting everything wrong with it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
ConstraintSet
|
The set to check. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[Finding]
|
Findings, addressed to the row that caused each. Empty when the |
list[Finding]
|
set is well formed, which is not the same as feasible. |
validate_constraint_rows ¶
validate_constraint_rows(
constraints: Sequence[ConstraintRow],
prefix: str = "constraints",
) -> list[Finding]
Check a list of constraint rows wherever it is carried.
The same rows appear in two documents (a stored constraint set, and an optimised index's derivation), and a client editing either needs identical answers, so there is one checker and the caller only says where the rows live.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
constraints
|
Sequence[ConstraintRow]
|
The rows to check. |
required |
prefix
|
str
|
Dotted path the findings are addressed under, e.g.
|
'constraints'
|
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[Finding]
|
Findings, addressed to the row that caused each. |
build_constraints ¶
Turn a stored set into optimiser constraint objects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
ConstraintSet
|
A set that has already been validated. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[Constraint]
|
The constraints, in the order the rows carry them. Order matters |
list[Constraint]
|
only for reporting, since the solver applies them all at once. |
Raises:
| Type | Description |
|---|---|
CalculationError
|
If a row's type is unknown, which validation would have caught. Reaching here means the set was never validated. |
build_constraint_rows ¶
Turn constraint rows into optimiser constraint objects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
constraints
|
Sequence[ConstraintRow]
|
Rows that have already been validated, from a stored set or from an optimised index's derivation. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[Constraint]
|
The constraints, in the order the rows carry them. |
has_errors ¶
Whether any finding blocks saving or running.
label_map ¶
Constraint label to the row id that produced it.
The optimiser reports binding constraints by their own generated labels, such as "maximum weight 10.0000% on AAA", which say what bound but not which row of the editor to highlight. Building the same objects in the same order and reading their labels back gives the mapping, without the optimiser needing to know that a stored document exists.
constraint_types ¶
Every constraint type and the parameters it accepts.
Served so a client can build its editor from the same source the solver reads, rather than from a copy that drifts.
data_stores ¶
Named data stores: which exist, which one the engine serves, and loading it.
A data store holds one dataset. Users name their stores ("Synthetic data",
"My data"), one is active at a time, and the engine remembers which, so the
next start serves the same data. A store is a py-beacon data folder or a
Postgres database, and each can be refreshed from its own source
(refresh_plan).
The registry is saved with the engine's other documents (indices, universes),
so it lives wherever --documents points.
What the engine serves at startup¶
In order:
--data <path>: a folder named on the command line.$BEACON_DATA_PATH.- The active registered store.
- If nothing is registered yet but a store exists in the default app-data folder, it is registered as "Synthetic data" (or by its source) and made active, so an existing app-data store is served without any setup.
- Nothing: the engine starts empty, and a store can be loaded later.
The first two fail loudly, because naming data that cannot be read is a mistake worth stopping for. The active store only warns and starts empty, so a store that was moved or damaged never stops the engine from starting and offering another.
StoreRecord ¶
Bases: BaseModel
A registered store, as saved. Which store is active is saved here too, as a flag on its record, so the registry is one collection with nothing beside it to fall out of step.
StoreRegistry ¶
The registered data stores and which one is active.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path | None
|
Where the engine keeps its documents. None uses the platform app-data location, as every other collection does. |
None
|
listing ¶
Every readable store in name order, and what was skipped and why.
get ¶
One store's record, or None when there is none or it can't be read.
find_by_path ¶
The store registered for this folder or database, if any.
new_id ¶
An unused id derived from name.
A second store with the same name gets a numbered id ("my-data-2"), so names never have to be unique, only ids.
create ¶
create(
name: str,
path: Path | str,
kind: str = "folder",
managed: bool = False,
store_id: str | None = None,
connection: dict[str, Any] | None = None,
refresh_from: str = "source",
) -> dict[str, Any]
Register a store, with an id derived from its name unless given.
path is a folder for a folder store; for a database it is the connection described without its password.
update ¶
update(
store_id: str,
name: str | None = None,
refresh_from: str | None = None,
) -> dict[str, Any] | None
Change a store's display name or refresh source. Its id stays.
active_id ¶
The store the engine last served, or None.
If more than one record is flagged, as a crash between two writes in
set_active could leave it, the most recently loaded one wins.
set_active ¶
Remember which store to serve, including across restarts.
The new store is flagged before the old one is cleared, so a crash in
between leaves two flagged rather than none, and active_id resolves
that.
StartupData
dataclass
¶
StartupData(
fetcher: DataFetcher | None,
store_id: str | None,
store_name: str | None,
origin: str,
)
What the engine serves when it starts, and a line saying why.
load ¶
Read a registered store into a fetcher.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
dict[str, Any]
|
The store's registry record. |
required |
**settings
|
Any
|
Passed to the loader: |
{}
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If the store cannot be read. |
postgres_source ¶
The database a Postgres store's record points at.
refresh_plan ¶
What refreshing a store would do, and if nothing, why not.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
RefreshAction | None
|
The action ("extend", "reread" or "download"), or None with a |
str
|
sentence saying why the store has nothing to refresh. |
available ¶
Whether a store can be loaded now, as far as can be told cheaply.
A folder must hold a store. A database is only checked for its password variable: connecting on every listing would make the list as slow as the slowest database in it, so a database that is down is found when it is loaded.
describe ¶
A store's listing row, read without loading it.
resolve_startup ¶
Find the data to serve at startup. See the module docstring for the order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
explicit
|
Path | None
|
|
required |
root
|
Path | None
|
Where the engine keeps its documents ( |
required |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If |
definitions ¶
Index definition documents: validation and materialisation.
A stored definition is a JSON document describing a rule pipeline. It is not an
IndexDefinition: the library object takes constructed rule and scheme
instances, which JSON cannot carry. This module owns both directions: checking
a document and turning a valid one into the library object.
Validation collects findings rather than raising at the first problem. A user editing a pipeline needs every issue at once, each addressable to the rule that caused it, not a single exception naming whichever one failed first.
selection_rules ¶
Selection rule name -> the parameters it accepts.
weighting_schemes ¶
Weighting scheme name -> the parameters it accepts.
validate_document ¶
Collect every finding for a definition document.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
IndexDocument
|
The definition to check, of either face. Which checks run is
decided by |
required |
Returns:
| Type | Description |
|---|---|
list[Finding]
|
list[Finding]: Every problem found, each carrying the path and, where |
list[Finding]
|
applicable, the id of the rule responsible. Empty when the definition |
list[Finding]
|
is valid and unremarkable; warnings alone do not block saving. |
build_index_definition ¶
Materialise a valid rule-driven document into an IndexDefinition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
IndexDocument
|
A document that has already passed validate_document() without errors, carrying a rule pipeline. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
IndexDefinition |
IndexDefinition
|
The library object, ready for IndexCalculator. |
Raises:
| Type | Description |
|---|---|
InvalidRuleError
|
If the document is optimiser-derived. An
|
ValueError
|
If the document is invalid after all. The library's own constructor validation is the final word. |
build_definition ¶
Materialise a document of either face into a library definition.
A rule-driven document builds an :class:IndexDefinition; an
optimiser-derived one builds an
:class:~beacon.index.derived.OptimisedIndexDefinition over its source,
resolved through the store. This is recursive, so a chain of derivations builds
a chain of definitions and the whole thing calculates through one path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
IndexDocument
|
The stored definition to materialise. |
required |
documents
|
DocumentStore
|
Where the sources of a derivation are read from. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
AnyIndexDefinition |
AnyIndexDefinition
|
The library object, ready for |
AnyIndexDefinition
|
class: |
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If a derivation names a source that is not stored. |
InvalidRuleError
|
If a derivation chain returns to itself, or runs
deeper than :data: |
derivatives ¶
Stateless derivatives pricing.
Nothing here reads a stored document or writes one. A request carries every input it needs and the response is a pure function of it, which is what makes these endpoints safe to call repeatedly from a form as someone types, and what lets a test assert that the storage directory is untouched afterwards.
The term-structure and roll reads are the exception only in that they resolve a price from the data source. They still write nothing.
A futures fair value is S·e^((r − q + c)·T). The response splits the carry
into its financing, dividend and borrow components, each expressed as the
price effect it contributes rather than as a rate: "financing adds 1.24" is a
sentence about this contract, while "r is 5%" is a sentence about the world.
price_futures ¶
Value a futures contract and decompose its carry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
FuturesPriceRequest
|
Every input the calculation needs. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
FuturesPriceResponse |
FuturesPriceResponse
|
Fair value, the carry split into parts, contract |
FuturesPriceResponse
|
value, and a tenor x rate grid. |
price_trs ¶
Value a total return swap and schedule its financing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
TrsPriceRequest
|
Trade terms, legs and valuation inputs. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
TrsPriceResponse |
TrsPriceResponse
|
Financing schedule, present value, fair spread, |
TrsPriceResponse
|
breakeven table and DV01. |
build_term_structure ¶
build_term_structure(
index_id: str,
fetcher: DataFetcher,
expiries: list[str],
as_of: str | None,
risk_free_rate: float,
dividend_yield: float,
) -> TermStructureResponse
Price a strip of futures on an index, off its own spot.
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If the index cannot be priced. |
build_roll ¶
build_roll(
index_id: str,
fetcher: DataFetcher,
front_expiry: str,
back_expiry: str,
as_of: str | None,
risk_free_rate: float,
dividend_yield: float,
) -> RollResponse
The cost or gain of rolling from one contract to the next.
Both legs are priced theoretically off the same spot and curve, so the roll reported here is the carry roll rather than a market one. With a flat curve it is positive in backwardation and negative in contango, which is the sign convention a desk expects.
documents ¶
Reading stored documents: tolerant listings, strict details.
A listing answers "what is there". A document the server cannot parse or validate is skipped, with a WARNING naming the id and the fault, and the rest of the collection is served; the response counts what was left out and why.
A detail route answers "give me this one". A document the server cannot read gets the same 404, envelope and pointer as an absent one, and the underlying fault is logged at WARNING rather than returned, because it is about this server's storage and not about the request. Listings and detail routes share one definition of "unreadable", so they cannot disagree about what exists.
Write paths use stored, which keeps "is something stored here" and "can it be
read" apart, so a corrupt document can still be deleted or replaced.
SkipCounts
dataclass
¶
How many documents a listing left out, and why.
The three causes call for different responses, so they are counted separately.
Attributes:
| Name | Type | Description |
|---|---|---|
unparseable |
int
|
Not valid JSON, or a schema version nothing can migrate. The file itself is damaged; restore or remove it. |
from_newer_build |
int
|
Written by a newer py-beacon than the one reading it. Nothing is wrong with the file; upgrade the engine. |
unrecognised |
int
|
Valid JSON that this build's model does not accept. The document and the engine disagree about its shape -- usually a version gap the schema migrations do not cover. |
Stored
dataclass
¶
Bases: Generic[T]
What a collection holds under one id: both questions, answered once.
Three states, and every write path branches on some pair of them: absent (nothing stored), present-but-unreadable (a file is there and the server cannot interpret it), and readable (here is the document). A bool alone cannot express the middle one, which is precisely the state that made a corrupt document undeletable.
Built from a single store.read, so present cannot contradict the
document the same call returned -- an exists followed by a read can.
Attributes:
| Name | Type | Description |
|---|---|---|
document |
T | None
|
The built document, or None when absent or unreadable. |
fault |
Exception | None
|
Why it could not be read; None when it read, and also None when
nothing is stored. |
warn_guard_skipped ¶
Log a guard that this document's fault made impossible to run.
A no-op when the document is absent or readable: there is no guard to skip in either case.
The log line is the only record, and it has to name both halves -- which check did not run, and why -- because the request succeeds and the response says nothing about it. Skipping is the lesser evil (a guard that cannot run must not make a document permanently unfixable), but it is still a guard that did not run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
guard
|
str
|
The check that was skipped, as a noun phrase. |
required |
describe
|
str
|
The document it would have guarded, e.g. |
required |
validated ¶
A build function that validates a document against model.
The common case: a document that carries its own identity, so the id the file was found under adds nothing and is ignored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
type[ModelT]
|
The response model the stored document must satisfy. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Callable |
Callable[[str, dict[str, Any]], ModelT]
|
|
raw ¶
A build function that hands back the stored dict unchanged.
For a caller that wants the document as stored rather than as a model: the delete cascade reads derivations off raw dicts, and a guard that only reads one key has no reason to hold the whole document to a model.
stored ¶
stored(
store: DocumentStore,
document_id: str,
build: Callable[[str, dict[str, Any]], T],
) -> Stored[T]
Ask what a collection holds under an id, without conflating the answers.
The write-path counterpart to load_document: where a read turns an
unreadable document into a not-found, a write needs the distinction kept,
so this one refuses nothing and reports.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
DocumentStore
|
The collection to look in. |
required |
document_id
|
str
|
Identifier of the document. |
required |
build
|
Callable[[str, dict[str, Any]], T]
|
Turns |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Stored |
Stored[T]
|
The document, or the fault that stopped it being read. |
read_collection ¶
read_collection(
store: DocumentStore,
build: Callable[[str, dict[str, Any]], T],
describe: str,
) -> tuple[list[T], SkipCounts]
Read every document in a collection, skipping the unreadable ones.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
DocumentStore
|
The collection to read. |
required |
build
|
Callable[[str, dict[str, Any]], T]
|
Turns |
required |
describe
|
str
|
What these documents are, for the log line, singular: "universe", "index definition". |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
list[T]
|
|
SkipCounts
|
model rather than only logged: a picker silently short by three is |
|
tuple[list[T], SkipCounts]
|
indistinguishable from a correct one, and the server is the only side |
|
tuple[list[T], SkipCounts]
|
that knows. Broken down by cause, because this side also knows why, |
|
tuple[list[T], SkipCounts]
|
and the client cannot work it out. |
load_document ¶
load_document(
store: DocumentStore,
document_id: str,
build: Callable[[str, dict[str, Any]], T],
describe: str,
source: str = "DocumentStore",
) -> T
Read one document, answering not-found when it cannot be read.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
DocumentStore
|
The collection to read from. |
required |
document_id
|
str
|
Identifier of the document. |
required |
build
|
Callable[[str, dict[str, Any]], T]
|
Turns |
required |
describe
|
str
|
The subject of the not-found message, e.g. |
required |
source
|
str
|
The pointer the not-found message carries. Defaults to the store; a route with somewhere better to send the client (running a backtest, say) passes its own. |
'DocumentStore'
|
Returns:
| Type | Description |
|---|---|
T
|
Whatever build returns. |
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If the document is absent, is not valid JSON, or does not satisfy its model. One answer for all three, because a client can act on none of them differently. |
slug ¶
Derive a document id from a display name.
Shared by universes and data stores, so both turn a name into an id the same way.
Lower-cased, runs of non-alphanumerics collapsed to one dash, trimmed: "My Tech Names!" becomes "my-tech-names", which is what appears in the URL.
Returns an empty string when nothing survives. A name of pure punctuation has no identifier, and the caller refuses it rather than inventing one.
errors ¶
Exception to HTTP mapping.
Every library exception is registered here, in one place, so a new one cannot reach a client as an unlabelled 500. Codes are part of the API contract: clients branch on them, so they must stay stable even if the message changes.
FindingsError ¶
Bases: InvalidRuleError
A refusal that carries every finding, not one message for the form.
Subclasses InvalidRuleError for its 422 / INVALID_RULE mapping, with no
registration of its own. The findings ride along as an attribute, which
_beacon_detail puts into the envelope's detail, so a client can point
at each bad rule, member or row. Index pipelines, universes, feature
imports and constraint sets all refuse through this one class.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject
|
str
|
What was refused, e.g. "universe" or "index definition 'X'".
Published as |
required |
reason
|
str
|
Why, in a phrase. Published as |
required |
findings
|
list[Finding]
|
Every problem found. |
required |
classify ¶
Map a library exception to its HTTP status and stable code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exc
|
BeaconError
|
The raised library exception. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
int
|
|
str
|
fall through to the catch-all rather than escaping as a bare 500. |
failure_envelope ¶
The {code, message, detail} an exception publishes, without a response.
For a caller that has to record a failure rather than answer a request: a background job, whose caller received 202 long before anything went wrong. A deliberate refusal and a crash therefore reach the client with different codes, exactly as they would over HTTP.
The ladder matches register_exception_handlers deliberately, so the same
exception carries the same code whichever way it travels. That includes
ValueError -> INVALID_ARGUMENT, with the tension noted on
ARGUMENT_CODE above and one wrinkle of its own: inside an accepted job a
ValueError is less certainly the caller's fault than it is at the request
boundary, because the request was already validated. It is still the
likelier reading (the job body is where a stored document becomes a
definition, and that is exactly where a bad stored value surfaces), and
one code for one exception is worth more than a second judgement call in a
second place.
Anything else is wrapped as an UnexpectedCalculationError rather than
given a code of its own. A fault that escaped every guard IS that case, so
it publishes UNEXPECTED_CALCULATION_FAILURE and carries original_type
exactly as one raised in the library would. Reaching for the BEACON_ERROR
catch-all instead would put a crash under the code an unregistered
refusal gets, so a client could not tell the two apart.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exc
|
BaseException
|
Whatever the job raised. |
required |
calculation
|
str
|
What was being computed, for the wrap's calculation name.
A job's |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict[str, Any]
|
The |
dict[str, Any]
|
Not the outer envelope: a job records the detail, not a response. |
register_exception_handlers ¶
Attach the handlers that put every error into the envelope.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
FastAPI
|
The application to register on. |
required |
jobs ¶
In-process job registry for long-running work.
A backtest or an optimisation takes long enough that holding an HTTP connection open for it is the wrong shape: the client wants to submit, get an id back, and either poll or listen. Jobs run as asyncio tasks in the server process. There is no queue, no broker and no persistence, which suits a single local process owned by one desktop client. Restarting the server loses in-flight jobs, and that is the correct trade for this deployment.
Every state change is published to subscribers, so the WebSocket feed and polling see the same thing.
Job
dataclass
¶
Job(
id: str,
kind: str,
status: str = PENDING,
progress: float = 0.0,
message: str = "",
result: Any = None,
error: dict[str, Any] | None = None,
_task: Task[Any] | None = None,
)
A unit of background work and its observable state.
JobRegistry ¶
Owns running jobs and the subscribers watching them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result_store
|
DocumentStore | None
|
Where completed results are persisted. None keeps everything in memory, which is what the unit tests want and what a process with nowhere to write falls back to. |
None
|
results
property
¶
Where completed results are persisted, or None when nowhere is.
Read-only, and exposed for exactly one caller: GET /jobs reads this
collection through documents.read_collection the way every other
listing reads its own. The model half of "unreadable" has to be
applied where the model is known, and JobStatus is a wire model. The
registry is deliberately free of the wire layer, so the listing cannot
be assembled in here without dragging schemas down with it.
What stays in here is the bookkeeping the registry owns and no route
can express: retention, the cascade delete, and the latest-result
queries. Those decide skip-or-see for themselves; see _prune.
subscribe ¶
Register a subscriber and return its event queue.
publish ¶
Send an event to every subscriber, dropping the oldest if full.
Deliberately synchronous and non-blocking: a job reporting progress must never await a slow reader.
publish_data_loaded ¶
Announce that the engine now serves a different data store.
A client holding anything derived from the data (lists of names, coverage, previews) should refetch it: every dataset may have changed at once.
publish_data_freshness ¶
Announce that a dataset's contents may have changed.
get ¶
Return an in-memory job by id, or None.
Only jobs this process ran. Use :meth:snapshot to include results
persisted by an earlier process.
snapshot ¶
Return a job's state, from memory or from disk.
A job this process ran is authoritative; otherwise the persisted result of an earlier process is served, which is what lets a completed backtest survive a restart and still be readable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
str
|
Identifier of the job. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
dict or None: The snapshot, or None if the job is unknown to both. |
latest_result ¶
The result of the most recent successful job of a kind.
Read from the store rather than from memory. Every terminal job is persisted, so the store is the complete record, and it is the only one of the two that survives a restart, which is the case this exists to serve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
The job kind, e.g. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
dict or None: The stored result, or None when nothing of that kind |
dict[str, Any] | None
|
has succeeded. |
forget ¶
Drop every job and persisted result of one kind.
The cascade half of deleting the thing a kind is keyed to: an index's backtest results go with its definition, rather than surviving under an id that does not resolve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
The exact kind, e.g. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
How many records went (in-memory jobs plus persisted |
int
|
results), so the caller can log what the delete cost. |
latest_results_by_kind ¶
The newest successful result for every kind under a prefix.
Reads the store, which holds every terminal job including the ones this
process ran. The /jobs listing deliberately excludes those so a job
does not appear twice, and filtering the same way here would hide every
model the running process had just estimated, which is most of them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prefix
|
str
|
Kind prefix including its separator, e.g. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict[str, dict[str, Any]]
|
Kind to its newest result. |
submit ¶
Start a job and return it immediately.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
Label for what this job is, e.g. "backtest". |
required |
body
|
JobBody
|
Coroutine function taking a progress reporter. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Job |
Job
|
The registered job, already scheduled. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called off the event loop. FastAPI runs a sync
endpoint in a worker thread, where there is no loop to attach
a task to, so a submitting endpoint must be |
cancel ¶
Request cancellation of a job.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
str
|
Identifier of the job. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if cancellation was requested, False if the job is |
bool
|
unknown or already finished. |
stream_events
async
¶
Yield events from a subscriber queue until cancelled.
methods ¶
Answer 405 for a method a fixed path does not support.
A fixed path such as /indices/validate sits beside a parameterised one such
as /indices/{index_id}. The router alone would match PUT /indices/validate
against the parameterised path with index_id="validate", and the client would
be told its request was malformed when the truth is that the verb does not
exist there. The published spec lists every method each fixed path supports,
so this reads the spec once and, for a fixed path, refuses with 405 any method
the spec does not list for it. A fixed path with no parameterised sibling gets
the same 405 as always.
optimisation ¶
Running an optimisation, and reading the frontier and exposures off it.
A solve is fast; estimating the risk model it needs is not, because that means pulling a price history for every name and building a covariance. So a run is a job like a backtest, and the result carries enough that the frontier and exposures panes read it rather than re-solving.
Where the inputs come from¶
- Target weights: the index's latest completed run, via its rebalance snapshots. Optimising against an index nobody has calculated is not a thing that can be done, so it is a 404 rather than a silent default.
- Risk model: estimated from the constituents' own price history over the run's window. Shrunk toward constant correlation, because a covariance estimated on a few hundred observations across a similar number of names is badly conditioned and an optimiser inverts it.
- Expected returns: the historical mean, annualised, and this is a modelling choice worth stating plainly: historical mean returns are a poor forecast. They are used because they are the only return estimate derivable from the data the server holds, and because a frontier has to be drawn against something. A caller with a real forecast should supply it. The field says so.
Factor exposures without a factor file¶
Exposures need loadings, and there is no fundamentals data (that is the features layer, still to be designed). So the factors here are the ones that are derivable from price and share count:
- size: log market capitalisation
- momentum: trailing return, excluding the most recent month
- volatility: trailing standard deviation of returns
Value and quality are absent rather than approximated. A momentum factor built from prices is the real thing; a value factor faked without book values would not be, and labelling one as such would be worse than not having it.
constituent_prices ¶
constituent_prices(
fetcher: DataFetcher,
identifiers: list[str],
start: str | None = None,
end: str | None = None,
) -> pd.DataFrame
Close prices for a set of names, names on the columns.
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If none of them can be priced. |
expected_returns_from ¶
Annualised historical mean returns.
A poor forecast, and deliberately the honest one: it is what the data supports. Anything more sophisticated invented here would look like a view the server does not have.
factor_exposures ¶
Size, momentum and volatility loadings, standardised.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prices
|
DataFrame
|
Constituent prices. |
required |
fetcher
|
DataFetcher
|
For share counts. |
required |
as_of
|
Timestamp
|
Date the loadings are measured at. |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
pd.DataFrame: z-scored exposures, names on the index. |
build_optimisation_job ¶
build_optimisation_job(
run_id: str,
request: OptimisationRunRequest,
constraint_set: ConstraintSet,
constraints: list[Any],
target_weights: dict[str, float],
label_for: dict[str, str],
fetcher: DataFetcher,
) -> Callable[
[ProgressReporter], Awaitable[dict[str, Any]]
]
Build the coroutine that runs an optimisation.
Returns:
| Type | Description |
|---|---|
Callable[[ProgressReporter], Awaitable[dict[str, Any]]]
|
A coroutine function suitable for JobRegistry.submit. |
assemble_optimisation ¶
assemble_optimisation(
run_id: str,
request: OptimisationRunRequest,
constraint_set: ConstraintSet,
result: Any,
target_weights: dict[str, float],
label_for: dict[str, str],
prices: DataFrame,
risk_model: Any,
fetcher: DataFetcher,
) -> OptimisationRunResult
Build the wire payload from a completed solve.
Carries the prices' own summary rather than the frames themselves: the frontier and exposures panes re-derive what they need from the identifiers and the window, which keeps a stored run small.
build_frontier ¶
build_frontier(
run: dict[str, Any],
constraints: list[Any],
fetcher: DataFetcher,
risk_free_rate: float,
) -> FrontierView
Trace the frontier over the run's universe and window.
build_exposures ¶
Factor exposures of the active position, and its risk decomposition.
target_weights_from ¶
The index weights an optimisation is measured against.
Reads the rebalance in force on as_of from a stored backtest, so the optimiser and the weights pane agree about what the index held.
preview ¶
What an index definition resolves to at a date, in whichever face it has.
For a rule pipeline, that is the constituent derivation waterfall: how a universe narrows to an index, one rung per selection rule, each naming what it removed, then weighting and capping. The point is attributability: every excluded asset reports the rule that excluded it, so a methodology author can see why a name is missing rather than only that it is.
For a derivation there is no waterfall to show. An optimised index
eliminates nothing: it reallocates exactly the names its parent published, and
the solve moves every weight at once. Answering with an empty funnel would be a
worse lie than refusing, so the answer is a different shape: the parent's
weights beside the solved ones, and which constraints cost something. Both
faces come back through one endpoint and one response model, with steps and
solve mutually exclusive, exactly as pipeline and derivation are on the
document being previewed.
Neither face computes anything itself. The pipeline walk is
beacon.index.calculation.selection's, and the solve is
beacon.index.derived.solve_snapshot, the same call calculate_derived_index
makes at every rebalance. A preview and the run it previews cannot disagree,
because they are the same code.
build_preview ¶
build_preview(
document: IndexDocument,
fetcher: DataFetcher,
documents: DocumentStore,
as_of: str | None = None,
) -> PreviewResponse
Resolve an index definition at a date, in whichever face it has.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
IndexDocument
|
A validated index definition, rule-driven or derived. |
required |
fetcher
|
DataFetcher
|
Data source the rules, weighting scheme and solve read from. |
required |
documents
|
DocumentStore
|
Where a derivation's source is resolved from. Unused for a rule-driven document, and required all the same: the draft route previews a document that was never saved, so the store is the only way its parent can be found. |
required |
as_of
|
str | None
|
Date to evaluate at, YYYY-MM-DD. Defaults to the base date. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
PreviewResponse |
PreviewResponse
|
Per-asset outcomes and final weights, plus either the |
PreviewResponse
|
waterfall ( |
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If a derivation names a source that is not stored. |
InvalidRuleError
|
If a derivation's parent published no snapshot on or before as_of. |
CalculationError
|
If a derivation's solve is infeasible. The solver's own message names the binding conflict. |
reference ¶
Assembling a batch reference response.
Reference data for many identifiers in one request, so a universe table does not need one call per name.
Order is the request's order. A table renders rows in the order it asked for them, and a response sorted by identifier or by whatever the store happened to hold would force the client to re-sort against its own request. Every requested identifier gets exactly one entry at its requested position.
A miss is an entry, not a failure. One unknown ticker in five hundred must
not fail the batch: the table should render 499 rows and mark one unknown.
Entries carry found, so "we have no data for this" and "this name has no
value for that field" stay distinguishable.
Derived fields are requested by name alongside stored ones. adv_3m sits
in the same fields list as NAME and SECTOR, so a client asks for what it
wants to display in one place and reads the answer out of one mapping. It is
opt-in because computing it means slicing the price history for every
identifier in the batch, which is work nobody should pay for by default.
A money field is published twice, in two named currencies. Each money field
carries its local figure (market_cap_local, in local_currency) beside the
converted one (market_cap, in market_cap_currency), and the optional
currency parameter names what the converted one is converted into, USD when
the caller says nothing. The local number is a fact about the company, the
converted one is what compares to a weight, and publishing both means no
client has to choose and none can be misled by the choice. A missing rate
nulls the converted figure only: the local one is knowable whatever the FX
situation, and nulling it would hide something the server holds.
parse_list ¶
Split a repeatable query parameter into a clean, ordered list.
Accepts both repetition (?fields=A&fields=B) and the comma-separated
form (?fields=A,B), because both are natural to write and a client
should not have to know which one this server prefers. Applied to every
list parameter here rather than only to identifiers: handling one and not
the other is how ?fields=NAME,SECTOR ends up rejected as a single column
literally named "NAME,SECTOR".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
list[str] | None
|
Values as FastAPI parsed them. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[str]
|
Entries in request order, duplicates removed. |
parse_identifiers ¶
The identifiers parameter, validated against the batch limit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
list[str] | None
|
Values as FastAPI parsed them. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[str]
|
Identifiers, in request order, with duplicates removed. |
Raises:
| Type | Description |
|---|---|
InvalidRuleError
|
If none were supplied, or more than MAX_BATCH. |
parse_currency ¶
The currency parameter, validated, or the default when absent.
Raises:
| Type | Description |
|---|---|
InvalidRuleError
|
If it is not a three-letter code. An unknown-but- well-formed code converts to nothing and reports null, which is a data answer; "dollars" is a request that cannot be honoured, and silently nulling every converted figure for it would look like missing FX data rather than a typo. |
build_entries ¶
build_entries(
fetcher: DataFetcher,
identifiers: list[str],
date: str | None = None,
fields: list[str] | None = None,
currency: str = DEFAULT_CURRENCY,
) -> list[ReferenceEntry]
Assemble one entry per requested identifier, in request order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fetcher
|
DataFetcher
|
The data source. |
required |
identifiers
|
list[str]
|
What to look up, already validated. |
required |
date
|
str | None
|
Point-in-time date for reference validity. |
None
|
fields
|
list[str] | None
|
Stored columns and derived field names to return. None returns every stored column and no derived field: computing ADV for a batch nobody asked it for would be the endpoint's whole cost paid by every caller. |
None
|
currency
|
str
|
What the converted money fields are converted into, defaulting to USD. The local figures come back in the instrument's own currency whatever this says, so a caller that names nothing still gets both numbers and both labels. |
DEFAULT_CURRENCY
|
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[ReferenceEntry]
|
One |
Raises:
| Type | Description |
|---|---|
InvalidRuleError
|
If a requested stored column is not in the dataset. A silently absent column would show as an empty table row and be read as missing data rather than as a misspelled request. |
reports ¶
Rendering reports: the built-in factsheet, and rendering a stored template.
Two kinds of template, and the distinction matters:
- A stored template is a block list a user built. It is rendered exactly as saved, because that is what "I designed this page" means. Nothing is substituted into it.
- A built-in template is generated from a completed run.
FACTSHEET-A4is the first: it reads an index's latest backtest and lays out the headline figures, the constituents and the contribution chart.
Rendering runs as a job. Building a factsheet reads a stored run and derives
contributions from it, and the result is a PDF, which does not belong in a
JSON job payload. So the render writes a file under the storage root, the job
result carries its id, and GET /reports/renders/{render_id} streams it.
is_built_in ¶
Whether a template is generated rather than stored.
build_factsheet ¶
Lay out a one-page factsheet from a completed backtest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index_name
|
str
|
Display name for the header. |
required |
run
|
dict[str, Any]
|
A stored backtest result. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ReportTemplate |
ReportTemplate
|
The blocks, in reading order. |
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If the run carries no composition (an older stored result may have a level but no constituents), since a factsheet without holdings is not a factsheet. |
build_render_job ¶
build_render_job(
render_id: str,
request: RenderRequest,
template: ReportTemplate,
directory: Path,
) -> Callable[
[ProgressReporter], Awaitable[dict[str, Any]]
]
Build the coroutine that renders a template to a PDF.
Returns:
| Type | Description |
|---|---|
Callable[[ProgressReporter], Awaitable[dict[str, Any]]]
|
A coroutine function suitable for JobRegistry.submit. |
ensure_renderable ¶
Raise if a template obviously cannot produce a page.
Only the checks that are cheap and certain. Whether the blocks fit is the renderer's answer, and it gives a better one (naming the block that overflowed), so it is left to say it.
risk ¶
Estimating a risk model, and serving what it says about itself.
Estimation is a job because it means pulling a price history for every name in the universe before any matrix arithmetic happens. The read is cheap and serves the stored result.
The diagnostics are the interesting part¶
A correlation matrix looks equally plausible whether or not it can be trusted, so the endpoint reports how it was made and how well conditioned it is rather than only the numbers:
- intensity: how much weight went on the structured target. Zero means the raw sample covariance, which on a short history across many names is mostly noise.
- condition number: largest eigenvalue over smallest. An optimiser inverts this matrix, and a large condition number means the inverse amplifies estimation error rather than reflecting it.
- positive semi-definite: computed from the eigenvalues, not asserted. A matrix that fails this can produce a negative portfolio variance, and a caller about to invert it needs to know rather than be reassured.
average_correlation is reported alongside because it is the sanity check a
person can actually do: a diversified equity universe sits somewhere around
0.3–0.6, and a figure far outside that says the window or the universe is not
what someone thought.
constituent_returns ¶
constituent_returns(
fetcher: DataFetcher,
identifiers: list[str],
start: str | None,
end: str | None,
) -> pd.DataFrame
Daily returns for a set of names, names on the columns.
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If fewer than two names can be priced. A covariance over one asset is a variance, and the endpoint promises a matrix. |
build_estimation_job ¶
build_estimation_job(
model_id: str,
request: RiskModelRequest,
identifiers: list[str],
fetcher: DataFetcher,
) -> Callable[
[ProgressReporter], Awaitable[dict[str, Any]]
]
Build the coroutine that estimates a risk model.
Returns:
| Type | Description |
|---|---|
Callable[[ProgressReporter], Awaitable[dict[str, Any]]]
|
A coroutine function suitable for JobRegistry.submit. |
assemble_risk_model ¶
Build the wire payload from an estimated model.
routers ¶
HTTP routers, one module per resource group.
Each module exposes a build_router() that returns an APIRouter; the app
factory mounts them. Routers never construct their own data source: they read
it from application state, so a request cannot outlive or contradict the
process configuration.
build_beacon_router ¶
Build the /beacon router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the backtest submission endpoint. |
build_coverage_router ¶
Build the /data/coverage router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying coverage reporting and the sync endpoint. |
build_data_router ¶
Build the /data router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the prices and reference endpoints. |
build_derivatives_router ¶
Build the /derivatives router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the two pricing endpoints and the two |
APIRouter
|
index-level reads. |
build_indices_router ¶
Build the /indices router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying index list, read, validate, create and |
APIRouter
|
update. |
build_events_router ¶
Build the /ws event feed.
Kept separate from the HTTP router because the bearer dependency that guards every other route takes a Request and cannot run on a WebSocket handshake. This router is mounted unguarded and authorises itself.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the WebSocket endpoint. |
build_jobs_router ¶
Build the /jobs router and the /ws event feed.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying job polling, cancellation and the socket. |
build_optimise_router ¶
Build the /optimise router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying constraint-set CRUD, run submission, and the |
APIRouter
|
frontier and exposures reads. |
build_refresh_router ¶
Build the /data/stores/{store_id}/refresh route.
build_reports_router ¶
Build the /reports router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying template CRUD, the render job, and the |
APIRouter
|
rendered-document download. |
build_risk_router ¶
Build the /risk-models router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying model reads and the estimation job. |
build_universes_router ¶
Build the /universes router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying universe list, read, members, upsert and |
APIRouter
|
delete. |
build_watchlists_router ¶
Build the /data/watchlists router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying watchlist list, read, upsert and delete. |
beacon ¶
Beacon View: running an index's backtest, and reading the result.
A backtest calculates the whole index and then simulates a tracking portfolio
day by day, which is far too slow to hold an HTTP connection open for. The
submission endpoint therefore returns a job id; the client polls
GET /jobs/{id} or listens on /ws.
The read endpoints are the other half. They serve the panes of the view (overview, weights, attribution, a single name, and a comparison across indices), and each answers from the most recent successful run of that index rather than recalculating anything. Job results are persisted and the run payload carries composition, so a client switching tabs does not wait on a recalculation, and two panes read a moment apart describe the same run.
Every read is a 404 until a backtest has been run, which is the honest answer: there is no view of an index nobody has calculated.
build_beacon_router ¶
Build the /beacon router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the backtest submission endpoint. |
coverage ¶
Data-coverage reporting and the sync job.
Coverage reports whether each dataset is loaded, how many identifiers it holds,
the span of dates it covers, and when it was last refreshed. A null age means
the dataset is not loaded at all, which is a different statement from "loaded
and never refreshed"; see decisions/0002-caching-and-data-freshness.md.
POST /{dataset}/sync is deprecated. It refreshes the active store from its
own source, the same as POST /data/stores/{store_id}/refresh.
build_coverage_router ¶
Build the /data/coverage router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying coverage reporting and the sync endpoint. |
data ¶
Data router: prices and reference data.
There is no /data/fundamentals endpoint: fundamentals will be served by a
general features endpoint covering any per-instrument datapoint that is
neither reference data nor a corporate action and that can drive a backtest or
an index rule. That endpoint is not designed yet.
/data/corporate-actions returns the raw actions plus the aggregates that need
the whole series (a trailing dividend, its yield, and the compounded split
ratio), so a client does not reimplement the trailing window and get its
boundary wrong.
build_data_router ¶
Build the /data router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the prices and reference endpoints. |
derivatives ¶
Derivatives pricing endpoints.
Stateless by contract. The two pricing endpoints read nothing and write nothing: a request carries every input, and the response is a pure function of it. That is what makes them safe to call from a form on every keystroke, and a test asserts the storage directory is untouched afterwards rather than trusting the claim.
The term-structure and roll reads resolve a spot price from the data source. They still write nothing.
build_derivatives_router ¶
Build the /derivatives router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the two pricing endpoints and the two |
APIRouter
|
index-level reads. |
importing ¶
Load a user's own data from CSV files or an Excel workbook into a new store.
The engine runs on the user's own machine, so it takes the files by path, the way a data folder is registered: nothing is copied through the request, and large files cost nothing extra to send.
Every row is checked before anything is saved. If any is wrong, the answer is
422 INVALID_RULE with one finding per problem, each naming its sheet, row and
column. Otherwise the data is saved as a new store the engine owns (managed),
and loaded straight away unless activate is false.
indices ¶
Index definition CRUD with structured validation.
A rejected save returns findings, not a bare 422. A user editing a pipeline needs every problem at once, each addressable to the rule that caused it, so the client can mark the offending row rather than showing one message for the whole form.
load_index ¶
Read an index definition, or answer not-found.
Not-found covers a definition that is absent, one that is not valid JSON,
and one that does not satisfy IndexDocument (the third being what every
stored document hits the day a required field is added to the model). The
listing skips exactly the same documents, so the two cannot
disagree about what exists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
The incoming request. |
required |
index_id
|
Identifier
|
Identifier of the definition. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
IndexDocument |
IndexDocument
|
The stored definition. |
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If it cannot be served. |
build_schedule ¶
build_schedule(
document: IndexDocument,
as_of: str | None = None,
limit: int = SCHEDULE_HORIZON_PERIODS,
) -> ScheduleView
Derive an index's rebalance schedule around a date.
Derived rather than stored: the next rebalance is a function of the schedule, the calendar and today, so storing it would leave a date that silently expires.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
IndexDocument
|
The index definition. |
required |
as_of
|
str | None
|
The date to answer from; defaults to today. |
None
|
limit
|
int
|
Most dates to serve in each of |
SCHEDULE_HORIZON_PERIODS
|
Returns:
| Name | Type | Description |
|---|---|---|
ScheduleView |
ScheduleView
|
The next rebalance, days until, and a strip of dates |
ScheduleView
|
either side with the counts they were trimmed from. |
build_indices_router ¶
Build the /indices router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying index list, read, validate, create and |
APIRouter
|
update. |
jobs ¶
Job polling and the WebSocket event feed.
Polling and the socket report the same state: the socket is a latency
optimisation, not a separate source of truth. A client that misses a frame can
always fall back to GET /jobs/{id}, and one that cannot hold a socket open
loses nothing but immediacy.
There is no endpoint here that creates a job. Jobs are submitted by the endpoints that own the work (backtests, optimisation, risk, reports, data loads and refreshes), so a bare "start a job" route would be dead weight and an easy way to spawn work with no purpose.
build_jobs_router ¶
Build the /jobs router and the /ws event feed.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying job polling, cancellation and the socket. |
build_events_router ¶
Build the /ws event feed.
Kept separate from the HTTP router because the bearer dependency that guards every other route takes a Request and cannot run on a WebSocket handshake. This router is mounted unguarded and authorises itself.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the WebSocket endpoint. |
optimise ¶
Optimiser endpoints: constraint sets, runs, frontier and exposures.
Constraint sets are stored documents like indices and watchlists. A run is a job, because the solve itself is fast but the risk model it needs is not: that means a price history for every constituent and a covariance built from it.
The frontier and exposures panes read a completed run rather than re-solving, which is the same arrangement as the Beacon View endpoints and for the same reason: a client switching tabs should not wait on a recalculation.
Validation happens before the job. A malformed constraint set is a bad request and the client should learn that from the submission, not from a job that fails a moment later. It reports every problem it finds, addressed to the row that caused it, because someone fixing a constraint editor needs all the errors rather than the first.
build_optimise_router ¶
Build the /optimise router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying constraint-set CRUD, run submission, and the |
APIRouter
|
frontier and exposures reads. |
refresh ¶
Refresh a data store from its own source.
What a refresh does depends on the store:
- Synthetic data is extended to today, keeping every day it holds. This
runs
python -m beacon.synthetic --extendin a child process, as generation does. - A folder is read again, picking up files changed outside the engine.
- A database has its tables read again.
- Imported files have nothing to refresh; importing again makes a new store. The request is refused.
- A folder set to refresh from Yahoo Finance downloads new prices for its instruments and saves them into the folder. Yahoo is never the default: a store uses it only when the user chose it for that store.
Whatever a refresh changes is saved, so it survives a restart. If the store
is the one being served, the engine then serves the refreshed data, with a
new data_version.
build_refresh_router ¶
Build the /data/stores/{store_id}/refresh route.
submit_refresh ¶
submit_refresh(
request: Request,
record: dict[str, Any],
end: str | None = None,
) -> RefreshJobStatus
Check a refresh can run, and start it as a job.
Shared with the deprecated POST /data/coverage/{dataset}/sync, which
refreshes the store being served.
download_into ¶
download_into(
path: Path,
downloader: Downloader,
on_progress: Callable[[int, int, str], None]
| None = None,
) -> tuple[int, str | None]
Download prices after a folder store's last date, and save them into it.
The instruments are the ones its reference data lists, or, without reference data, every identifier it prices.
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
int
|
Market rows added, and the last date the store now holds. |
str | None
|
Nothing is written when no rows were added. |
reports ¶
Report templates and rendering.
Templates are stored documents. Rendering is a job, because the result is
bytes, and bytes do not belong in a JSON job payload, so the render writes a
file and the job result carries its id for GET /reports/renders/{id} to
stream.
Two kinds of template. A stored one is rendered exactly as saved, because that
is what "I designed this page" means. A built-in one (FACTSHEET-A4) is
generated from an index's latest run. See beacon.server.reports for why there
is no templating language in between.
build_reports_router ¶
Build the /reports router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying template CRUD, the render job, and the |
APIRouter
|
rendered-document download. |
risk ¶
Risk-model endpoints.
Estimation is a job: it means pulling a price history for every name in the universe before any matrix arithmetic happens. Reading a finished model is cheap and serves the stored result, the same arrangement as backtests and optimisation runs.
The model id is chosen by the caller rather than generated, so a client can re-estimate "the one I use for tech" over a new window and keep referring to it by the same name. Each estimate supersedes the last under that id.
build_risk_router ¶
Build the /risk-models router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying model reads and the estimation job. |
stores ¶
Named data stores: list, register, rename, forget, and load one.
Loading runs as a job, like a backtest: reading a large store takes seconds,
and the client watches its progress on the event socket. When it finishes,
the engine serves the new store's data, remembers it for the next start, and
publishes a data.loaded event.
A request already running when a load finishes keeps the data it started with: the swap replaces the engine's data whole, it never changes it in place.
record_or_404 ¶
A store's record, or 404 naming it.
remove_managed_folder ¶
Delete a folder the engine created, and nothing outside its own root.
The check is the point: a registry record could have been edited by hand, and deleting whatever path it names would make one bad file a way to remove anything on the disk.
refuse_if_refreshing ¶
Refuse to load or forget a store while a refresh is rewriting it (409).
serve_store
async
¶
Load a store and start serving it: the one path for every load.
Used by activation and by generation, so a store the engine just wrote is
served exactly as one the user picked. The caller has set
active_data.loading; this clears it however it ends. A failed load
leaves the data already served in place.
claim_loading ¶
Mark a load as started, or refuse when one already is (409).
Set before the job is scheduled, so a second request arriving before the job starts is refused rather than racing it.
load_store_job ¶
The coroutine that loads a store and starts serving it.
synthetic ¶
Generate synthetic data into a new store, as a job.
The engine generates on request, so an app can start the engine first and offer "Generate synthetic data" when nothing is loaded.
The job runs python -m beacon.synthetic in a child process rather than the
generator in this process, for three reasons:
- One path. The engine runs the same command a person would, so the same settings always give the same data, whichever way it was made.
- Memory. The default size peaks at about 2.5 GB. A child process gives it all back when it exits; a long-running engine might not.
- Failure. If generation runs out of memory or crashes, the child dies and the engine carries on. Cancelling the job kills the child.
The child prints a progress line per stage (--progress), which the job
reports on the event socket.
command ¶
The command line that generates config into out.
Every setting is passed explicitly, dates included, so the child generates exactly what was validated, even if it runs past midnight.
extend_command ¶
The command line that extends the synthetic store at path.
run_synthetic
async
¶
Run python -m beacon.synthetic in a child process, reporting progress.
Generates a store or extends one, for the reasons in the module docstring.
Raises:
| Type | Description |
|---|---|
CalculationError
|
If the command exits unsuccessfully, carrying the end of its own output. |
universes ¶
Universes: named sets of instrument identifiers.
A universe is a server-side concept. The library has no universe object (an
IndexDefinition carries a plain list of identifiers), so these documents
exist to let several definitions share one curated list rather than each
repeating it.
Members are checked against the loaded data. A universe naming an
instrument the server has no data for is a universe that produces an empty
index and no explanation. Both POST and PUT resolve every member against
the loaded data and refuse the ones that are not there, as findings naming
each missing identifier, in the shape the index editor already renders. A bare
422 would tell somebody a list of five hundred tickers was wrong without
saying which one.
Seeded universes are read-only. The synthetic generator writes a GLOBAL
universe covering everything it produced, so a fresh workspace has something
to select. It is marked source: "seeded" and refuses edits: it derives from
the dataset, so regenerating would discard whatever had been changed. Refusing
now beats losing it later.
load_universe ¶
Read a universe or raise the mapped not-found error.
Shared with the indices router, which resolves a universe reference when saving a definition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
The incoming request. |
required |
universe_id
|
Identifier
|
Identifier of the universe. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Universe |
Universe
|
The stored universe. |
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If no such universe exists, or the stored document cannot be parsed or validated. The listing skips exactly those, so the two surfaces agree by construction. |
seed_global_universe ¶
Write the GLOBAL universe for a loaded dataset.
Idempotent, and deterministic: the members are the dataset's identifiers in sorted order, so regenerating with the same seed reproduces the same document byte for byte.
Rewritten when the dataset's membership changes -- a store swapped for a larger one should not leave GLOBAL describing the old one -- but left alone otherwise, so the file's mtime does not churn on every boot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
DocumentStore
|
The universe document store. |
required |
fetcher
|
Any
|
The loaded data source. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
Whether anything was written. |
build_universes_router ¶
Build the /universes router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying universe list, read, members, upsert and |
APIRouter
|
delete. |
watchlists ¶
Watchlist CRUD, persisted through the DocumentStore.
Watchlists are user-authored and must outlive the process, so they go to disk rather than to application state.
build_watchlists_router ¶
Build the /data/watchlists router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying watchlist list, read, upsert and delete. |
runs ¶
Reading a stored run's composition.
Three small readers, in their own module because both views and weights
need them and neither should have to import the other to get at them. Every
view of a completed backtest starts by turning the stored payload back into
snapshots, so this is the shared floor rather than a utility drawer.
snapshots_from ¶
Read the rebalance snapshots off a stored run.
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If the run carries none. An older stored result may have a level and metrics but no composition, and saying so is better than serving an empty index. |
weight_map ¶
weight_map(
snapshots: list[RebalanceSnapshot],
uncapped: bool = False,
) -> dict[pd.Timestamp, dict[str, float]]
Snapshots keyed by timestamp, as the analysis helpers expect.
snapshot_at ¶
The rebalance in force on a date.
The latest snapshot at or before as_of, because an index holds the weights set at its last rebalance until the next one. A date before the first rebalance has no answer and says so rather than returning the first, which would report weights that were not yet in force.
schemas ¶
Wire schemas for the Beacon API.
Library result objects are dataclasses holding pandas structures. They are never exposed directly: their field names and types are internal and would otherwise become an API contract by accident. Everything crossing the wire is declared here, so OpenAPI describes it and a library refactor cannot silently reshape a response.
SkippedCauses ¶
Bases: BaseModel
Why a listing left documents out, one count per cause.
Each cause has its own remedy, so each has its own count and a description that names it.
from_counts
classmethod
¶
Publish a listing's SkipCounts.
TolerantCollection ¶
Bases: BaseModel
A listing that leaves out what it cannot read, and says so.
The base of every collection the server lists from the store.
skips
staticmethod
¶
Both fields from one SkipCounts, so they cannot disagree.
Spread into a collection's constructor. Taking the total and the
breakdown from the same object is what keeps skipped equal to the
sum of its causes -- two arguments computed separately at seven call
sites is seven places for them to drift.
Money ¶
Bases: BaseModel
An amount with its denomination.
A bare float would leave the currency implicit, which breaks as soon as a response mixes denominations.
SeriesPayload ¶
Bases: BaseModel
A pandas Series on the wire.
TableFrame ¶
Bases: BaseModel
A pandas DataFrame on the wire.
Row-oriented so column order is preserved and the payload stays compact.
from_dataframe
classmethod
¶
Build from a pandas DataFrame.
DataSourceStatus ¶
Bases: BaseModel
Whether data is loaded, how much, and from where.
ChangelogSectionView ¶
Bases: BaseModel
One heading under a release and its items.
ChangelogEntryView ¶
Bases: BaseModel
One release of the engine.
ChangelogResponse ¶
Bases: BaseModel
Response of GET /changelog.
HealthResponse ¶
Bases: BaseModel
Response of GET /health.
CalendarCoveragePayload ¶
Bases: BaseModel
The window asked for beside the one the calendar could offer.
Present only when the calendar narrowed the run, so its presence is the signal. A window the calendar covers nothing of never reaches a result at all: that refuses, because an empty index is a failure wearing a success.
from_coverage
classmethod
¶
Build from the library's CalendarCoverage.
Only ever called for a partial cover, where both covered ends are set. An empty one refuses before a result exists.
IndexResultSummary ¶
Bases: BaseModel
Serialised view of an IndexResult.
from_result
classmethod
¶
Build from a library IndexResult.
BacktestMetrics ¶
Bases: BaseModel
Headline metrics from a backtest run.
Tracking figures are null when the run had no target index to compare to.
RebalanceSnapshot ¶
Bases: BaseModel
The index's composition at one rebalance.
Both weight sets are carried. weights is what the index applied;
uncapped_weights is what the weighting scheme produced before any cap.
They are equal on an uncapped index, and the difference is the only way to
answer what capping cost, a question that cannot be reconstructed from the
applied weights alone.
The run payload publishes these snapshots as rebalances, and the stored
backtest record publishes the same rows on each index book.
PortfolioBookPayload ¶
Bases: BaseModel
The portfolio's books on the wire.
No rebalances here, unlike the index books: a portfolio makes no
rebalance decision of its own (it trades toward one), so decided weights
would be a field with nothing honest to put in it.
BookPayload ¶
Bases: BaseModel
One comparator's record on the wire.
Two different facts about weights, not two copies of one: weights is
the daily panel (what the book actually HELD each day, drift included)
and rebalances is what each rebalance DECIDED. They agree only
on a rebalance date; everywhere else prices have moved the held weights
away from the decided ones. A client wanting decided weights reads
rebalances rather than resampling the panel, which cannot answer what
capping cost whatever it is resampled onto.
IndexBooksPayload ¶
Bases: BaseModel
The run's calculated indices on the wire.
Mirrors the library's IndexBooks: target is the index being aimed
at, pre-optimisation; optimised is the solved index's own
calculation, null until an optimised run fills it.
UnfilledOrderPayload ¶
Bases: BaseModel
A buy the simulation could not execute in full.
PriceGapPayload ¶
Bases: BaseModel
A day a name had no bar on a session that should have had one.
RebalancePricingPayload ¶
Bases: BaseModel
The session one rebalance's trades were priced from.
BacktestResultSummary ¶
Bases: BaseModel
Serialised view of a BacktestResult, in the shape of its books.
The nested shape mirrors the library object, one home per fact. Books the
run did not have (no benchmark given, no index calculated) are null rather
than empty, so a client can tell "not measured" from "measured and empty".
index is a container of two books, {target, optimised}, matching the
library's IndexBooks.
from_result
classmethod
¶
Build from a library BacktestResult.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
BacktestResult
|
The finished run. |
required |
cap
|
float | None
|
The maximum constituent weight declared by the definition whose rules produced the target book: the document's own on a passive run, its parent's on an optimised one. Stamped on that book's rebalance snapshots only: a solved index has no cap of its own, since its constraints are what shaped its weights. |
None
|
BacktestRecordRow ¶
Bases: BaseModel
One stored backtest record, as a listing knows it.
The row is deliberately thin: the id to fetch the record by, and when it
was captured. Names come from the index catalogue the client already
holds, and everything else from /beacon/{index_id}/record.
BacktestRecordCollection ¶
Bases: TolerantCollection
Response of GET /beacon/backtests.
An envelope rather than a bare array, so the listing can say how many stored records it left out.
PricesResponse ¶
Bases: BaseModel
Response of GET /data/prices/{identifier}.
from_frame
classmethod
¶
Build from a date-indexed market-data frame.
FeatureValue ¶
Bases: BaseModel
One field and what it was worth.
FeatureResponse ¶
Bases: BaseModel
Response of GET /data/features/{identifier}.
FeatureBatchEntry ¶
Bases: BaseModel
One instrument in a batch feature response.
FeatureBatchResponse ¶
Bases: BaseModel
Response of GET /data/features.
FeatureTypeCoverage ¶
Bases: BaseModel
One feature dataset, and how much of it is present.
FeatureCatalogue ¶
Bases: BaseModel
Response of GET /data/features/catalogue.
What a client populates its controls from. Derived from the loaded data rather than a fixed vocabulary, so a dataset somebody loads tomorrow becomes a filter without a code change.
FieldDescriptor ¶
Bases: BaseModel
One datapoint a client can offer as a filter.
FieldCatalogue ¶
Bases: BaseModel
Response of GET /data/fields.
Every datapoint an expression can name, from one place, so a client builds one field picker rather than one per dataset. Derived from the loaded store, so a column or dataset nobody declared still appears.
TablePage ¶
Bases: BaseModel
Response of GET /data/tables/{dataset}.
The stored data as it is, before any view shapes it. Paged because the default synthetic store holds 11.8M market rows, and an unbounded dump is not something a client can render or an engine should assemble.
FeatureRow ¶
Bases: BaseModel
One row of an import.
FeatureImport ¶
Bases: BaseModel
Body of POST /data/features.
FeatureImportResult ¶
Bases: BaseModel
What an import did.
UniverseMembership ¶
Bases: BaseModel
One universe an instrument belongs to.
ReferenceResponse ¶
Bases: BaseModel
Response of GET /data/reference/{identifier}.
Fields are whatever columns the loaded reference data carries (the library does not impose a schema on it), so they are returned as a mapping rather than as named attributes.
from_row
classmethod
¶
from_row(
identifier: str,
row: Series,
universes: list[UniverseMembership] | None = None,
) -> ReferenceResponse
Build from a single reference-data row.
IdentifierMatch ¶
Bases: BaseModel
One identifier a search or enumeration returned.
IdentifierSearchResponse ¶
Bases: BaseModel
Response of GET /data/identifiers.
Search when q is given, enumeration when it is not.
Ranking is decided server-side and is part of the contract: exact
identifier, identifier prefix, name prefix, identifier substring, name
substring, alphabetical within each. Once limit is applied a client
cannot re-rank what it was not sent.
ReferenceEntry ¶
Bases: BaseModel
One identifier's row in a batch reference response.
BatchReferenceResponse ¶
Bases: BaseModel
Response of GET /data/reference.
Entries are in the order the request named them, one per identifier, so a table can render straight down the list without re-sorting against what it asked for.
CorporateAction ¶
Bases: BaseModel
One corporate action.
kind is the authoritative answer to what value means, and the reason a
client needs no list of type strings. Reading type and inferring cash or
ratio from a hardcoded list works until a type the client has never seen
arrives, at which point it renders as whichever the list defaults to:
confidently, and wrongly.
CorporateActionsResponse ¶
Bases: BaseModel
Response of GET /data/corporate-actions/{identifier}.
Carries the raw history and the two aggregates that need the whole series to compute, so a client asking "what did this pay" does not have to reimplement the trailing window and get its boundary subtly wrong.
from_frame
classmethod
¶
from_frame(
identifier: str,
frame: DataFrame,
trailing_dividend: float,
trailing_dividend_yield: float | None,
cumulative_split_ratio: float,
) -> CorporateActionsResponse
Build from a corporate-action history slice.
SyncRequest ¶
Bases: BaseModel
Body of POST /data/coverage/{dataset}/sync.
Deprecated with the endpoint. Its fields are accepted and ignored: a sync
refreshes the whole active store from its own source, as
POST /data/stores/{id}/refresh does.
Watchlist ¶
Bases: BaseModel
A named set of instrument identifiers.
WatchlistUpsert ¶
Bases: BaseModel
Body of PUT /data/watchlists/{id}.
The id comes from the URL, so it is not repeated here: accepting it in both places invites the two to disagree.
WatchlistCollection ¶
RuleSpec ¶
Bases: BaseModel
One rule in the pipeline, addressable by its id.
WeightingSpec ¶
Bases: BaseModel
The weighting group of the pipeline.
TreatmentSpec ¶
Bases: BaseModel
The treatment group of the pipeline.
PipelineSpec ¶
Bases: BaseModel
The grouped rule pipeline: Selection, Weighting, Treatment.
UniverseRef ¶
Bases: BaseModel
Where an index's universe comes from.
Either a reference to a stored universe or a literal list. identifiers
is always populated on read, so consumers never have to resolve it.
ConstraintRow ¶
Bases: BaseModel
One constraint, in the shape a client's editor holds it.
Maps 1:1 to a class in beacon.optimise.constraints: the row a user edits,
the JSON that is stored and the object the solver receives are the same
thing in three representations, so a rule cannot change meaning in
translation.
DerivationPayload ¶
Bases: BaseModel
How an optimised index is derived from the index it was built on.
The whole of an optimised index's methodology: the source it reallocates, what the solve minimises, and what the answer must satisfy. No weights (neither the parent's nor the solved ones), because definitions are rules and weights are calculated.
IndexDocument ¶
Bases: BaseModel
A stored index definition, in one of its two faces.
A document carries either a rule pipeline (pipeline and universe)
or a derivation, never both and never neither. derivation is the
discriminator: present, the index is optimiser-derived and its methodology
is the derivation; absent, it is a rule pipeline over a universe.
Finding ¶
Bases: BaseModel
One validation result, addressable to the rule that caused it.
ValidationReport ¶
Bases: BaseModel
Response of the validation endpoint, and of a rejected save.
IndexCollection ¶
OptimiseRequest ¶
Bases: BaseModel
Body of POST /indices/{index_id}/optimise.
Everything the derived index needs that the parent cannot supply. The source is deliberately absent: provenance is server-truth, taken from the URL, so a client cannot assert a parentage the server did not create.
DeletedIndex ¶
Bases: BaseModel
One index a delete removed, and what went with it.
IndexDeletion ¶
Bases: BaseModel
Response of DELETE /indices/{index_id}.
Everything the delete removed, so a client reports the outcome from the response rather than from its own prediction of the blast radius. The named index comes first, then each optimised child in the order the cascade reached it.
ReportTemplateDocument ¶
Bases: BaseModel
A stored report template, as JSON.
Blocks are kept as free-form mappings rather than a discriminated union so
the wire shape stays exactly what beacon.report.blocks reads and writes.
A second definition of the same thing here is a second definition to keep
in step, and the block model already validates its own rows on the way in.
ReportTemplateCollection ¶
RenderRequest ¶
Bases: BaseModel
Body of POST /reports/render.
RenderResult ¶
Bases: BaseModel
Result payload of a completed render job.
FuturesPriceRequest ¶
Bases: BaseModel
Body of POST /derivatives/futures/price.
Stateless: every input the calculation needs is here, and nothing is read from or written to storage.
CarryDecomposition ¶
Bases: BaseModel
Carry split into the pieces a person can reason about.
Each part is the price effect of one rate acting alone. They do not sum to the total exactly, because carry compounds rather than adds; the residual is reported rather than spread across the parts, which would make each of them slightly wrong in order to hide that the split is approximate.
FuturesPriceResponse ¶
Bases: BaseModel
Response of POST /derivatives/futures/price.
TrsPriceRequest ¶
Bases: BaseModel
Body of POST /derivatives/trs/price.
TrsAccrual ¶
Bases: BaseModel
One financing period.
TrsPriceResponse ¶
Bases: BaseModel
Response of POST /derivatives/trs/price.
TermStructureEntry ¶
Bases: BaseModel
One expiry in a term structure.
TermStructureResponse ¶
Bases: BaseModel
Response of GET /derivatives/{index_id}/term-structure.
RollResponse ¶
Bases: BaseModel
Response of GET /derivatives/{index_id}/roll.
Both legs are priced theoretically off the same spot and curve, so this is the carry roll rather than a market one.
RiskModelRequest ¶
Bases: BaseModel
Body of POST /risk-models/{model_id}/estimate.
RiskDiagnosticsPayload ¶
Bases: BaseModel
How an estimate was produced, and how far it can be trusted.
RiskModelView ¶
Bases: BaseModel
Response of GET /risk-models/{model_id}.
RiskModelSummary ¶
Bases: BaseModel
One entry in GET /risk-models.
RiskModelCollection ¶
Bases: BaseModel
Response of GET /risk-models.
ConstraintSet ¶
Bases: BaseModel
A named list of constraints.
ConstraintSetCollection ¶
SavedConstraintSet ¶
Bases: BaseModel
Response of a successful save: the set plus any warnings.
ParameterSpec ¶
Bases: BaseModel
One parameter of a configurable type, described well enough to render.
Names, types, defaults and whether a parameter is required are read from the constructor, so they cannot drift from what the code accepts. Labels, ordering and choices are declared on the class, because a signature cannot carry them.
TypeSpec ¶
Bases: BaseModel
One configurable type a client can offer.
RuleTypes ¶
Bases: BaseModel
Response of GET /indices/rule-types.
Everything a methodology editor needs to render a real form: which rules
and schemes exist, what each takes, and how to label and order the fields.
Without it RuleSpec.type is a free-text box and params a list of
key/value pairs, so a misspelled parameter is only discovered on submit.
CalendarOption ¶
Bases: BaseModel
One selectable trading calendar, as GET /indices/calendars serves it.
Every field is derived from exchange_calendars at request time except
name, which is curated and falls back to the code. Nothing here is a
hand-kept table of the calendar set itself, so the options a client offers
cannot drift from the calendars the schedule accepts.
CalendarList ¶
Bases: BaseModel
Response of GET /indices/calendars.
IndexDocument.calendar is required, so this publishes the calendars the
engine accepts, the way /indices/rule-types and
/optimise/constraint-types publish theirs. It is read from
exchange_calendars at request time, never a hand-kept copy, so the wire
set cannot drift from the set the calculation schedules on.
ConstraintTypes ¶
Bases: BaseModel
Response of GET /optimise/constraint-types.
Served so a client builds its editor from the same source the solver reads, rather than from a copy that drifts.
OptimisationRunRequest ¶
Bases: BaseModel
Body of POST /optimise/runs.
WeightRow ¶
Bases: BaseModel
One name's index, optimal and active weight.
OptimisationRunResult ¶
Bases: BaseModel
Result payload of a completed optimisation job.
FrontierPoint ¶
Bases: BaseModel
One portfolio on the efficient frontier.
FrontierView ¶
Bases: BaseModel
Response of GET /optimise/runs/{run_id}/frontier.
FactorExposure ¶
Bases: BaseModel
One factor loading.
RiskDecomposition ¶
Bases: BaseModel
Active risk split into factor and specific parts.
The two sum to the total exactly, because the covariance is defined as
B F Bᵀ + D. Pair an arbitrary covariance with arbitrary loadings and
there is a cross term; the identity belongs to this model and not to any
pairing of a matrix with some exposures.
ExposuresView ¶
Bases: BaseModel
Response of GET /optimise/runs/{run_id}/exposures.
Factors are the ones derivable from price and share count (size, momentum, volatility), plus a market intercept. Value and quality are absent rather than approximated: a momentum factor built from prices is the real thing, a value factor faked without book values would not be.
SavedIndex ¶
Bases: BaseModel
Response of a successful save: the document plus any warnings.
FieldNode ¶
Bases: BaseModel
A named datapoint: expressions.core.Field.
namespace is the surface the value comes from and dataset narrows a
feature to one vendor's TYPE, so two sources can both ship a revenue
without collision.
ComparisonNode ¶
Bases: BaseModel
A field, an operator and a value: expressions.core.Comparison.
AllNode ¶
Bases: BaseModel
Every operand must pass: expressions.core.All.
AnyNode ¶
Bases: BaseModel
At least one operand must pass: expressions.core.Any_.
NotNode ¶
Bases: BaseModel
The negation of an expression: expressions.core.Not.
ExpressionNode ¶
Bases: RootModel[Annotated[FieldNode | ComparisonNode | AllNode | AnyNode | NotNode, Field(discriminator='node')]]
One node of a serialised expression, discriminated on node.
The grammar a screen is written in: a field, a comparison over one, or a
boolean composition of either. Recursive (all, any and not carry
nodes of this same union), so an arbitrarily nested screen is one type.
A RootModel rather than a bare union so the union is a named schema in
this document: a recursive $ref needs a name to point at, and so does
ParameterSpec.ref.
Universe ¶
Bases: BaseModel
A named set of instrument identifiers.
UniverseUpsert ¶
Bases: BaseModel
Body of PUT /universes/{id}.
UniverseCreate ¶
Bases: BaseModel
Body of POST /universes.
No id: the server derives one from the name, so a client cannot create two universes whose ids differ only in punctuation and expect them to be distinct documents.
UniverseCollection ¶
UniverseMembers ¶
Bases: BaseModel
Response of GET /universes/{id}/members.
ScheduleView ¶
Bases: BaseModel
Response of GET /indices/{index_id}/schedule.
Derived, not stored: the next rebalance is a function of the schedule, the calendar and today, and storing it would leave a date that silently expires.
PreviewRequest ¶
Bases: BaseModel
Body of POST /indices/{id}/preview, which previews the saved index.
PreviewDocumentRequest ¶
Bases: BaseModel
Body of POST /indices/preview, which previews a document as supplied.
The route for a draft. The by-id route reads what is stored, so while an editor holds unsaved changes its figures describe the old definition, with nothing on screen to say they are stale. This one previews exactly what was sent, so editing a rule updates the resolved figures without saving.
PreviewStep ¶
Bases: BaseModel
One rung of the derivation waterfall.
There is one of these per selection rule, in pipeline order, plus a first entry for the universe itself so the funnel starts from a stated total.
PreviewAsset ¶
Bases: BaseModel
Per-asset outcome of the derivation.
Two disjoint groups of fields, matching the two faces of a preview. The
rule-provenance fields (excluded_by, excluded_at, uncapped_weight,
capped) describe a walk down a pipeline and are null on a derived
preview, which has no rules to attribute anything to. The derived fields
(source_weight, solved_weight, weight_delta) describe a reallocation
and are null on a rule-driven one. Neither group was overloaded to carry
the other's meaning: a client reading excluded_by on a derived index
would be reading an answer to a question nobody asked.
PreviewConstraint ¶
Bases: BaseModel
One constraint at the solved point: whether it bound, and its room.
Every constraint appears, not only the binding ones. A binding constraint's slack is zero by definition, so a report of only those is a list of zeros; what a reader actually wants beside "this cap bound" is "and the turnover budget had four points of room left".
PreviewSolve ¶
Bases: BaseModel
What the optimiser did at one rebalance: the derived face of a preview.
A derivation has no waterfall: the solve moves every weight at once rather than eliminating names in steps, so there are no rungs to show. This is the honest analogue: which parent snapshot was solved, under what, and which rules cost something.
PreviewResponse ¶
Bases: BaseModel
Response of POST /indices/{id}/preview, in one of its two faces.
Exactly one of steps and solve is present, mirroring pipeline and
derivation on the document the preview was built from: a rule-driven
index answers with the waterfall, a derived one with the solve. solve is
the discriminator, and it is the same discriminator the client already
branches on one level up.
Everything outside the pair is common to both: the resolved weights, their total, and one row per name.
BenchmarkRef ¶
Bases: BaseModel
What to compare a backtest against.
Distinct from the index being tracked. The tracked index measures replication accuracy; a benchmark measures relative performance against something the portfolio was never trying to replicate.
RelativeMetricsPayload ¶
Bases: BaseModel
Performance against a benchmark, over their shared window.
BacktestRequest ¶
Bases: BaseModel
Body of POST /beacon/{index_id}/backtest.
ConcentrationPayload ¶
Bases: BaseModel
How concentrated a weight vector is.
DriftPayload ¶
Bases: BaseModel
How far weights moved between two rebalances.
OverviewView ¶
Bases: BaseModel
Response of GET /beacon/{index_id}/overview.
ConstituentRow ¶
Bases: BaseModel
One constituent's row in the weights table.
Everything a row needs is here, so the table renders from one response
rather than joining three. The two weights are the point: raw_weight is
what the weighting scheme produced, weight is what survived the cap, and
the difference is what capping moved.
RiskPayload ¶
Bases: BaseModel
How the index's volatility divides among its holdings.
Contributions sum to volatility exactly rather than approximately: the
decomposition is an identity, so a client can show the parts and the whole
without them disagreeing.
ActiveRiskPayload ¶
Bases: BaseModel
How tracking error against a benchmark divides among active positions.
Contributions sum to tracking_error exactly, the same identity the total
decomposition satisfies, on active weights rather than holdings.
WeightsView ¶
Bases: BaseModel
Response of GET /beacon/{index_id}/weights.
ContributionPayload ¶
Bases: BaseModel
One constituent's share of the index return.
AttributionView ¶
Bases: BaseModel
Response of GET /beacon/{index_id}/attribution.
Contributions are Carino-linked, so they sum to the compounded total return
rather than approximately to it. residual is reported regardless and
should sit at machine epsilon; anything larger means an assumption broke
upstream, which is worth surfacing rather than rounding away.
AssetView ¶
Bases: BaseModel
Response of GET /beacon/{index_id}/assets/{identifier}.
CompareEntry ¶
Bases: BaseModel
One index within a comparison, on the shared window.
CompareView ¶
Bases: BaseModel
Response of GET /beacon/compare.
BacktestRunResult ¶
Bases: BaseModel
Result payload of a completed backtest job.
Every series here derives from the same NAV, rebased to 100: returns is
the level's percentage change, drawdown is the level against its running
peak, and annual_returns compound back to the total. A client that
recomputes any of them lands on these numbers exactly.
JobStatusOf ¶
Bases: BaseModel, Generic[ResultT]
State of one background job, generic over its result payload.
Generic so that every kind of job can publish the shape of the thing it returns.
Not used as a response model directly: the parametrisations below are,
and JobStatus is the untyped one.
JobStatus ¶
Bases: JobStatusOf[Any]
State of one background job, with an untyped result.
Used for a listing, which mixes kinds, and as the fallback arm of
AnyJobStatus for a kind nothing models yet.
BacktestJobStatus ¶
OptimisationJobStatus ¶
RenderJobStatus ¶
RiskModelJobStatus ¶
LoadResult ¶
Bases: BaseModel
Result payload of a completed load:{store_id} job.
LoadJobStatus ¶
GenerateResult ¶
Bases: BaseModel
Result payload of a completed generate:{store_id} job.
GenerateJobStatus ¶
RefreshResult ¶
Bases: BaseModel
Result payload of a completed refresh:{store_id} job.
RefreshJobStatus ¶
JobCollection ¶
Bases: TolerantCollection
Response of GET /jobs.
Untyped results on purpose: a listing spans every kind at once, so the
per-kind arms buy a client nothing it can use without reading kind
anyway. GET /jobs/{job_id} is where the typed result lives.
DatasetCoverage ¶
Bases: BaseModel
What the loaded data actually spans, for one dataset.
CoverageResponse ¶
Bases: BaseModel
Response of GET /data/coverage.
ErrorDetail ¶
Bases: BaseModel
The body of an error envelope.
ErrorEnvelope ¶
Bases: BaseModel
Every non-2xx response uses this shape.
headline_metric ¶
Read a core metric, which BacktestResult.summary() always populates.
The summary's value type is float | None because the tracking figures
are optional; the five headline metrics are not. A missing one is a break
in the mirror between summary() and BacktestMetrics, not a degraded
run, so it raises CalculationError naming the key rather than reporting
a value that was never measured.
rebalance_snapshots ¶
rebalance_snapshots(
index_result: IndexResult, cap: float | None = None
) -> list[RebalanceSnapshot]
Composition at each rebalance, in date order.
Carries the uncapped weights alongside the applied ones. On an uncapped index the two are identical and the duplication costs a little space; on a capped one the difference is the only record of what the cap did, and it cannot be recovered from the applied weights afterwards.
The cap itself comes from the definition rather than from the cap report, because the calculator only files a report on dates where the cap actually bound. "A 20% cap applies and nothing reached it" and "no cap applies" are different statements about a methodology, and a client asking what the rules are should get the same answer on both dates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index_result
|
IndexResult
|
The calculated index. |
required |
cap
|
float | None
|
The definition's maximum constituent weight, if it has one. |
None
|
price_gap_payloads ¶
The run's carried-forward marks, for whichever payload publishes them.
Shared by the record and the run payload rather than written twice: the same fact crossing the wire in two shapes is how the two drift.
rebalance_pricing_payloads ¶
The session each rebalance priced from, for either payload.
security ¶
Bearer-token authentication.
The server binds to loopback but that is not a security boundary: any process on the machine can reach it. Every route therefore requires the token the launcher generated, including /health.
verify_bearer_token ¶
Reject the request unless it carries the configured bearer token.
Wired in as a router-level dependency, so it runs before any handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
The incoming request; the expected token is read from application state, where create_app() put it. |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
401 when the Authorization header is missing, malformed, or carries the wrong token. |
serialisation ¶
Wire formats shared by every router.
pandas objects do not survive a plain JSON encoder intact (timestamps, NaN and numpy scalars all need handling), so frames and series are converted here into an explicit, stable shape rather than left to a default encoder.
dataframe_to_payload ¶
Serialise a DataFrame as {index, columns, data}.
Row-oriented data keeps the payload compact and preserves column
order, which a dict-of-columns would not guarantee.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
DataFrame
|
The frame to serialise. An empty frame yields empty lists. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict[str, Any]
|
|
dict[str, Any]
|
names), and |
|
dict[str, Any]
|
NaN, NaT and infinities all become None. |
series_to_payload ¶
Serialise a Series as {index, name, data}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
series
|
Series
|
The series to serialise. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict[str, Any]
|
|
dict[str, Any]
|
and |
store ¶
Versioned JSON document storage on the platform's app-data directory.
The server is a local process with no database. User-authored artefacts,
such as watchlists, index definitions and constraint sets, are small JSON
documents that must survive a restart and, more importantly, must survive a
schema change without the user losing them. Every document therefore carries a
schema_version, and reads run it forward through the migration chain before
it reaches the caller.
DocumentStore ¶
A namespaced directory of versioned JSON documents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
collection
|
str
|
Subdirectory name, e.g. |
required |
root
|
Path | None
|
Base directory. Defaults to the platform app-data location. Tests pass a temporary path. |
None
|
read ¶
Read a document, migrating it forward to the current schema.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document_id
|
str
|
Identifier of the document. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
dict or None: The document at the current schema version, or None |
dict[str, Any] | None
|
if it does not exist. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If the stored file is not valid JSON, or was written by a newer version of the application than this one understands. |
write ¶
Write a document, stamping it with the current schema version.
The write goes to a temporary file in the same directory and is then moved into place, so a crash mid-write leaves the previous document intact rather than a truncated one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document_id
|
str
|
Identifier of the document. |
required |
document
|
dict[str, Any]
|
Payload to store. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict[str, Any]
|
The stored document, including its schema_version. |
delete ¶
Delete a document.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document_id
|
str
|
Identifier of the document. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if a document was removed, False if none existed. |
read_all ¶
Read every document in the collection, migrating each forward.
store_schemas ¶
Request and response models for named data stores.
PostgresConnection ¶
Bases: BaseModel
Where a Postgres store's tables or views are. Read-only.
DataStoreCreate ¶
Bases: BaseModel
Body of POST /data/stores: register an existing store.
DataStoreUpdate ¶
Bases: BaseModel
Body of PATCH /data/stores/{store_id}. Omitted fields are unchanged.
RefreshRequest ¶
Bases: BaseModel
Body of POST /data/stores/{store_id}/refresh. Optional.
DataStore ¶
Bases: BaseModel
One registered data store.
DataStoreCollection ¶
GenerateSyntheticRequest ¶
Bases: BaseModel
Body of POST /data/synthetic: generate a synthetic data store.
Every field is optional. Anything left out takes the same default as
python -m beacon.synthetic, because the engine runs that command: the
same settings always give the same data, whichever way it was made.
ImportRequest ¶
Bases: BaseModel
Body of POST /data/import: load CSV files or an Excel workbook.
ImportResult ¶
Bases: BaseModel
Response of POST /data/import.
types ¶
Serving the catalogue: turning registered classes into renderable type specs.
One adapter for both editors. The methodology editor and the optimiser's constraint editor ask the same question (what types exist, and what does each take), so they get the same answer shape and a client can render both with one component.
specs_for ¶
Every registered type of one kind, ready to serve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
catalogue.SELECTION, WEIGHTING or CONSTRAINT. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[TypeSpec]
|
Type specs, name-ordered, each carrying its parameters in the |
list[TypeSpec]
|
order a form should show them, and, for a constraint, the unit its |
|
list[TypeSpec]
|
slack is reported in. |
views ¶
Reading a completed run: overview, weights, attribution, per-asset, compare.
A backtest is a job because it is slow. These are the panes that read what the job produced, and they must be fast: a client switching tabs should not be waiting on a recalculation. So they derive everything from the stored run rather than recomputing the index.
The stored run carries its level and metrics, plus two things that cannot be recovered from a NAV series:
- rebalance snapshots: the weights at each rebalance, both as applied and as they would have been uncapped. Everything about composition comes from these: the weights pane reads one, attribution drifts them forward day by day, the per-asset pane reads a name's history across them, and cap drag needs the uncapped set to compare against.
- costs and starting capital: two scalars, from which the cost drag falls out.
Daily weights are not stored. drifted_weights() reconstructs them from the
snapshots and the prices.
attribute() uses Carino linking, so the contributions sum to the compounded
total return rather than approximately to it. The endpoint reports the residual
regardless: it should sit at machine epsilon, and one that does not means an
assumption has broken somewhere upstream, which is worth surfacing rather than
rounding away.
build_overview ¶
Headline view of a completed run.
build_attribution ¶
build_attribution(
index_id: str,
run: dict[str, Any],
fetcher: DataFetcher,
start: str | None,
end: str | None,
) -> AttributionView
Per-constituent contributions over a window, with the two drags.
build_asset_view ¶
build_asset_view(
index_id: str,
identifier: str,
run: dict[str, Any],
fetcher: DataFetcher,
) -> AssetView
One constituent: its weight history and how it fared against the index.
build_compare ¶
Several indices on one axis, over the window they all share.
Aligned rather than concatenated: two indices with different start dates would otherwise be compared over different periods, and the one with the shorter history would look better or worse for no reason but its span. Every level is rebased to 100 on the first shared date, so the lines start together and the comparison is of shape rather than of scale.
weights ¶
The weights pane: composition at a date, per constituent.
It answers one question, what does the index hold, and how did it get there, with everything the table needs to answer it.
Each row carries two weights. raw_weight is what the weighting scheme
produced; weight is what survived the cap. The pair is what makes a capped
index legible: without the raw figure a reader sees several names sitting at
exactly 20% and cannot tell whether the cap was binding hard on one and barely
on another. Raw weights sum to 1 and applied weights sum to 1, and the weight
moved between them is cap_redistributed.
The aggregate DriftPayload and every row's delta_since_rebalance come from
the same held-weight vector, so the total always matches the rows it is a total
of. Drift is measured against the targets the last rebalance set, not against
the previous rebalance. An equal-weighted index resets to 1/n every time, so
comparing consecutive rebalances would report zero drift forever; the question
worth answering is how far prices have moved the index since it was last reset.
shares_outstanding is the company's shares outstanding, not shares held per
index unit (the usual fact-sheet "shares" column, which needs a divisor and a
notional this endpoint has neither of).
concentration_of ¶
Concentration measures for one weight vector.
build_weights ¶
build_weights(
index_id: str,
run: dict[str, Any],
as_of: str | None,
fetcher: DataFetcher,
with_risk: bool = False,
benchmark: dict[str, float] | None = None,
benchmark_id: str | None = None,
) -> WeightsView
Composition at a date, with per-constituent rows, drift and cap flags.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index_id
|
str
|
The index being read. |
required |
run
|
dict[str, Any]
|
The stored run, for context the snapshot does not carry. |
required |
as_of
|
str | None
|
Date asked about; None means the latest rebalance. |
required |
fetcher
|
DataFetcher
|
Data source, for prices and shares outstanding. |
required |
with_risk
|
bool
|
Decompose the index's volatility across its constituents. Off by default because estimating a covariance over every name is the pane's whole cost. |
False
|
benchmark
|
dict[str, float] | None
|
Weights to measure tracking error against, if any. |
None
|
benchmark_id
|
str | None
|
What to call it in the response. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
WeightsView |
WeightsView
|
The pane's whole payload. |
build_rows ¶
build_rows(
snapshot: RebalanceSnapshot,
held: dict[str, float] | None,
as_of: str | None,
fetcher: DataFetcher,
contributions: RiskContributions | None = None,
active_by_name: dict[str, float] | None = None,
benchmark: dict[str, float] | None = None,
) -> list[ConstituentRow]
One row per constituent, heaviest first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
snapshot
|
RebalanceSnapshot
|
The rebalance in force. |
required |
held
|
dict[str, float] | None
|
Drifted weights, or None when nothing has drifted yet. |
required |
as_of
|
str | None
|
The date shares outstanding are read at. |
required |
fetcher
|
DataFetcher
|
Data source. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[ConstituentRow]
|
Rows ordered by applied weight, descending. A weights table is |
list[ConstituentRow]
|
read from the top, so the order that matters is the one the reader |
|
list[ConstituentRow]
|
cares about rather than the order the store happened to hold. |
prices_for ¶
prices_for(
fetcher: DataFetcher,
identifiers: list[str],
start: str | None,
end: str | None,
) -> pd.DataFrame
Close prices for a set of names over a window, names on the columns.