oak-domain-investments
The investments domain for the OakQuant platform — a complete, goal-linked investing product delivered as a single discoverable plugin, spanning all four OakQuant layers with zero edits to any core library.
What it is
Investing is a discoverable plugin. The package advertises itself through
entry points and registers into the host's shared registries — Timber's
model/service registries, grove's FastAPI app, grove's Celery worker + beat,
grove's config store, and acorn's Oracle tool registry — never by editing the
core libraries. Install it and a full investing capability appears across the
platform; leave it out and nothing breaks. See the oak-domain-plugins skill
for the wider plugin shape.
This is OakQuant's largest and most mature domain. It began as a single watchlist migration and is now the whole B2C investing product: financial-goal creation with AI vision and personalized inspiration imagery, brokerage linkage via Plaid, portfolio and goal dashboards, a self-learning single-stock research flow, an index×sector sentiment heatmap, a behavioral Financial DNA assessment, an advisor review queue with SLAs, and a continually-learning prediction engine. Grove core owns zero investing code or config — everything investing lives here. The bulk of this program is in production.
The four-layer shape
A single installed package plugs into four OakQuant layers, one entry point per group. Each host discovers only its own entry-point group and imports only that entry point's module, so the timber layer never pulls in grove/FastAPI or acorn, and the acorn layer never pulls in the SQLAlchemy model stack.
| Layer | Entry-point group | Object | Contributes |
|---|---|---|---|
| timber | timber.domains |
oak_domain_investments:InvestmentsDomain |
ORM models (Python + bundled YAML built by the ModelFactory at Step 8.5) and ~40 registered services (watchlist, goals, portfolio, Plaid, stock/market data, forecasting, prediction brain, research, Financial DNA, advisor review, …) |
| grove (web) | grove.domains |
oak_domain_investments.grove_plugin:InvestmentsGroveDomain |
Ten FastAPI routers (watchlist, goals, portfolio, Plaid, advisor, prediction track-record, semantic search, stock lookup, calendar-generate) + UI options-helpers |
| grove (worker) | grove.workers |
oak_domain_investments.worker_plugin:InvestmentsWorkerDomain |
The adaptive-prediction Celery tasks + their beat schedule (predict / predict-universe / observe / mature) |
| grove (config) | grove.feature_packages |
oak_domain_investments:feature_packages_dir |
Nine bundled Tier-1 config packages (workflows, tasks, HITL screens, pages, nav, home widgets, schedulers) seeded into the grove DB |
| grove (secrets) | grove.secret_specs |
oak_domain_investments:secret_specs |
The market-data provider + Plaid secret specs grove fetches from ranger at boot (all optional; a missing key degrades a feature, never blocks startup) |
| acorn | acorn.domains |
oak_domain_investments.acorn_plugin:InvestmentsAcornDomain |
The Oracle "investments" assistant profile (financial persona) + ~20 gated agent tools |
| sky | (host widget registry) | sky-investments |
The goal/portfolio/watchlist/heatmap/oak-tree widgets (registered into sky's widget registry; lives in the sky repo) |
Grove and acorn delegate to the same services the timber layer registers
(reached through service_registry.domain("investments")), so there is one
implementation behind every surface. The grove routers mount outside grove's
auth middleware and so re-assert the X-Grove-API-Key service boundary; caller
identity and enterprise scope arrive as canopy-forwarded X-User-* headers.
Every investing surface is capability-gated: only an investments-provisioned
tenant can reach it.
Capabilities
Goals & vision (in production)
Config-driven financial-goal creation on the shared HITL engine: goal + timeline
intake → deterministic budget/feasibility → AI-written vision → a generated
inspiration-image gallery → a persisted InvestmentGoal with calendar
milestones. GoalService owns the lifecycle (create/update/progress/soft-delete,
assisted rewrite, in-screen budget/vision/image recalculation). The premium
goal dashboard adds time-adjusted progress and a summary roll-up.
Personalized goal imagery (in production)
GoalImageService composes grove's generic image service with cambium to
generate inspiration imagery for a goal. When a user has uploaded a self photo
(a consent-gated PersonReference), goal images preserve their likeness via
gemini-2.5-flash-image. Image regeneration is generative (token cost), so it
runs only on explicit user confirmation.
Portfolio & holdings (in production)
Manual holdings CRUD, buy/sell transactions, and brokerage→goal allocation
(PortfolioService + InvestmentService). The portfolio dashboard (premium)
returns holdings, per-lot positions, watchlist, and a value timeline backed by a
daily PortfolioValueSnapshot scheduler. Brokerage truth (a PortfolioHolding)
and goal allocation (a goal-linked Position carrying portfolio_holding_id)
are kept strictly distinct — allocating never mutates the holding.
Brokerage linkage via Plaid (in production, Plaid PRODUCTION)
PlaidBridge owns link-token creation, public-token exchange, linked-account
listing, idempotent holdings sync, and unlink — composing the configured Plaid
singleton with timber's DB and grove's generic tier guard. The
onboard_investments HITL flow lets a user connect a brokerage (auto-syncing
real holdings) or add a position manually, launchable from home or from chat.
Watchlist & accumulation (in production)
The rich, goal-linked WatchlistItem (AI + advisor recommendations, price
snapshots, buy/sell/stop targets, and an accumulation plan). Items may be
goal-linked or unlinked (a personal watchlist, goal_id nullable). The home
watchlist gadget aggregates every tracked symbol across all goals plus the
unlinked list. GrowthService runs a forward paper-accrual of accumulation
plans and rolls the latest per-item value into goal attainment; projection.py
builds the value-vs-contributed projection (profit = the gap) and can re-fit from
actual snapshots.
Single-stock research (the oak tree) (in production)
stock_research_service runs a config-driven, cost-disciplined flow —
explore → fundamentals → technicals → growth → news sentiment → forecast →
summarize — rendered as an interactive oak tree with category drill-down. Five of
six steps are deterministic/statistical; only the summary may touch an LLM, on a
cambium-distilled payload. The price forecast uses an ensemble whose signal
weights are learned from realized outcomes and records each forecast into a
feedback ledger so accuracy improves over time.
Index & sector research (the heatmap) (in production)
SectorResearchService explores indices across geographies/types, scores
sentiment + volatility across the ~11 GICS sectors, surfaces top companies and
movers/shakers, and renders an index×sector sentiment heatmap with per-cell
drill-down into single-stock research. Sentiment is batched-LLM per index with a
lexical fallback.
Macro — current-events deep dive (built)
macro/ answers the question single-stock research cannot: not "how is
this company doing" but "what is happening to it". It reads world-scoped news
(GDELT — keyless, so it works with no vendor keys at all — plus AlphaVantage
topic news and Finnhub general news), collapses syndicated copies of one story
into one event, and buckets articles into a curated 18-theme macro taxonomy
(data/macro/themes.yaml): tariffs, sanctions, armed conflict,
drought and crop failure, critical-minerals shortages, political uncertainty,
central-bank policy, supply-chain disruption, the AI capital cycle, and more.
Each theme is then scored on four named terms rather than one opaque number — attention (how loud, relative to the rest of the board), conviction (how one-directional the coverage is), market confirmation (do the instruments the theme says should move actually move that way, scaled by each instrument's own volatility), and breadth (how much of the sector map it touches). That third term is what separates this from a news summary: a theme with loud coverage and flat proxies is reported as loud but unpriced, which is a different and far more useful object than "important".
The deep dive then crosses the affected sectors against the user's own holdings and watchlist, writes the desk note (one supervised model call per theme, over facts it did not choose), and produces dated, falsifiable things to watch — the taxonomy's own release/meeting calendar plus one-sigma trigger levels computed from each proxy's current price.
Three refusals are built in, each of them a bug this codebase has already paid for once. A missing market term is redistributed, never imputed as zero (zero says "the market disagrees"; missing says "we could not look"). An empty board renders its cause, never a blank panel that reads as a quiet week. And no watch item ever claims an official release date — the taxonomy knows CPI is monthly, it does not know the next print is the 11th, so every cadence item carries a review date, labelled as one.
Degrades rather than blanks: with no vendor keys the board is thinner but real, and with no model key the briefing is still written from the curated taxonomy.
Adaptive prediction engine (in production)
A shared meta-learning "brain" (adapt/: estimate, cascade, cluster, ledger,
backtest, track-record) that predicts short-horizon moves for a resolved
universe (watchlist ∪ default), observes realized outcomes, and matures/scores
them — closing an autonomous continual-learning loop on grove's beat. The
cascade blends multiple strategies (reversal → news-cluster → deep) over one
shared model. A premium+ track-record endpoint exposes the honest,
outcome-scored accuracy.
Evaluation harness — adapt evaluate (in production, CI-gated)
adapt/evaluate/: the instrument the prediction engine is judged on. Overlap-aware
effective sample size, a circular block bootstrap over dates, purged and embargoed
walk-forward, a five-entry baseline board (random sign, always-up, buy-and-hold,
persistence, 12-1 momentum), a pre-registered trials ledger and deflated-Sharpe
correction — behind one command, wired into CI as GATE 0.
⭐ It exists because the engine was unmeasured, not bad: 5-day bets placed daily overlap by four days, so 47.84% over 1,392 bets is 0.83σ from chance, not 1.61σ. Every rate on this package's surfaces now carries its denominator, its effective sample size and an interval. See docs/evaluation-harness.md.
Financial DNA (in production)
FdnaService computes a behavioral investor profile — persona (e.g.
architect / pragmatist / guardian / explorer / delegator), risk score, key
dimensions, and deterministic allocation / strategy / guardrail recommendations —
from a 16-screen / 6-phase HITL assessment scored on both answers and answer
timing. Reachable as a launchable flow and via the acorn assistant.
Advisor portfolio review (built, enterprise + admin)
AdvisorReviewService detects holdings drift from the Financial DNA target
allocation, drafts an AI rebalancing recommendation, and routes it to the
advisor_review work queue with a real SLA; an advisor claims / reviews /
approves or rejects it. Includes SLA-monitor and drift-scan schedulers and an
admin SLA override.
Market data & analytics (in production)
A self-contained market-data stack migrated out of timber core: multi-provider
fetchers (yfinance keyless default, with Alpha Vantage / Finnhub / Polygon when
keyed); data_processor (returns, risk, technical indicators, portfolio
metrics); analytics (fundamental ratios, growth, valuation); sentiment
(lexical + optional LLM); forecasting (signal→model→ensemble); and
stock_search over a bundled symbol catalog that powers the type-ahead and HITL
stock pickers.
Two fetch services are registered and they are not interchangeable by accident:
| registry name | service | reads through the cache? |
|---|---|---|
data_fetcher |
stock_data_service |
no — every call hits the vendor |
stock_cache |
stock_cache_service |
yes — StockDataSnapshot, per-metric TTL (info 24h · news 1h · price 1h · financials 168h), past price ranges immutable |
stock_cache exposes the same four fetch_* methods as data_fetcher
(provider_order included), so it substitutes for it with no call-site change;
the get_* names remain as aliases. The research flow (stock_research_service)
uses stock_cache. Other callers — watchlist, alerts, growth, portfolio,
sector research, the worker's prediction loop — still hold data_fetcher and
refetch on every call.
Cross-cutting integrations
The domain also contributes, via the registry, its own calendar event
generators (earnings / ex-dividend from holdings, goal target-dates +
milestones) and calendar taxonomy — generate_goal_events takes an optional
goal_id= so the goal write path can run the SAME generator for one goal and
put a new (or restored, or re-dated) goal on the calendar inside the request,
rather than waiting up to 24h for daily_calendar_refresh; hydration
handlers for the home
goals/portfolio widgets; Rustle context-access rules for its
context-conversations; monitor session specs so grove's stuck-workflow
monitor can name investing session tables generically; usage queries so the
billing guard can count investing tier limits (max_goals, max_holdings,
max_watchlist, max_goal_workflow_runs, max_index_research_runs) without
grove naming any investing model; and the nav items + home widgets grove
composes into its generic shell.
Grove HTTP surface
InvestmentsGroveDomain mounts ten routers on grove's app (each behind the
X-Grove-API-Key boundary and the investments capability gate):
| Prefix | Purpose |
|---|---|
/api/v3/watchlist |
goal + unlinked watchlist, add/update, projection, advisor review |
/api/v3/goals |
goal list/detail, dashboard, assisted rewrite, in-screen recalc (budget/vision/images), reopen-save, soft-delete |
/api/v3/portfolio |
home snapshot, premium dashboard, manual holdings CRUD, transactions, brokerage→goal allocation, market data |
/api/v3/external/plaid |
link-token, token-exchange, linked accounts, holdings sync, unlink, health |
/api/v3/advisor |
advisor inbox, review detail, claim/approve/reject, admin SLA override |
/api/v3/adapt |
premium+ prediction track-record |
/api/v3/investments |
semantic + hybrid vector search over cached financial data |
/api/v3/ui · /api/human-tasks |
stock symbol search / by-symbol lookup (type-ahead + HITL pickers) |
/api/v3/calendar |
on-demand single-user calendar-event generation |
Acorn agent surface
InvestmentsAcornDomain registers the "investments" assistant profile (the
Oracle financial persona, greeting, suggestions, and investments-tagged tool
resolution) plus ~20 gated tools, each user-scoped through the forwarded
principal and returning markdown. They cover the user's own data
(get_user_goals, get_portfolio_holdings, get_goal_investments,
get_watchlist, get_financial_dna_profile), watchlist writes
(add_to_watchlist), analysis (analyze_stock_info, analyze_stock_news,
analyze_stock_price, analyze_portfolio, market_outlook, advice_on_topic,
explain_concept, investment_philosophy, quote_of_the_day), semantic search
(vector_search, search_stocks, search_indices, hybrid_search), and
research_stock, which drives the grove oak-tree flow and hands back the
interactive A2UI screen. All financial knowledge lives in this package — acorn
core keeps only the generic @tool / grove-passthrough / profile mechanisms.
Feature packages (bundled grove config)
The grove.feature_packages entry point points grove at
feature_packages/, ten portable, self-describing config bundles that
grove's sync_configs discovers and seeds into the grove DB — so the domain's
workflows/tasks/HITL screens/pages/nav travel with the package:
goal_workflow · onboard_investments · financial_dna · stock_research ·
index_research · portfolio · advisor_portfolio_review ·
investment_growth · macro · investments (base decisioning tables).
Models
Two Python models (WatchlistItem, WatchlistValueSnapshot) plus ~30 tables
built from bundled YAML by the timber ModelFactory at Step 8.5 — byte-identical
to grove's prior definitions, so the live tables map field-for-field and
create_all is a no-op for them. Notable tables: investment_goals,
goal_images, goal_milestones, positions, portfolio_holdings,
portfolio_transactions, portfolio_value_snapshots, plaid_item,
financial_account, the read-through cache tables (cached_stock_data,
cached_market_indices, stock_data_snapshots, …), the workflow-session tables
(stock_research_sessions, index_research_sessions, financial_dna_sessions,
create_financial_goal_sessions, onboard_investments_sessions,
advisor_review_sessions, macro_sessions), and the prediction feedback tables (predictions,
prediction_outcomes, prediction_scores, news_clusters).
How it plugs in
Each host loads only its own entry-point group; discovery is automatic once the package is installed:
[project.entry-points."timber.domains"]
investments = "oak_domain_investments:InvestmentsDomain"
[project.entry-points."grove.domains"]
investments = "oak_domain_investments.grove_plugin:InvestmentsGroveDomain"
[project.entry-points."grove.workers"]
investments = "oak_domain_investments.worker_plugin:InvestmentsWorkerDomain"
[project.entry-points."grove.feature_packages"]
investments = "oak_domain_investments:feature_packages_dir"
[project.entry-points."grove.secret_specs"]
investments = "oak_domain_investments:secret_specs"
[project.entry-points."acorn.domains"]
investments = "oak_domain_investments.acorn_plugin:InvestmentsAcornDomain"
For local development against checkouts, each host also honors an env-var
fallback (e.g. TIMBER_DOMAIN_PLUGINS=oak_domain_investments:InvestmentsDomain)
so the plugin loads without a reinstall.
Install
pip install oak-domain-investments
Requires Python 3.13+ (both first-party dependencies declare ^3.13; the
package advertised 3.11 until 0.53.0 and could never install there).
Depends on timber-common>=0.6.16, plaid-python, and
cambium-ai. The import package is oak_domain_investments.
Develop / test against a sibling timber checkout:
# from the repo root, with ../timber on the path
PYTHONPATH=../timber:. TIMBER_DOMAIN_PLUGINS=oak_domain_investments:InvestmentsDomain \
python3 -m pytest tests/ -q
Secrets & configuration
All provider secrets are optional and fetched from ranger by grove's
bootstrap via the grove.secret_specs entry point — a missing key degrades a
feature rather than blocking startup:
- Market data —
ALPHA_VANTAGE_API_KEY,FINNHUB_API_KEY,POLYGON_API_KEY(absent → the fetchers fall back to the keyless yfinance provider). - Plaid —
PLAID_CLIENT_ID,PLAID_SECRET,PLAID_SECRET_PRODUCTION,PLAID_ENVIRONMENT,PLAID_RECOVERY(absent → brokerage linking is simply unavailable).
License
Apache-2.0. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file oak_domain_investments-0.56.0.tar.gz.
File metadata
- Download URL: oak_domain_investments-0.56.0.tar.gz
- Upload date:
- Size: 912.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
efb7b6dd23dd6447a6e21d6a996f2c724e0202e08a20a1b166c2a703520b12b0
|
|
| MD5 |
c7139fada72ac6ee098d2a9eed046be7
|
|
| BLAKE2b-256 |
f12d9c296e235fc197ef72d9a4856cff4c74aaece1cdb2749eb35ee36ab1bb74
|
Provenance
The following attestation bundles were made for oak_domain_investments-0.56.0.tar.gz:
Publisher:
release.yml on oakquant-ai/oak-domain-investments
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oak_domain_investments-0.56.0.tar.gz -
Subject digest:
efb7b6dd23dd6447a6e21d6a996f2c724e0202e08a20a1b166c2a703520b12b0 - Sigstore transparency entry: 2742690506
- Sigstore integration time:
-
Permalink:
oakquant-ai/oak-domain-investments@f49f5db8443ab39e8e5f391bf400e18e51963757 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/oakquant-ai
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f49f5db8443ab39e8e5f391bf400e18e51963757 -
Trigger Event:
push
-
Statement type:
File details
Details for the file oak_domain_investments-0.56.0-py3-none-any.whl.
File metadata
- Download URL: oak_domain_investments-0.56.0-py3-none-any.whl
- Upload date:
- Size: 815.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5933227943912c0f2b55deeaf4d0fa1b074dbdd3c610c55f55e0bf8f7cf82a6b
|
|
| MD5 |
7a84f90def01c4bbe4b503f7cfbd1116
|
|
| BLAKE2b-256 |
52001844e531bd98c873ca4af141e9f255e0d85500e404773497e69ccaaf6a53
|
Provenance
The following attestation bundles were made for oak_domain_investments-0.56.0-py3-none-any.whl:
Publisher:
release.yml on oakquant-ai/oak-domain-investments
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oak_domain_investments-0.56.0-py3-none-any.whl -
Subject digest:
5933227943912c0f2b55deeaf4d0fa1b074dbdd3c610c55f55e0bf8f7cf82a6b - Sigstore transparency entry: 2742690566
- Sigstore integration time:
-
Permalink:
oakquant-ai/oak-domain-investments@f49f5db8443ab39e8e5f391bf400e18e51963757 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/oakquant-ai
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f49f5db8443ab39e8e5f391bf400e18e51963757 -
Trigger Event:
push
-
Statement type: