Metadata-Version: 2.4
Name: oak-domain-investments
Version: 0.45.0
Summary: Investments domain plugin for the OakQuant timber substrate (watchlist + plaid + data_fetcher + data_processor + analytics + forecasting + investing_operations migrated from core; more to follow).
Author-email: Pumulo Sikaneta <pumulo@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: cambium-ai>=0.2.6
Requires-Dist: plaid-python>=39.2.0
Requires-Dist: timber-common>=0.6.16
Provides-Extra: dev
Requires-Dist: fastapi>=0.110; extra == 'dev'
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# 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.**

[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE)
[![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
[![Status: Live in production](https://img.shields.io/badge/status-live_in_production-brightgreen.svg)](#)
[![PyPI](https://img.shields.io/pypi/v/oak-domain-investments.svg)](https://pypi.org/project/oak-domain-investments/)

## 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.

### 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.

### 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/`, nine 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` · `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`), 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:

```toml
[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

```bash
pip install oak-domain-investments
```

Requires Python 3.11+. 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:

```bash
# 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](./LICENSE).
