ETF NAV Discount Strategy — Model Card
Strategy ID: etf-nav-discount
Version: 0.1 (research)
Status: research — not ready for feature-dev
Date: 2026-05-12 UTC
This card describes what feature-developer needs to know to productize this strategy. It is premature to file implementation cards until: (a) backtest has been run on real data, (b) Kristerpher makes the headline-vs-adjacent call per issue #479, (c) business-legal-researcher has scoped the regulatory question.
What It Does
Evaluates a weekly trigger: if ticker trades at a discount of threshold_pct or
more below its published NAV, submit a market buy order for qty shares in the
user's paper (or live, post-opt-in) Alpaca account.
The strategy is authored by the user in natural language, parsed by Claude into a validated DSL struct, confirmed by the user before activation, then executed by a background scheduler.
Input Schema
Strategy execution trigger (internal, not user-facing)
{
"strategy_id": "string (uuid4)",
"ticker": "string",
"threshold_pct": "number (negative float, e.g. -0.50)",
"qty": "integer",
"order_type": "string (market | limit)",
"paper_only": "boolean",
"max_notional_usd": "number"
}
Condition evaluation inputs (fetched at evaluation time)
{
"market_price": "number (USD)",
"nav_per_share": "number (USD)",
"nav_date": "string (ISO 8601 date)",
"eval_datetime_utc": "string (ISO 8601 datetime)"
}
Output Schema
Condition evaluation result
{
"eval_id": "string (uuid4)",
"condition_met": "boolean",
"discount_pct": "number",
"data_quality_flags": ["array of strings"],
"execution_id": "string (nullable)"
}
Order submission result (if condition met)
{
"execution_id": "string (uuid4)",
"order_status": "string (pending | filled | cancelled | error)",
"alpaca_order_id": "string (nullable)",
"fill_price": "number (nullable)",
"qty_filled": "integer (nullable)"
}
Data Dependencies
| Data Source | Field | Update Frequency | Licensing |
|---|---|---|---|
Alpaca /v2/stocks/{symbol}/quotes/latest |
market_price | real-time | Alpaca Basic (free for funded accounts) |
| Fund family sites (SPDR, iShares, Vanguard) | nav_per_share | end-of-day | Free for non-commercial; commercial license TBD — flag to BLR |
| ETF.com (fallback) | nav_per_share | end-of-day | TBD — flag to BLR |
Alpaca /v2/account |
pattern_day_trader flag | live | Same as above |
Compute + Storage Budget (per user)
At MVP (3 strategies per user, weekly schedule):
| Operation | Frequency | Cost |
|---|---|---|
| Claude parse call (strategy creation) | 1× per strategy | ~$0.0001 (Haiku, 500 tokens) |
| NAV fetch (fund site HTTP GET) | 1× per week per strategy | Negligible bandwidth |
| Alpaca quote fetch | 1× per week per strategy | Alpaca API call, within free tier |
| ConditionEvaluation DB row | 52× per year per strategy | ~1 KB per row; 156 KB/user/year |
| ExecutionRecord DB row | ~0-10× per year per strategy | ~500 B per row |
At 1,000 users: ~156 MB of evaluation data per year. Negligible at Postgres scale.
Expected Latency
- NAV fetch: 300–2,000 ms (HTTP to fund site)
- Alpaca quote fetch: 50–200 ms
- Condition evaluation (Python): < 10 ms
- Alpaca order submission: 100–500 ms
- Total execution pipeline: < 3 seconds per strategy per scheduled window
Failure Modes (Summary)
See failure-modes.md for full documentation. Key production risks:
- Stale NAV — abort + alert if NAV is > nav_lag_days old
- NAV source offline — multi-source fallback; abort if all fail
- Signal fires only in stress — expected behavior; UI must communicate clearly
- Over-accumulation without exit — position cap is the critical safety rail
- PDT rule violation — check Alpaca account flag before every execution
- Regulatory (advice framing) — Claude-generated UI copy must be reviewed by counsel
What to Monitor in Production
| Signal | Threshold | Alert |
|---|---|---|
| NAV source failure rate | > 10% of weekly fetches | ops@ alert |
| Condition evaluation abort rate | > 20% of evaluations | ops@ alert |
| Order rejection rate | > 5% of submitted orders | ops@ alert + user notification |
| Strategy accumulation per user | > max_notional_usd | auto-pause + user notification |
| Claude parse failure rate | > 15% of parse attempts | ops@ alert |
| PDT block rate | Any occurrence | User notification |
Open Questions Before Feature-Dev
These must be resolved before implementation cards are filed:
-
OQ-1 (hard blocker — regulatory): business-legal-researcher must scope the investment-adviser registration question for automated execution of user-authored strategies. Does the LLM-as-parser framing hold? What disclosures are required?
-
OQ-2 (hard blocker — data): commercial licensing for NAV data (fund sites, ETF.com). BLR must confirm before scraper ships to production.
-
OQ-3 (backtest blocker): backtest has not been run on real historical data. The strategy cannot be promoted to
backtestedstatus without runningreference.pyagainst actual Alpaca historical bars + NAV data. Assign to a feature-developer with Alpaca sandbox access. -
OQ-4 (product decision): Kristerpher's headline-vs-adjacent call per issue #479. This shapes whether this strategy is the product's primary surface or an add-on.
-
OQ-5 (UX): human-confirm flow design. Does every trigger require confirm, or only the first per strategy per live-trading activation? Product decision.
Disclaimer
This model card describes a research-stage strategy. No backtest has been run on real data. All performance claims are hypotheses, not validated results. This document does not constitute investment advice. Productization requires regulatory clearance, validated backtest results, and explicit operator decisions on the items in the Open Questions section above.