Form ADV data surfaces

AUM, account counts, client classification, and fee revenue rolled up for Form ADV filings.

Form ADV Part 1 asks a registered investment adviser to report regulatory AUM, account counts by client type, fee revenue, and the advisory services offered. horizon.reporting.adv produces the data surfaces those fields consume.

The module does not file the form. Counsel owns the filing. The SDK makes the numbers auditable and reproducible at any point in time.

Import

python
from horizon.reporting import AdvBuilder, AdvSnapshot

Quickstart

python
from datetime import datetime, timezone
from horizon.reporting import AdvBuilder

snap = AdvBuilder().build(
    registry=registry,                           # AccountRegistry
    account_values={                             # or a callable(aid) -> usd
        "acc_jane_taxable": 500_000.0,
        "acc_jane_ira":     250_000.0,
        "acc_bob_taxable":  100_000.0,
        "acc_fund_1":     2_500_000.0,
    },
    fee_assessments=q1_fees,                     # list[FeeAssessment]
    period_start=datetime(2026, 1, 1, tzinfo=timezone.utc),
    period_end=datetime(2026, 3, 31, tzinfo=timezone.utc),
)

print(snap.to_text())

Output:

----------------------------------------------------------------------
Form ADV data surfaces (as of 2026-04-19T00:00:00+00:00)
Period: 2026-01-01 to 2026-03-31
----------------------------------------------------------------------
Regulatory AUM:              $       3,350,000.00
Accounts:                                       4
Households:                                     1
Clients:                                        3

Client classification:
  Accredited:                                 2
  Qualified purchaser:                        1
  Retail (non-accredited):                    1

AUM by sub-type:
  fund                          $       2,500,000.00
  sma                           $         850,000.00

AUM by asset class:
  crypto                        $       2,500,000.00
  equity                        $         725,000.00
  option                        $         125,000.00

AUM by custodian:
  cust_ibkr                     $       2,500,000.00
  cust_schwab                   $         850,000.00

Fee revenue (period):
  Total:                     $           63,750.00
  management                    $          13,750.00
  performance                   $          50,000.00
  Accounts w/ perf fee:                         1
----------------------------------------------------------------------

snap.to_dict() returns a JSON-safe dict for archival alongside the audit log.

What’s in the snapshot

python
@dataclass(frozen=True)
class AdvSnapshot:
    as_of: datetime
    period_start: datetime | None
    period_end: datetime | None

    total_aum_usd: float
    n_accounts: int
    n_households: int
    n_clients: int

    n_accredited: int
    n_qualified_purchaser: int
    n_retail_non_accredited: int

    aum_by_sub_type: dict[str, float]
    aum_by_asset_class: dict[str, float]
    aum_by_custodian: dict[str, float]
    aum_by_tax_type: dict[str, float]

    fee_revenue_total_usd: float
    fee_revenue_by_kind: dict[str, float]
    n_accounts_paying_performance_fee: int

    advisory_services: tuple[str, ...]

Client classification

Counts run over the registry’s clients (not accounts):

  • n_accredited: Client.is_accredited == True.
  • n_qualified_purchaser: Client.is_qualified_purchaser == True.
  • n_retail_non_accredited: not Client.is_accredited.

A client flagged as a qualified purchaser is typically also accredited; the counters are independent so both populate.

AUM by asset class

Each account’s AUM splits evenly across the asset classes in its asset_class_allowlist. This gives a conservative Form ADV estimate when an account is approved for multiple asset classes. Callers with true per-asset valuations can pass a custom account_value_fn and post-process.

Advisory services

Derived from the set of asset classes in use across the registry:

Asset class in useSurfaces
Equity, Option, Future, Forexportfolio_management_equity
Optionoptions_strategies
Crypto, Perpdigital_asset_advisory
Predictionevent_contract_advisory
AccountSubType.Fund in registrypooled_vehicle_management

These flags feed Form ADV 2A Item 4 service descriptions. The module does not write prose; it flags categories that match the firm’s actual activity.

Fee revenue

fee_assessments is a flat list of FeeAssessment records from Fees. The builder sums by kind (management, performance, other) and counts distinct accounts charged a performance fee.

Pass the fee stream from the period you’re reporting on. For the annual Form ADV amendment, pass the full year’s assessments.

Valuation

Two ways to supply account values:

python
# Static dict
snap = builder.build(registry=r, account_values={"acc_1": 100_000})

# Callable
def value_fn(account_id: str) -> float:
    return ledger.equity_by_account(account_id)
snap = builder.build(registry=r, account_values=value_fn)

Accounts without a value default to 0. The reporting window (as_of) does not change the valuation; pass a snapshot already valued at that point.

Archival

For every Form ADV update cycle:

python
import json
from pathlib import Path

snap = AdvBuilder().build(...)
Path(f"adv/{snap.as_of.date().isoformat()}.json").write_text(
    json.dumps(snap.to_dict(), indent=2, default=str)
)

Keep the JSON alongside the audit log for the full retention period. A later examiner can reproduce the numbers by replaying the log and re-running the builder.

Out of scope

  • Form ADV filing. Counsel files through IARD. The SDK feeds the numbers.
  • Form ADV Part 2 brochure prose. Counsel’s writing. This module provides structured data only.
  • Regulatory AUM vs. gross client assets. Form ADV has a specific definition of Regulatory AUM (continuous and regular supervisory services). The module sums all accounts in the registry; exclude any non-discretionary or held-away accounts from the input values if the RAUM treatment requires it.
  • GIPS composites. Performance reporting, composite construction, and GIPS verification are out of scope. See Performance for the per-account / per-fund metrics.

Related