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,
startup_store: dict[str, Any] | 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. |
startup_store |
dict[str, Any] | None
|
A registered store's record to load in the
background once the engine is serving, with |
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,
startup_store: dict[str, Any] | 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
|
startup_store
|
dict[str, Any] | None
|
A registered store to load once serving. |
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
¶
from_result(
result: BacktestResult,
cap: float | None = None,
settings: dict[str, Any] | None = None,
weights: bool = True,
) -> BacktestResultSummary
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
|
settings
|
dict[str, Any] | None
|
What ran, resolved, to keep with the record. |
None
|
weights
|
bool
|
Whether each book's daily weights are filled in. Left
empty, |
True
|
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. |
analysis ¶
A run's analysis on the wire: a backtest request's analysis built into the library's request, a stored run's analytics block kept and read beside its record, the catalogue of everything an analysis can be asked for, and a run's large tables by column or as Arrow.
The server works out no figure here: the library does, and this turns its answers into JSON or Arrow.
build_request ¶
The library's request from a backtest request's analysis.
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
If a report, level, statistic, view, section or setting is unknown, or a setting's value is refused. |
keep_analytics ¶
Keep a run's analytics block beside its tables.
read_analytics ¶
A stored run's analytics block, or None when it was made without one.
build_rerun_job ¶
build_rerun_job(
strategy_id: str,
folder: Path,
record: dict[str, Any] | None,
request: AnalysisRequest,
source: DataFetcher | None,
save: str | None = None,
) -> Callable[
[ProgressReporter], Awaitable[dict[str, Any]]
]
The job that gives request's analysis of a strategy's latest run.
With the choices the run's own analysis was made with, the run's block gains what was asked for and is kept; with other choices the answer is kept only under save. The run's factor work is read from its folder and kept there.
keep_saved ¶
Keep block with the run under name, replacing one kept so.
saved_analyses ¶
Every analysis kept by name with the run, newest first, without their blocks.
read_saved ¶
An analysis kept by name with the run.
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If none is kept under name. |
delete_saved ¶
Remove the analysis kept under name.
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If none is kept under it. |
with_tables ¶
with_tables(
block: dict[str, Any],
strategy_id: str,
tables: Mapping[str, DataFrame],
) -> dict[str, Any]
block naming the run's large tables and where to read each.
wire_table ¶
A run's large table with its columns as they are sent, numbers rounded.
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If the run gives no such table. |
columnar ¶
A large table by column: text as its distinct values and a code a row, a column the same on every row sent once.
arrow ¶
A large table as an Arrow IPC stream, its text columns dictionary-encoded.
analysis_schemas ¶
The wire shapes of a run's analysis: the request a backtest takes, the analytics block a run gives, the catalogue of what can be asked for, and a large table by column.
AnalysisRequestSpec ¶
Bases: BaseModel
What a run's analysis should give: the reports wanted, each at a
level, with statistics added or taken out, their time views, and every
choice the analysis makes. GET /analysis/options lists the reports,
statistics and settings.
StatisticRef ¶
Bases: BaseModel
One statistic a report shows, and how.
ReportSelection ¶
Bases: BaseModel
What a report shows, in order.
RollingValue ¶
Bases: BaseModel
A statistic over each trailing window.
StatisticValue ¶
Bases: BaseModel
One statistic's value.
UnavailableReason ¶
Bases: BaseModel
Why a statistic was not worked out for the run.
TableRef ¶
Bases: BaseModel
A large table of the run, read on its own.
AnalyticsBlock ¶
Bases: BaseModel
A run's analysis: each statistic once, and each report as a list of statistic ids.
AnalyticsView ¶
Bases: BaseModel
Response of GET /strategies/{strategy_id}/analytics.
AnalyticsRerun ¶
Bases: BaseModel
Body of POST /strategies/{strategy_id}/analytics: the analysis to
give of the strategy's latest run, and a name to keep it under.
SavedAnalysis ¶
Bases: BaseModel
One analysis kept by name with a strategy's latest run.
SavedAnalyses ¶
Bases: BaseModel
Response of GET /strategies/{strategy_id}/analyses.
SavedAnalysisView ¶
CodedColumn ¶
Bases: BaseModel
A column of text sent once per distinct value.
ColumnarTable ¶
Bases: BaseModel
A large table by column: each column a list of values, or, for repeated text, its distinct values and a code a row.
StatisticSpec ¶
Bases: BaseModel
One statistic, as the catalogue lists it.
ReportEntrySpec ¶
Bases: BaseModel
One statistic in a report level.
ReportSectionSpec ¶
Bases: BaseModel
A level's statistics of one category, in order.
ReportLevelSpec ¶
Bases: BaseModel
A report at one level.
ReportSpec ¶
Bases: BaseModel
One report, at both levels.
TableSpec ¶
Bases: BaseModel
A large table of a run.
UnavailableSpec ¶
Bases: BaseModel
One reason a statistic can be unavailable.
AnalysisCatalogue ¶
Bases: BaseModel
Response of GET /analysis/options: everything an analysis can be
asked for, so a client hard-codes none of it.
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. |
backtest_options ¶
GET /backtest/options: everything a backtest form needs, from the engine.
Every family's types come from the catalogue in the shape
/indices/rule-types uses, so one form component renders them all. The
presets carry their resolved settings, the vehicle's and the market model's
settings and the implementation's plain settings are described field by
field (with applies_to marking the exchange-traded ones), and the modelling
assumptions are listed with the engine's defaults. Nothing here reads data.
engine_defaults ¶
The modelling assumptions a run makes when none are set: the library's conventions, and the data treatment a data source has by default (no staleness limit is 0 here).
backtest_settings ¶
A backtest request's settings, built into the library's objects, and read back out fully resolved.
Each part of the request (strategy, implementation, flows, vehicle and
modelling assumptions) is built from its {type, params} objects through the
catalogue, so the server accepts exactly the types GET /backtest/options
lists. Every problem is collected as a finding with the path of the field
that caused it, such as vehicle.settings.pricing.params.threshold, and a
request with any is refused whole.
A vehicle's settings over its preset¶
A vehicle is a preset (generic when none is named) with settings laid over
it: a typed object (pricing) or a list (limits) replaces the preset's
whole, a plain object (market) merges key by key, an omitted key keeps the
preset's value, and null means none, refused on a setting that cannot be
none. Settings for exchange-traded vehicles are refused on any other.
Findings
dataclass
¶
The problems found in a request, each at its field's path.
BuiltSettings
dataclass
¶
BuiltSettings(
currency: str | None,
dividends: str,
reinvest_into: str,
modelling_assumptions: ModellingAssumptions | None,
strategy: Callable[[AnyIndexDefinition], Strategy],
implementation: Implementation | None,
flows: list[Flows] | dict[str, list[Flows]],
vehicle: Vehicle | None,
resolved: dict[str, Any],
share_classes: list[ShareClass] = list(),
initial_capital: float | dict[str, float] = 1000000.0,
)
A request's settings as the library's objects.
Attributes:
| Name | Type | Description |
|---|---|---|
strategy |
Callable[[AnyIndexDefinition], Strategy]
|
Builds the strategy for the path's index definition. |
resolved |
dict[str, Any]
|
The settings, fully resolved, as the run reports them. |
build ¶
build(
request: BacktestRequest,
fetcher: DataFetcher | None = None,
) -> tuple[BuiltSettings | None, list[Finding]]
Build a request's settings, or say everything wrong with them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
BacktestRequest
|
The backtest request. |
required |
fetcher
|
DataFetcher | None
|
The data the run would read, to resolve the modelling assumptions' data treatment; the library defaults when None. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
BuiltSettings | None
|
The built settings, or None when any finding was made, and |
list[Finding]
|
the findings. |
typed ¶
One {type, params} object, built through the catalogue; None, with
findings, when it cannot be.
typed_payload ¶
An object as its {type, params} form, read off its attributes.
vehicle_settings ¶
Every setting of vehicle, in the shape vehicle.settings takes.
market_settings ¶
An ETF market model's settings, by name.
preset_vehicle ¶
The vehicle a preset key names; the generic one for generic.
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.
ClassContext
dataclass
¶
What measuring a run's share classes needs: the classes, for their hedge ratios, the index's currency and the data the rates come from.
assemble_result ¶
assemble_result(
result: BacktestResult,
index_result: IndexResult,
benchmark: RelativeMetricsPayload | None = None,
cap: float | None = None,
settings: dict[str, Any] | None = None,
classes: ClassContext | None = None,
comparators: Comparators | 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
|
classes
|
ClassContext | None
|
What measuring its share classes needs; None without them. |
None
|
comparators
|
Comparators | None
|
The index and benchmark it is compared against; None leaves the comparisons empty. |
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, levels: Series
) -> RelativeMetricsPayload
Measure the portfolio against a resolved benchmark.
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 it is compared against. |
required |
levels
|
Series
|
The benchmark's levels, resolved. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
RelativeMetricsPayload |
RelativeMetricsPayload
|
The comparison and the rebased benchmark. |
build_backtest_job ¶
build_backtest_job(
strategy: StrategyDocument,
document: IndexDocument,
fetcher: DataFetcher,
request: BacktestRequest,
index_store: DocumentStore,
record_store: DocumentStore | None = None,
built: BuiltSettings | None = None,
store: Mapping[str, Any] | None = None,
) -> JobBody
Build the job body that runs one backtest of a stored strategy.
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 |
|---|---|---|---|
strategy
|
StrategyDocument
|
The stored strategy run. Its id and name are stamped on the run's settings, and its record is filed under its id. |
required |
document
|
IndexDocument
|
The index it holds, 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, with the strategy's own strategy and implementation. |
required |
built
|
BuiltSettings | None
|
Its strategy, implementation, flows, vehicle and modelling assumptions, already built; built here from request when None. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
JobBody |
JobBody
|
A coroutine function taking a progress reporter. |
strategy_and_vehicle_payloads ¶
What a strategy, flows, a vehicle and the implementation's stages add to the run payload; each empty or null when unused.
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. |
resolve_benchmark_book ¶
resolve_benchmark_book(
reference: BenchmarkRef,
fetcher: DataFetcher,
index_store: DocumentStore,
start: str,
end: str,
) -> IndexResult | pd.Series
A benchmark reference as a backtest takes it: a stored index's calculation, which carries its holdings as well as its levels, or a market-data series's levels.
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
As |
comparisons ¶
A run against its comparators, on the wire: its index and its benchmark, each with the comparator's own figures over the run's window and the run's figures relative to it.
The run is given by its daily returns, the ones its metrics use, so the comparison with its own index agrees with its tracking error and tracking difference.
Comparators
dataclass
¶
Comparators(
index_id: str,
benchmark: BenchmarkRef | None = None,
benchmark_levels: Series | None = None,
)
What a run is compared against: its index, by id, and an external benchmark with its resolved levels.
comparison_payloads ¶
comparison_payloads(
result: BacktestResult,
index_result: IndexResult,
comparators: Comparators,
) -> ComparisonsPayload
The run against its index and its benchmark.
compression ¶
Compressing large responses for a client on another machine, and not for one on the same machine, where compressing costs more time than the transfer it saves.
RemoteGZip ¶
Gzip responses of at least MINIMUM_SIZE bytes, for clients on
another machine only.
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,
startup_store: dict[str, Any] | 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. |
startup_store |
dict[str, Any] | None
|
A registered store's record to load in the
background once the engine is serving, with |
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,
startup_store: dict[str, Any] | 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
|
startup_store
|
dict[str, Any] | None
|
A registered store to load once serving. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
ServerConfig |
ServerConfig
|
The assembled configuration. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no token is available from either source. |
resolve_token ¶
The bearer token: token if given, else BEACON_API_TOKEN.
Raises:
| Type | Description |
|---|---|
ValueError
|
If neither supplies one. |
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,
pending: dict[str, Any] | None = None,
)
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 |
load_now
|
bool
|
Whether to load a registered store here. False leaves it
in |
True
|
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: |
derivative_records ¶
What a fund's swap, index futures and dividends owed add to a run's result.
A synthetic fund's swap: its value and the counterparty exposure it makes each day, and each reset. A fund's index futures, whether it holds the index through them or equitises its cash buffer: their exposure each day, in money and as a share of NAV, and each roll. Every series is on the run's days, and everything is empty or null for a fund without them, so a run without them answers as before.
derivative_payloads ¶
The swap and futures records a run's payload carries.
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 |
holding_reference ¶
Reference columns on the rows that name a holding, as of each row's date.
The caller chooses the columns with reference_fields, a comma-separated list
of reference columns. Without it a row carries a default set: the name, the
asset class, the sector and the dataset's country column. Each row's columns
are read as they stood on that row's date (see
beacon.data.reference_as_of), so a past holding shows the classification of
its time, not today's.
A column the dataset lacks is not a mistake when the API documents it: the
columns that say what a series is answer as the engine reads them (a missing
ASSET_CLASS is Equity), and the descriptive columns answer null. Any
other column the dataset lacks is refused, naming the ones it has.
HoldingReference ¶
HoldingReference(
fetcher: DataFetcher | None,
identifiers: Iterable[str],
columns: Sequence[str],
)
Reference columns for the names a response names, on their dates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fetcher
|
DataFetcher | None
|
The loaded data; None puts no columns on the rows. |
required |
identifiers
|
Iterable[str]
|
Every name the response will ask about. |
required |
columns
|
Sequence[str]
|
The chosen columns; empty puts none on the rows. |
required |
of ¶
One name's columns on date; None when none were chosen or the name had no reference row then.
windows_of ¶
windows_of(
identifiers: Iterable[str], start: str, end: str
) -> dict[str, list[dict[str, Any]]] | None
Each name's windows over a span; None when none were chosen.
map_of ¶
Several names' columns on one date; None when none were chosen.
chosen_columns ¶
The reference columns a request asks for, as stored (upper case).
Raises:
| Type | Description |
|---|---|
InvalidRuleError
|
If a column is neither in the dataset nor one the API documents. |
holding_reference ¶
holding_reference(
fetcher: DataFetcher | None,
reference_fields: str | None,
identifiers: Iterable[str],
) -> HoldingReference
The reference columns a request asks for, for the names it will name.
A response that can be read without data loaded, such as a saved run, carries no columns then, unless some were asked for by name.
Raises:
| Type | Description |
|---|---|
NoDataLoadedError
|
If columns were asked for by name and no data is loaded to read them from. |
InvalidRuleError
|
If a column is unknown (see |
holdings ¶
A strategy's holdings workflow at a date, one row per name, from the universe to the shares held.
What a run decided at each rebalance is saved with it, inside the strategy's record under a key of its own: how each rebalance's universe narrowed (the index's selection), each name's weight after the implementation's screens, and its target and final weights, and the book's units held, recorded only on the days they change, with its daily NAV. The view joins that to the run's own index and strategy weights: the index weights before and after the index's cap, the strategy's choice, each implementation stage, the weight traded to, and, as of the date asked about, the weight held and its drift.
A held weight is the units held times the day's price in the book's currency, over the day's NAV: what the book's own record says, without storing every day's weights.
holdings_document ¶
What a run decided at each rebalance, for its holdings workflow.
build_holdings ¶
build_holdings(
strategy_id: str,
index: IndexDocument,
run: dict[str, Any],
document: dict[str, Any] | None,
fetcher: DataFetcher,
date: str | None,
reference: HoldingReference,
benchmark_on: Callable[[str], dict[str, float] | None],
universe_on: Callable[
[list[str], str], dict[str, float | None]
]
| None = None,
universe_name: str | None = None,
) -> HoldingsView
The workflow in force on date, the run's last day when None.
benchmark_on gives the run's benchmark index's weights on a rebalance date, or None for a run without one; universe_on the universe's weights of some names on a date.
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If date is before the run's first rebalance. |
holdings_schemas ¶
Response models for a strategy's holdings workflow at a date.
index_level ¶
An index on its own: GET /indices/{index_id}/level.
The index calculated alone, with no portfolio, costs or vehicle: its level, the headline figures of that level, and its weights at each rebalance. Through the same cache a backtest uses, so a backtest over the same dates reuses the calculation, and the other way round.
IndexLevelView ¶
Bases: BaseModel
Response of GET /indices/{index_id}/level.
calculate_index ¶
calculate_index(
document: IndexDocument,
index_store: DocumentStore,
fetcher: DataFetcher,
start: str | None = None,
end: str | None = None,
) -> IndexResult
A stored index calculated alone, over the loaded data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
IndexDocument
|
The stored definition; a derived one's source is resolved through index_store. |
required |
index_store
|
DocumentStore
|
Where stored definitions live. |
required |
fetcher
|
DataFetcher
|
The loaded data. |
required |
start
|
str | None
|
First date; the definition's base date when None. |
None
|
end
|
str | None
|
Last date; the last date the data covers when None. |
None
|
build_index_level ¶
The view of a calculated index.
job_threads ¶
Running a job's work in a worker thread, so the event loop stays free.
A job body is a coroutine the registry runs as a task on the event loop. A
backtest, an optimisation or a risk model is minutes of synchronous pandas and
numpy work, and run inside that coroutine it holds the loop for all of it:
no request is answered, /health included, and the submit's own 202 waits
too, because the task starts before the response goes out.
run_in_thread hands the work to a worker thread with a plain progress
function. A report from the thread is passed to the loop to publish, and once
the job is cancelled the next report stops the work, so a cancelled run does
not go on to write its results.
JobCancelledError ¶
Bases: Exception
Raised in the worker thread at its next report after a cancellation.
run_in_thread
async
¶
Run work in a worker thread, publishing its reports on the loop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
ProgressReporter
|
The job's progress reporter. |
required |
work
|
Callable[[Report], Result]
|
The synchronous work, called with a progress function. |
required |
Returns:
| Type | Description |
|---|---|
Result
|
What work returns. |
Raises:
| Type | Description |
|---|---|
CancelledError
|
When the job is cancelled. The thread itself cannot be interrupted, so it stops at its next report instead. |
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. |
market_caps ¶
Each name's market cap at a date, local and converted, and how old it is.
A point-in-time cap is a price times a share count, both of which move, so it
is a market-data join rather than a stored attribute. /data/reference
publishes it as a derived field and the index preview beside each
constituent; both call market_caps, so the two never disagree.
market_caps ¶
market_caps(
fetcher: DataFetcher,
identifiers: list[str],
requested: set[str],
end: Timestamp,
currency: str,
) -> dict[str, dict[str, Any]]
Market capitalisation per name, local and converted into currency.
A point-in-time cap is a price times a share count, both of which move, so this is a market-data join rather than a stored attribute -- which is why it is derived and has to be asked for by name.
uses_free_float ¶
Whether an index weights by free-float market cap: cap-weighted with free float applied.
universe_weights ¶
universe_weights(
caps: dict[str, dict[str, Any]],
names: list[str],
free_float: bool,
) -> dict[str, float | None]
Each name's share of the universe's total market cap.
A universe has no weighting of its own, so its weights are its names' market caps over their total: free-float caps where the index weights by them, full caps otherwise. None for a name with no cap that day.
market_inputs ¶
Each data store's market inputs, kept once for every run on it.
A backtest's run keeps the market inputs it read in its data store's shared
folder (beacon.analysis.shared_inputs), under market_inputs/<store id>
in the server's storage, and only the list of pieces it used beside its
record. A run on data from no registered store keeps its inputs itself, as
before. The bookkeeping is beacon.analysis.shared_runs'; this gives it the
stored runs.
- Merging. A folder holding more than 20 files is merged into one file per table, dropping the pieces no run uses.
- Deleting a store. The runs on it keep their pieces, and the folder is marked as kept for a deleted store, which such a run says when it opens; or, with cascade, the runs are deleted, and the folder with them.
- Runs stored before. A run that kept its inputs itself is moved to the
shared layout when the server starts, under
unattributed, and reads the same.
root_of ¶
The storage root the records are kept under, where the shared folders are.
stored_runs ¶
Each stored strategy's run folder.
shared_for ¶
The shared folder of the data store store (its id), or None for
data from no registered store.
merge_if_due ¶
Merge shared when it holds too many files; whether it did.
forget_store ¶
forget_store(
records: DocumentStore,
store: Mapping[str, Any],
cascade: bool = False,
) -> dict[str, Any]
Deleting the data store store: the runs on it keep their market inputs, or with cascade are deleted.
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict[str, Any]
|
|
dict[str, Any]
|
and deleted, and the |
migrate ¶
Move the stored runs that keep their market inputs themselves to the shared layout; the strategies moved.
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.
A plain OPTIONS request, one that is not a CORS preflight, gets the same 405 on
every path in the spec, parameterised ones included, with Allow listing every
method the path supports. The router alone listed only the methods of the
first route it matched.
LiteralPathMethods ¶
ASGI middleware refusing undocumented methods on fixed paths.
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.
OptimisationTarget
dataclass
¶
The weights an optimisation solves for, and where they came from.
Attributes:
| Name | Type | Description |
|---|---|---|
index_id |
str
|
The index targeted, or the one the targeted strategy holds. |
date |
str
|
The rebalance the weights were in force from, YYYY-MM-DD. |
weights |
dict[str, float]
|
The target's weights. |
constituent_prices ¶
constituent_prices(
fetcher: DataFetcher,
identifiers: list[str],
start: str | None = None,
end: str | None = None,
currency: str | None = None,
) -> pd.DataFrame
Close prices for a set of names in currency, names on the columns.
Converted into the index's currency, so the risk model behind a solve carries exchange-rate risk, and a size factor compares caps in one money. None leaves each name in its own currency (a run saved before runs recorded their currency).
Raises:
| Type | Description |
|---|---|
DataNotFoundError
|
If none of them can be priced. |
CalculationError
|
If a name's currency has no rate into currency. |
build_optimisation_job ¶
build_optimisation_job(
run_id: str,
request: OptimisationRunRequest,
constraint_set: ConstraintSet,
constraints: list[Any],
target: OptimisationTarget,
label_for: dict[str, str],
fetcher: DataFetcher,
currency: str,
) -> Callable[
[ProgressReporter], Awaitable[dict[str, Any]]
]
Build the coroutine that runs an optimisation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
OptimisationTarget
|
The weights solved for, and where they came from. |
required |
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: OptimisationTarget,
label_for: dict[str, str],
prices: DataFrame,
risk_model: Any,
fetcher: DataFetcher,
currency: str | None = None,
) -> 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.
parameter_schemas ¶
The shapes a structured backtest parameter takes, published for its editor.
A parameter whose display type is json names one of these in its ref, so a
client renders rows or a picker for it rather than a JSON box. No route returns
them, so publish adds them to the OpenAPI document's components.schemas
beside the schemas the routes reach, where every ref is resolved.
IdentifierList ¶
Bases: RootModel[list[str]]
Instrument identifiers, such as those GET /data/identifiers finds.
ReferenceValueList ¶
Bases: RootModel[list[str]]
Values of one reference column, matched exactly. The parameter's column
names the column, and GET /data/reference-values lists what it holds.
IdentifierValues ¶
Bases: RootModel[dict[str, float]]
A number for each identifier. The parameter's help says what the
numbers are, and whether they are scaled to add up to one.
DatedAmounts ¶
Bases: RootModel[dict[date, float]]
An amount for each date (YYYY-MM-DD), in the book's currency, positive in and negative out.
ConstraintRows ¶
Bases: RootModel[list[ConstraintRow]]
Optimiser constraints, one row each. The parameter's options names the
family whose types the rows take.
TypedSpecs ¶
Bases: RootModel[list[TypedSpec]]
Configurable objects, each a type and its parameters. The parameter's
options names the family their types come from.
CountryRates ¶
Bases: RootModel[dict[str, float]]
A rate for each two-letter country code, such as {"US": 0.15}, as a fraction.
FactorSignals ¶
Bases: RootModel[dict[str, TypedSpec]]
Each factor by a name of your choosing, as a signal whose scores are the
names' exposures to it. The parameter's options names the family the
signals' types come from.
FactorTargets ¶
Bases: RootModel[dict[str, float]]
A target active exposure, in scores, for each factor named in the
construction's factors. A factor with no target is aimed at zero.
SignalWeights ¶
Bases: RootModel[list[float]]
A weight for each of the factor's signals, in their order: zero or
more, not all zero. Leave out for equal weights.
GroupingFields ¶
Bases: RootModel[list[str]]
Reference fields to group names by, such as ["region", "sector"]: each name is centred within the group its values put it in.
parameter_schemas ¶
The schemas above, and any they refer to, by name.
publish ¶
Add the parameter schemas to app's OpenAPI document.
A schema a route already publishes, such as ConstraintRow, is kept as
the route publishes it.
periods ¶
A run's figures by calendar period: GET /strategies/{id}/periods.
Read from the saved run, so nothing is recalculated: its daily returns, the first from the run's starting point, and its comparators' levels. Each period runs from the previous period's close and the first from the run's start, so the periods compound to the whole run's figures.
build_periods ¶
build_periods(
strategy_id: str,
index_id: str,
run: dict[str, Any],
frequency: str,
) -> PeriodsView
A saved run's figures in each period of frequency.
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,
universe_name: 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
|
universe_name
|
str | None
|
The name of the stored universe the document names, for the universe step's description. |
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. |
query_guard ¶
A query parameter that takes one value is refused when it is given twice.
Without this, ?limit=10&limit=500 quietly answers with the last value, so a
client that built a URL wrong gets an answer to a question it did not ask. A
list parameter, such as ?fields=NAME&fields=SECTOR, may repeat. Which is
which is read from the published OpenAPI document, so the rule is the
contract's rather than a second list kept beside it.
single_valued_query ¶
Refuse a repeated query parameter that takes a single value.
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
Naming each parameter given more than once. |
record_tables ¶
A backtest record's large parts, kept as tables beside it.
Almost all of a stored record is a handful of large parts: each book's daily
weights, the fund's positions and transactions, each index's rebalance
snapshots and the holdings document. The record keeps its own shape with
those parts left empty, and says under TABLES_KEY where each one is kept:
- a book's daily weights, in the run's own weights tables, which the run's analysis keeps anyway;
- the fund's units in the holdings document, in the fund's holdings, which the run keeps for the same reason;
- the positions and transactions, as a table each;
- the rebalance snapshots and the rest of the holdings document, as a table each of every value in them, which gives back the same JSON.
A part goes into a table only when what it reads back as is the part exactly,
checked as it is written; one that would not come back exactly stays in the
JSON. whole_record puts the parts back, so every response is the one the
whole record gave.
slim_record ¶
document with its large parts left empty and kept as tables in
folder, the run's tables folder, where whole_record finds them.
A book's daily weights are served from run's weights tables, so the
document may carry them empty already: what is kept is how many of the
latest days it serves, weights_dates_total of them up to the record's
bound. Every other part is moved only when what it reads back as is the
part exactly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
dict[str, Any]
|
The record, as it is stored whole. |
required |
run
|
RunData
|
The run's analysis data, as written to folder. |
required |
folder
|
Path
|
The run's tables folder. |
required |
whole_record ¶
whole_record(
document: dict[str, Any],
folder: Path,
wanted: Collection[str] | None = None,
) -> dict[str, Any]
document with the parts slim_record moved put back, or as it is
when nothing was moved, as a record stored before.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
dict[str, Any]
|
The record as it is stored. |
required |
folder
|
Path
|
The run's tables folder. |
required |
wanted
|
Collection[str] | None
|
The record's top-level keys to put back, every one when None; the rest stay empty. |
None
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If a part's table is missing or cannot be read. |
transactions_from ¶
The run's trades as the record's transactions give them.
units_from ¶
Each name's units on the days they changed, 0 from the day it left, from the fund's holdings, as the holdings document gives them.
frame_table ¶
A frame's index and columns as a table, or None when a column holds values of more than one kind (a number beside text), which a table column cannot.
tree_table ¶
Every value in tree, a row each in the order they come, beneath a row for each list and dict that holds them.
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,
currency: str,
) -> pd.DataFrame
Daily returns for a set of names in currency, names on the columns.
Prices are converted into currency before the returns are taken, so a name quoted elsewhere carries its exchange-rate moves as part of its risk.
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. |
CalculationError
|
If a name's currency has no rate into currency. |
build_estimation_job ¶
build_estimation_job(
model_id: str,
request: RiskModelRequest,
identifiers: list[str],
fetcher: DataFetcher,
currency: str,
) -> 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 ¶
assemble_risk_model(
model_id: str,
request: RiskModelRequest,
model: Any,
currency: str | None = None,
) -> RiskModelView
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_analysis_router ¶
Build the router for reading a run's analysis.
build_backtest_router ¶
Build the /backtest router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the backtest options. |
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_reference_values_router ¶
The reference-values route, under /data.
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_strategies_router ¶
Build the /strategies router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying strategy list, read, validate, create, |
APIRouter
|
replace and delete. |
build_strategy_runs_router ¶
Build the router for backtesting stored strategies and reading their runs, under /strategies.
Mounted before the strategies router, so /strategies/compare and
/strategies/runs are not read as strategy ids.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the backtest submission, its validation, |
APIRouter
|
and the result views. |
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. |
analysis ¶
Reading a run's analysis: the catalogue of what an analysis can be asked for, a strategy's latest run's analytics block, and its large tables, by column in JSON or as Arrow.
build_analysis_router ¶
Build the router for reading a run's analysis.
backtest ¶
The backtest's catalogue: GET /backtest/options.
Backtests themselves are submitted for a stored strategy, at
POST /strategies/{strategy_id}/backtest; this lists what such a request, and
the strategy it runs, may carry.
build_backtest_router ¶
Build the /backtest router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the backtest options. |
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. |
reference_values ¶
GET /data/reference-values: the values a reference column holds.
What a picker for a list of reference values offers, such as the sectors an
exclusion screen may exclude: a parameter's column names the column.
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.
strategies ¶
Stored strategies: /strategies.
A strategy is what a backtest holds (an index in full, through a replication, or an active strategy measured against it) and how it trades. It is stored beside the index definitions, so it outlives the process and travels with them; everything else a backtest needs stays on the backtest.
build_strategies_router ¶
Build the /strategies router.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying strategy list, read, validate, create, |
APIRouter
|
replace and delete. |
strategy_runs ¶
Backtesting a stored strategy, and reading its runs.
A backtest calculates the strategy's index and then simulates the 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 serve the panes of Beacon View (overview, weights, attribution, a single name, and a comparison across strategies), and each answers from the strategy's most recent successful run rather than recalculating anything, so two panes read a moment apart describe the same run. Every read is a 404 until the strategy has been backtested.
build_strategy_runs_router ¶
Build the router for backtesting stored strategies and reading their runs, under /strategies.
Mounted before the strategies router, so /strategies/compare and
/strategies/runs are not read as strategy ids.
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Router carrying the backtest submission, its validation, |
APIRouter
|
and the result views. |
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.
interpreter ¶
This Python, with the isolation flags the engine itself runs under.
An engine started isolated (-I, as the Beacon app starts its bundled
one) ignores the user's own site-packages and PYTHON* variables. A child
started without the same flags would not, and could load a different
numpy or pandas from the user's site-packages than the engine uses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
flags
|
Any
|
The interpreter's flags; |
flags
|
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.
ReferenceWindow ¶
Bases: BaseModel
A span over which a name's reference columns held.
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
An order 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
¶
from_result(
result: BacktestResult,
cap: float | None = None,
settings: dict[str, Any] | None = None,
weights: bool = True,
) -> BacktestResultSummary
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
|
settings
|
dict[str, Any] | None
|
What ran, resolved, to keep with the record. |
None
|
weights
|
bool
|
Whether each book's daily weights are filled in. Left
empty, |
True
|
BacktestRecordRow ¶
Bases: BaseModel
One stored backtest record, as a listing knows it.
The row is deliberately thin: the strategy to fetch the record by, what
it holds, and when it was captured. Everything else comes from
/strategies/{strategy_id}/record.
BacktestRecordCollection ¶
Bases: TolerantCollection
Response of GET /strategies/runs.
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: the weights a target held on a date,
solved for under a constraint set.
The target is an index or a stored strategy, exactly one of the two. An index's weights are calculated, so no backtest is needed; a strategy's are what its latest run chose at that rebalance, so an active strategy's answers how close a portfolio can get to it under the constraints.
WeightRow ¶
Bases: BaseModel
One name's target, 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.
StageParam ¶
Bases: BaseModel
One setting of a stage, as a reader sees it.
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.
TypedSpec ¶
Bases: BaseModel
One configurable object: its type and its parameters.
The types and what each accepts are listed by GET /backtest/options.
StrategySpec ¶
Bases: BaseModel
What the backtest holds.
The path's index is the strategy's index: the one tracked, or the benchmark an active strategy is measured against.
CashBandSpec ¶
Bases: BaseModel
The shares of the book the cash may move between before it is brought back to the cash buffer.
ImplementationSpec ¶
Bases: BaseModel
How the strategy is carried out at the fund's size.
VehicleSpec ¶
Bases: BaseModel
How the money is held: a preset, with any settings laid over it.
Settings merge over the preset's: a typed object (pricing) or a list
(limits) replaces the preset's whole, a plain object (market) merges
key by key, an omitted key keeps the preset's value, and null means none
(refused on a setting that cannot be none). Money settings are in the
book's currency.
ModellingAssumptionsSpec ¶
Bases: BaseModel
What the run takes as given about markets and data. Every field is
optional; an unset one takes the engine's default, which
/backtest/options lists.
ShareClassSpec ¶
Bases: BaseModel
One share class of a backtest's fund: its own currency, fee and distribution policy, sharing the book with the other classes.
StrategyBacktestRequest ¶
Bases: BaseModel
Body of POST /strategies/{strategy_id}/backtest: everything a backtest
needs that the stored strategy does not hold.
Every field past the first five is optional, and a request without them runs exactly as one with only the dates, capital and cost does. The strategy and its implementation come from the stored strategy, so a body carrying either is refused.
BacktestRequest ¶
Bases: StrategyBacktestRequest
A backtest's settings with its strategy and implementation: what a stored strategy's run is built from. Not a request body of its own.
VehiclePreset ¶
Bases: BaseModel
One vehicle preset, with every setting resolved.
BacktestOptions ¶
Bases: BaseModel
Response of GET /backtest/options.
Everything a backtest form needs, from the engine itself: each family's
types with their fields (in the shape /indices/rule-types uses), the
vehicle presets with their resolved settings, the settings' descriptors,
the modelling assumptions with their defaults, and the closed choices.
BacktestValidation ¶
Bases: BaseModel
Response of POST /strategies/{strategy_id}/backtest/validate.
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 /strategies/{strategy_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 /strategies/{strategy_id}/weights.
ContributionPayload ¶
Bases: BaseModel
One constituent's share of the index return.
AttributionView ¶
Bases: BaseModel
Response of GET /strategies/{strategy_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 /strategies/{strategy_id}/assets/{identifier}.
CompareEntry ¶
Bases: BaseModel
One strategy's latest run within a comparison, on the shared window.
CompareView ¶
Bases: BaseModel
Response of GET /strategies/compare.
FlowPayload ¶
Bases: BaseModel
One day's flow, as the run dealt it.
HedgePayload ¶
Bases: BaseModel
One trade of a hedged class's forward.
ShareClassMetrics ¶
Bases: BaseModel
A class's figures against the index in its currency: converted for an unhedged class, hedged the same way for a hedged one.
ShareClassPayload ¶
Bases: BaseModel
One share class's record of a run. Its money is in its own currency, and
its series share level's dates.
MarketPayload ¶
Bases: BaseModel
An ETF's quotes on the exchange, on level's dates.
ComparatorFigures ¶
Bases: BaseModel
A comparator's own figures over a run's window, defined as a run's are.
RelativeFigures ¶
Bases: BaseModel
A run against a comparator over the same window.
ComparisonPayload ¶
Bases: BaseModel
A run against one comparator: the comparator's own figures and the run's relative to it, over the days both have.
ComparisonsPayload ¶
Bases: BaseModel
A run against each of its comparators; null where it has none.
PeriodFigures ¶
Bases: BaseModel
A run's figures over one calendar period.
PeriodComparison ¶
Bases: BaseModel
A comparator over one period: its own figures and the run's against it.
PeriodRow ¶
Bases: BaseModel
One calendar period of a run.
PeriodsView ¶
Bases: BaseModel
Response of GET /strategies/{strategy_id}/periods.
SwapResetPayload ¶
Bases: BaseModel
One reset of a synthetic fund's swap.
FuturesRollPayload ¶
Bases: BaseModel
One roll of a fund's index futures to the next contract.
ReplicationPayload ¶
Bases: BaseModel
What a replication did at one rebalance.
ActiveStepPayload ¶
Bases: BaseModel
What an active strategy did at one rebalance.
RebalanceStepPayload ¶
Bases: BaseModel
What the implementation's stages did at one rebalance.
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 ¶
AnalyticsJobStatus ¶
Bases: JobStatusOf[AnalyticsBlock]
An analytics:{strategy_id} job. result is the latest run's analysis,
whole: what was kept with it and what was worked out now.
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.
metrics_from ¶
The payload's metrics from BacktestResult.summary(): the headline
five, which must be there, and whichever others the run has.
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
|
unfilled_payloads ¶
The run's unfilled orders, for whichever payload publishes them.
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 |
share_class_results ¶
What a run's share classes add to its result.
Each class's record is in its own currency, its series on the run's days, and
its figures are measured against the index in its currency: converted for an
unhedged class, hedged the same way for a hedged one (see
beacon.backtest.class_benchmark).
flow_payloads ¶
Flows as dealt, on the wire.
share_class_payloads ¶
share_class_payloads(
result: BacktestResult,
share_classes: Sequence[ShareClass],
days: Index,
index_levels: Series,
index_currency: str,
fetcher: DataFetcher,
) -> dict[str, Any]
The share_classes and hedge_value a run's payload carries; empty
and null without share classes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
BacktestResult
|
The finished run. |
required |
share_classes
|
Sequence[ShareClass]
|
The classes it ran, for their hedge ratios. |
required |
days
|
Index
|
The run's days, which the classes' series are put on. |
required |
index_levels
|
Series
|
The index's levels, in index_currency. |
required |
index_currency
|
str
|
The index's currency. |
required |
fetcher
|
DataFetcher
|
The data the exchange and short rates are read from. |
required |
share_class_settings ¶
A backtest request's share classes, and its capital and flows by class.
Without share classes a request's capital is one amount and its flows one
list, as before. With them, each class can have its own capital and flows,
keyed by its name, in its own currency, and a single amount or list is taken
for a fund of one class only, as Backtest takes them. Every problem is a
finding at its field's path, such as share_classes.1.hedge_band or
flows.EUR H.0.
FundTerms
dataclass
¶
FundTerms(
share_classes: list[ShareClass],
initial_capital: float | dict[str, float],
flows: list[Flows] | dict[str, list[Flows]],
)
A request's share classes, capital and flows as Backtest takes them.
Attributes:
| Name | Type | Description |
|---|---|---|
share_classes |
list[ShareClass]
|
The classes; empty for a fund of one. |
initial_capital |
float | dict[str, float]
|
One amount, or one per class name. |
flows |
list[Flows] | dict[str, list[Flows]]
|
One list of scenarios, or one per class name. |
fund_terms ¶
The request's share classes, capital and flows, each problem reported.
stage_descriptions ¶
What each stage of a strategy's workflow is and does, as a reader sees it.
Each stage of a constituent preview or a holdings workflow carries its type, one plain sentence of what it does with its settings, and its settings as labelled values formatted with their units: money in the index's currency, fractions as percentages, basis points and days as such. The labels and units are the catalogue's own, so a stage reads the way its editor does.
Described
dataclass
¶
A stage as a reader sees it.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
str
|
Its class, or the kind for the steps that have none. |
description |
str
|
One plain sentence of what it does with its settings. |
params |
list[dict[str, str]]
|
Its settings, each |
Values ¶
A typed object's settings, each with its catalogue label and unit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
Its catalogue family, such as |
required |
type_name
|
str
|
Its class name. |
required |
params
|
dict[str, Any] | None
|
The settings given; the rest take their defaults. |
required |
currency
|
str
|
What money is in. |
required |
formatted ¶
A setting's value as a reader reads it.
rule ¶
An index's selection rule.
screen ¶
An implementation's screen.
universe ¶
The universe a rebalance starts from.
strategy ¶
What a tracking or active strategy chose.
capacity ¶
An implementation's caps and minimum position, as one stage.
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
|
compact
|
bool
|
Write documents without the space after each comma and colon, for a collection whose documents are large. |
False
|
panels_directory ¶
The folder beside a document that holds its large tables, as a run's record keeps them; the same identifier rules apply.
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.
StoreForgotten ¶
Bases: BaseModel
Response of DELETE /data/stores/{store_id}: what became of the
stored runs that read the store's data.
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.
StoreProblem ¶
Bases: BaseModel
Why a store cannot be read, when the engine can tell without loading it.
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.
strategies ¶
Stored strategies: what makes one valid, what stops one running, and what uses an index.
A strategy is checked by the same code that builds a backtest's settings, so a strategy that saves is one a backtest accepts, and the two cannot disagree about what is valid.
read_strategy ¶
A stored strategy as its model: the build function for reading one.
The model refuses fields it does not know, so a client sending a backtest's vehicle or period on a strategy hears about it, and the store's own version stamp is set aside before it is checked.
load_strategy ¶
A stored strategy, or not-found: absent and unreadable alike.
problem_of ¶
Why a strategy cannot be run as it stands, or None when it can.
Its index can go after it is saved: deleted through a store the refusal never saw, or not copied to this machine with the strategy.
with_problem ¶
A strategy as listed and read, with any problem it has.
validate_strategy ¶
Every problem with a strategy, each at its field's path.
The strategy and implementation are checked as a backtest checks them,
so their findings sit at the same paths (strategy.replication,
implementation.caps.0). Its index must be stored and readable.
strategies_using ¶
The ids of the stored strategies that hold any of these indices.
A strategy that cannot be read is not counted: what it holds cannot be known, and refusing a delete over it would leave the index undeletable until the strategy was repaired.
strategy_runs ¶
A stored strategy's runs, and the weights a target stands for.
Runs are kept per strategy: a backtest of a stored strategy is the job kind
backtest:{strategy_id}, and its saved record is filed under the strategy's
id. A run is served only when it carries its strategy's id, so a run saved
before runs were kept per strategy, filed under an index's id, is never taken
for a strategy of the same name.
A target for the optimiser or a risk model is an index or a strategy. An index's weights are calculated, so no run is needed; a strategy's are what its latest run chose at each rebalance: the index's for a plain index strategy, its replication's for a tracker, its construction's for an active strategy.
backtest_kind ¶
The job kind a strategy's backtests run under.
latest_run ¶
The most recent successful run of a strategy, or None.
A run of this kind that does not carry the strategy's id was saved before runs were kept per strategy, under an index of the same name, and is not this strategy's. Runs since are always newer, so only the newest needs the check.
require_run ¶
A strategy's latest run, or not-found pointing at how to make one.
strategy_steps ¶
What a run's strategy chose at each rebalance, as {date, weights}
rows in date order.
strategy_weights ¶
The rebalance date and the weights a run's strategy chose in force on as_of; the last when None.
index_weights ¶
index_weights(
document: IndexDocument,
index_store: DocumentStore,
fetcher: DataFetcher,
as_of: str | None,
) -> tuple[str, dict[str, float]]
The rebalance date and an index's weights in force on as_of, from its calculation up to that date; the latest when None.
strategy_schemas ¶
Request and response models for stored strategies.
StrategyDocument ¶
Bases: BaseModel
A stored strategy: what a backtest holds and how it trades.
Everything else a backtest needs (the period, capital, currency, dividends, costs, flows, vehicle, share classes and modelling assumptions) belongs to the backtest, not the strategy.
StrategyProblem ¶
Bases: BaseModel
Why a stored strategy cannot be run as it stands.
StoredStrategy ¶
StrategyCollection ¶
SavedStrategy ¶
Bases: BaseModel
Response of a strategy save: the document as stored, and any warnings.
StrategyDeletion ¶
Bases: BaseModel
Response of DELETE /strategies/{strategy_id}.
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.
parameter_spec ¶
parameter_spec(
parameter: Parameter,
schemas: Mapping[str, str],
columns: Mapping[str, str] | None = None,
options: Mapping[str, str] | None = None,
) -> ParameterSpec
One catalogue parameter as its API shape.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parameter
|
Parameter
|
The introspected parameter. |
required |
schemas
|
Mapping[str, str]
|
Parameter name -> component schema, as the class declared it. |
required |
columns
|
Mapping[str, str] | None
|
Parameter name -> the reference column its values are of. |
None
|
options
|
Mapping[str, str] | None
|
Parameter name -> the family its entries' types come from. |
None
|
specs_for ¶
Every registered type of one kind, ready to serve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
catalogue.SELECTION, WEIGHTING or CONSTRAINT, or one of a backtest's families. |
required |
families
|
Mapping[str, str] | None
|
Catalogue kind -> the name it is served under, for a parameter whose entries are themselves configurable types. |
None
|
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(
strategy_id: str,
index_id: str,
run: dict[str, Any],
fetcher: DataFetcher,
start: str | None,
end: str | None,
currency: str | None = None,
) -> AttributionView
Per-constituent contributions over a window, with the two drags.
Prices are converted into currency, the index's, so each contribution includes its name's exchange-rate return and the contributions add up to the index's return.
build_asset_view ¶
build_asset_view(
strategy_id: str,
index_id: str,
identifier: str,
run: dict[str, Any],
fetcher: DataFetcher,
currency: str | None = None,
) -> AssetView
One constituent: its weight history and how it fared against the index.
The comparison with the index is made in currency, the index's; the price series returned stays in the name's own currency, as a price chart should.
build_compare ¶
Several strategies' latest runs 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(
strategy_id: str,
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,
currency: str | None = None,
) -> WeightsView
Composition at a date, with per-constituent rows, drift and cap flags.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
strategy_id
|
str
|
The strategy whose run is read. |
required |
index_id
|
str
|
The index it holds. |
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
|
currency
|
str | None
|
The index's currency. Prices are converted into it before weights drift or risk is estimated, so a name quoted elsewhere carries its exchange-rate moves. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
WeightsView |
WeightsView
|
The pane's whole payload. |
build_rows ¶
build_rows(
snapshot: RebalanceSnapshot,
drift: 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 |
drift
|
dict[str, float] | None
|
Each name's held weight less its target, 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,
currency: str | None = None,
) -> pd.DataFrame
Close prices for a set of names over a window, names on the columns, converted into currency (None leaves each in its own).
Raises:
| Type | Description |
|---|---|
CalculationError
|
If a name's currency has no rate into currency. |