feat: dealer flow, mirror portfolio (M21), options convexity, FINRA short interest, alert producers, vendor gate
CI / Test & Type-Check (push) Canceled after 0s
CI / Test & Type-Check (push) Canceled after 0s
Snapshot of in-progress module work across multiple slices: - Dealer Flow: dealerExposureEngine, dealerMapService, dealerMapExplain, dealerMapIntegrity, dealerMapReplay, dealerStudyEngine, hanStyleLevels - Mirror Portfolio (M21): fundRepository, captureIngest, mirrorAlertProducers, fund holdings strip, live book, position capture ingest - Options: BSM, NormalizedOptionSurface types, OptionsChainRouter, ConvexityGate, option legs panel - Alert producers: vixLevel, rotation, thesis, unlock, portfolioRisk, mirror (fund_capture, fund_13f, mirror_diff) - FINRA short interest adapter + queue integration - SEC company tickers adapter + ingest (symbol search index seed) - Vendor gate (rate-limit-first data plane, ADR-0009) - CUSIP registry, reverse 13F refresh, stock float service - LRU cache, portfolio backtest engine - Frontend: dealer-flow, funds, journal, lab, monitor, plan, portfolio, reports, screener, strategies, theses, guided-start, exits, more pages - Volume profile, workspace profile, visibility-aware poll - ADRs 0010 (mirror math not advice), 0011 (symbol search index) - VENDOR_INTEGRATIONS.md, END_USER_TEST.md - .gitignore: exclude DBs, .DS_Store, local config, agent scratch
This commit is contained in:
@@ -70,7 +70,27 @@ Queue health exposes active **source cool-downs** so operators can see “Yahoo
|
||||
- **Positive:** New features have a clear rule: “cache first, queue refresh, static if needed.”
|
||||
- **Trade-off:** After a cool-down, data may be minutes-to-hours stale until the next successful drain.
|
||||
- **Trade-off:** Static ETF weights drift until the next successful Yahoo refresh (acceptable for education peek panels).
|
||||
- **Follow-ups:** Route remaining live Yahoo calls in `market.condition` / peer resolution through the same cache-or-queue pattern; surface cool-downs in admin queue UI.
|
||||
- **Follow-ups:** Surface per-family cool-downs in admin queue UI (process-wide `vendorGate` + queue_state).
|
||||
|
||||
## Implementation (2026-08 hardening)
|
||||
|
||||
All vendors share `app/server/src/services/vendorGate.ts` with **runtime registration** so future integrations do not require editing a closed union:
|
||||
|
||||
1. `registerVendorIntegration({ family, sourceKinds, policy })` before traffic
|
||||
2. `VendorSourceAdapter` / `defineVendorAdapter` auto-wrap `fetchOne`
|
||||
3. `AdapterQueue` constructor **throws** if any adapter `source_kind` lacks a family
|
||||
4. CI: `vendorHttpGuard.test.ts` fails on bare `fetch(` outside the gate modules
|
||||
5. Handbook: `docs/VENDOR_INTEGRATIONS.md`
|
||||
|
||||
| Family | Call style | Drain budget |
|
||||
|--------|------------|--------------|
|
||||
| yfinance | `withVendorGate` (yahoo-finance2 + options) | 3 |
|
||||
| sec | `secHttp` → vendorGate | 1 |
|
||||
| fred / finra / nasdaq / reddit | `vendorFetch` | 1 |
|
||||
| x | `withVendorGate` around bird CLI | 1 |
|
||||
| *(future)* | `registerVendorIntegration` + `VendorSourceAdapter` | policy.drainJobBudget |
|
||||
|
||||
A 429 cools the **family** (every `source_kind` in that family), not just the one job. Per-request min gaps are process-wide; job min-intervals in AdapterQueue are coarser backup.
|
||||
|
||||
## Rejected alternatives
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# ADR-0010: Mirror module outputs replication math, not advice (M21)
|
||||
|
||||
The Primary Rule (ADR-0007) forbids buy/sell/hold advice, yet the Mirror Portfolio module (M21) computes exact buy/sell quantities and pushes them as alerts. This is a deliberate, scoped carve-out: the module is a calculator for a **user-stated replication goal**, not a recommendation engine. The user declares the intent ("mirror Alpine Fox"); the app computes the math to satisfy it — the same "math, not instructions" posture as the SizingEngine. Every M21 string that would read "you should buy X because the fund did" is lint-blocked; the actionable alert uses mechanical framing ("to match your mirror target, the delta is N shares").
|
||||
|
||||
**Status:** accepted (2026-07-28, grilling session).
|
||||
|
||||
**Considered Options**
|
||||
- Tracker-only module (facts without mirror math) — rejected: loses the module's core job (what/when/how-much replication).
|
||||
- Mirror without alerts (surface-only) — rejected: the diff stays hidden from the "close tabs" mobile loop the user asked for.
|
||||
- Mirror output as advice-with-disclaimer — rejected: the Primary Rule is a posture, not a disclaimer regime.
|
||||
|
||||
**Consequences**
|
||||
- The actionable mirror alert is the closest Investor Flow comes to instructions; the mechanical-string lint is mandatory on every M21 string.
|
||||
- The module never renders a judgment on the fund's moves (option-2 reconciliation: computed, displayed, no verdicts).
|
||||
- Future features must not let the mirror drift into "you should mirror this fund" framing; the intent must always originate with the user.
|
||||
@@ -0,0 +1,27 @@
|
||||
# ADR-0011: Symbol Search Index — issuer CIK via SEC seed, hybrid add-resolve
|
||||
|
||||
Symbols lacked issuer identity and the app's only autocomplete corpus was the demand set
|
||||
(already-tracked symbols), making new-symbol discovery impossible. We decided: the `symbols`
|
||||
table gains an issuer CIK column, seeded from the SEC's `company_tickers.json` bulk file
|
||||
(~12k US tickers, overwritten in place daily) via a scheduled queue job; autocomplete resolves
|
||||
local-first against this table; adding an unmatched string is a soft-block ("not in the SEC
|
||||
registry — add anyway?") followed by background hydration through the existing adapter queue;
|
||||
and the symbol page surfaces a two-tier "who holds this" strip (tracked funds with weight, then
|
||||
institution count). A symbol carries TWO CIK roles in this app — the issuer CIK (the company the
|
||||
ticker represents, on `symbols`) and the filer CIK (the fund that files 13Fs, on `tracked_funds`)
|
||||
— same identifier space, different edges.
|
||||
|
||||
**Status:** accepted (2026-07-28, grilling session).
|
||||
|
||||
**Considered Options**
|
||||
- Live yfinance search suggestions on keystroke — rejected: violates ADR-0009 (no live vendor call on the request path); rate limits make autocomplete unreliable.
|
||||
- Curated universe seed (S&P 500 / NASDAQ 100) — rejected: narrow, and carries no issuer CIKs.
|
||||
- Strict resolve (block unknown symbols) — rejected: users need fresh/post-file-update tickers; strictness is a question, not a wall.
|
||||
- Loose add (no confirmation) — rejected: silently accepting typos/delisted names pollutes the "known database" the autocomplete relies on.
|
||||
- Full-row upsert on daily seed — rejected: would clobber yfinance-hydrated `ticker_kind` (gates module applicability), `sector`, `industry`, `peers`; merge policy is column-scoped instead.
|
||||
|
||||
**Consequences**
|
||||
- Two ownership domains on `symbols`: SEC fills issuer identity (cik, name, exchange); yfinance hydration owns classification (ticker_kind, sector, industry, peers). No overwrite war.
|
||||
- Non-SEC rows (crypto, some ETFs, indexes) are never purged — the SEC universe is a subset, not the whole.
|
||||
- The daily seed makes the local index essentially complete for US equities, so live-search fallback is rarely needed.
|
||||
- Autocomplete correctness now depends on the scheduled materializer running; a missed day only delays new tickers by one day (stale-while-revalidate).
|
||||
Reference in New Issue
Block a user