Status: Draft
Owner: software-architect
Last updated: 2026-04-22
Parent epic: #183 — Multi-tenant architecture (reframed)
Related PRs: #189 (MBT engine design), #183 (parent epic)
Related ADRs: 0003, 0013, 0014, 0015
Resolves MBT §14 Qs: 1 (starting cash), 2 (fill aggressiveness), 3 (interest-rate narrative)
MBT is the Raxx-native paper-trading engine. On PR #189, the three blocking open questions in §14 (starting cash default, multi-leg fill aggressiveness, interest-rate model) were answered by the user not with fixed numbers, but with a directive: build a profile-driven experience that infers sensible defaults from the user's stated goals, and teach rather than configure.
This doc specifies the investor-profile model: the onboarding question sequence that infers a profile, the three starter profiles and their per-profile MBT defaults, the educational overlay that explains every trade outcome, and the "stretch goals" cadence mechanic. It does NOT implement these — it shapes the boundary so feature-developer sub-cards can be filed.
These constraints are non-negotiable (ADR 0002, 0003, 0013, 0015):
audit_log row.Three profiles ship in v1. Names below are placeholders — the user must confirm final names before implementation (see §12, Open Question #1).
| Attribute | Trial | Income Builder | Diversifier |
|---|---|---|---|
| Placeholder name | Trial | Income Builder | Diversifier |
| Starting cash default | $10,000 | $50,000 | $100,000 |
| User-overridable? | Yes (within tier cap) | Yes (within tier cap) | Yes (within tier cap) |
| Risk temperament | Conservative | Balanced | Aggressive |
| Primary goal family | Learn mechanics | Generate income | Diversify existing book |
| Fill aggressiveness | Conservative (worse-of-mid/offer) | Mid (mid-price) | Aggressive (mid-price + attempt at mid on touch) |
| Interest-rate narrative tone | Teaching-heavy | Balanced | Assume-familiarity |
| Goal cadence default | Weekly | Monthly | Monthly |
| Target persona | New to investing, trialing on a recommendation | Has savings (~$50k), wants income-generating strategies | Already has a trading platform elsewhere; paper-testing here |
Tier caps still apply: Free users have a lower history-retention ceiling regardless of profile. Profile does not override tier limits.
Onboarding is 3–5 questions. Answers derive the profile; the user can override any default immediately after. Onboarding can also be re-run from Settings at any time.
Question sequence (copy is a placeholder — requires product-writing pass):
"What brings you to Raxx?" - "I'm new to options and want to learn the ropes" → signal: Trial - "I have savings I want to put to work through income strategies" → signal: Income Builder - "I already trade elsewhere and want a testing ground for new strategies" → signal: Diversifier
"Have you traded options with real money before?" - No → reinforces Trial signal - Yes, occasionally → supports Income Builder signal - Yes, actively → supports Diversifier signal
"What's your goal cadence — when do you want to measure your paper results?" - Daily → emphasizes Trial or very active Income Builder - Weekly → Trial or Income Builder - Monthly → Income Builder or Diversifier
"What would a meaningful paper-trade win look like for you?" (optional free-form + presets) - "$100 / week income" → Income Builder - "Beat the S&P 500 in 3 months" → Diversifier - "Understand how a vertical spread works" → Trial
(Derived + confirm) Show inferred profile card: starting balance, goal cadence, fill style. User accepts or selects a different profile from a dropdown. This is the override gate.
Profile derivation is a simple scoring function — no ML, no external call. Answers are weights; highest-weighted profile wins; ties break toward the more conservative profile.
Explicit mapping that resolves MBT §14 Qs 1–3:
| MBT parameter (from §14) | Trial | Income Builder | Diversifier |
|---|---|---|---|
| Starting cash balance | $10,000 | $50,000 | $100,000 |
| Multi-leg fill aggressiveness | Conservative: fill at worse-of-mid/offer | Mid: fill at combined NBBO mid | Aggressive: attempt mid; fallback to mid+1 tick |
| Interest-rate narrative tone | Teaching-heavy (full blurb on every accrual event) | Balanced (abbreviated note + tax summary) | Assume-familiarity (dollar amount + tax line only) |
| Paper history retention scope | Tier cap; 90d for Free | Tier cap; 3yr for Pro | Tier cap; unlimited for Pro+ |
| Goal cadence default | Weekly | Monthly | Monthly |
Fill-aggressiveness maps to the legs_fill_aggressiveness field on mbt_accounts. It is user-overridable per-order via a "Fill preference" toggle in Antlers (present but not prominent for Trial; more visible for Diversifier).
The overlay is Raptor-side logic + Antlers-side rendering. It does not require a separate service but does require a new module.
Mechanism:
After POST /api/mbt/orders produces a fill, the response envelope includes a narrative field:
{
"data": { ...order... },
"narrative": {
"headline": "Your vertical spread filled at $1.42 credit",
"body": "...",
"learn_more_key": "vertical_spread_credit_fill",
"tone": "teaching-heavy"
}
}
Antlers renders the narrative block below the fill confirmation. Tone is derived from the user's profile (investor_profiles.narrative_tone). learn_more_key links to a static glossary page (v1: anchor on a /learn page; v2: in-app drawer).
Narrative service: backend_v2/api/services/narrative_service.py. The implementation sub-card will define it. The design contract is:
(trade_event: TradeEvent, profile_tone: NarrativeTone) -> Narrativetrade_event values (v1): fill, partial_fill (v2), cancel, reject, itm_expiry, otm_expiry_worthless, stop_triggered, margin_interest_accrued, dividend_creditedprofile_tone values: teaching_heavy, balanced, assume_familiarityTemplates are keyed by (trade_event, profile_tone). Nine event types × 3 tones = 27 base templates. Multi-leg events (iron condor fill, spread expiry) add another ~12 templates. Total v1 template count: ~40.
Content authorship: v1 ships with engineering placeholder copy. A product-manager or marketing-strategist follow-up card should commission polished copy before GA. See Open Question #4.
When MBT accrues simulated margin interest on a position (nightly Celery task mbt.accrue_margin_interest), the next Antlers session render surfaces a margin-interest card. This is triggered by a new margin_interest_accrued activity type in mbt_activities.
The card (tone: teaching-heavy) contains:
The tax note is ALWAYS present, regardless of tone. The verbosity of points 1 and 2 scales with tone.
Regulatory note: Raxx does NOT advise on whether the user should use margin, which broker to use, or whether the deduction applies to their situation. The note surfaces the fact and defers to a CPA. This is the correct framing under §202(a)(11) — educational disclosure, not individualized advice.
Profile sets baseline goal (e.g., "earn $100/week in paper income"). After N consecutive weeks of the user meeting or exceeding the goal, MBT surfaces a stretch prompt:
investor_profiles.goal_target and resets the streak counter.Implementation:
investor_profiles.goal_target (numeric, nullable — null means no goal set)investor_profiles.goal_cadence (enum: daily / weekly / monthly)investor_profiles.goal_streak_count (integer)investor_profiles.goal_streak_threshold (integer, default 4 for weekly, 2 for monthly)mbt.evaluate_goal_streak runs after each cadence period closes (EOD for daily, week-close for weekly, month-close for monthly)The stretch prompt is delivered via the activity feed as a system_nudge activity type — not email, not push. User can suppress all nudges from Settings.
Separate investor_profiles table (1:1 with users). Separate because: profile can grow (new fields, new tones) without touching the users table; cleaner GDPR export scope; easier to wipe on erasure.
investor_profiles
id PK
user_id FK -> users.id ON DELETE CASCADE, UNIQUE
profile_type ENUM ('trial' | 'income_builder' | 'diversifier')
starting_cash NUMERIC(15,2) -- user-overridable at onboarding
narrative_tone ENUM ('teaching_heavy' | 'balanced' | 'assume_familiarity')
fill_aggressiveness ENUM ('conservative' | 'mid' | 'aggressive')
goal_target NUMERIC(15,2) NULL -- NULL = no goal set
goal_cadence ENUM ('daily' | 'weekly' | 'monthly') DEFAULT 'weekly'
goal_streak_count INTEGER DEFAULT 0
goal_streak_threshold INTEGER DEFAULT 4
onboarding_completed BOOLEAN DEFAULT FALSE
created_at TIMESTAMPTZ
updated_at TIMESTAMPTZ
mbt_accounts.cash_balance is seeded from investor_profiles.starting_cash at account-creation time (not a live FK — a one-time copy). This preserves MBT account independence if the user later changes their profile starting cash.
Migration: additive — new table, no changes to existing mbt_accounts or users columns. Rollback: DROP TABLE investor_profiles (safe; MBT accounts already seeded fall back to their stored cash_balance).
Feature gated behind FLAG_MBT_INVESTOR_PROFILES (independent of FLAG_MBT_PAPER_TRADING).
FLAG_MBT_STRETCH_GOALS) after GA if deferred.PII scope: investor_profiles contains financial goals and risk tolerance. This is PII-lite — not SSN, not income, not net worth — but it is sensitive. Treat with GDPR Article 9 caution (financial data is not explicitly special-category, but risk tolerance + goal is commercially sensitive).
investor_profiles rows included in the mbt.export_user_data Celery task. Export format: JSON alongside mbt_accounts, mbt_positions, mbt_orders.ON DELETE CASCADE from users.id. 30-day cooling per ADR 0003.investor_profiles row persists as long as the user account persists. No independent retention clock needed; it follows the user-account lifecycle.investor_profiles create/update writes an audit_log row with redacted goal_target (store "goal updated" not the dollar value, to avoid PII proliferation in audit logs).Regulatory line — education vs advice (§202(a)(11)):
The profile system adjusts Raxx-controlled defaults based on user-stated preferences. It does NOT: - Recommend a specific security - Recommend a specific strategy - Claim to optimize the user's portfolio - Base its defaults on analysis of market conditions
The "stretch goals" mechanic nudges users toward higher self-stated targets. It does NOT tell them how to achieve those targets. The nudge is: "you said you wanted X; you exceeded it; want to aim for more?" — user-directed throughout.
The educational overlay explains what happened; it does not prescribe what to do next.
Flag for counsel (deferred): if future product iterations introduce profile-based strategy suggestions ("users with Income Builder profiles often run covered calls — here's one for you"), that crosses the §202(a)(11) line. That feature MUST NOT ship without securities-attorney sign-off. This is a standing block on all feature cards that propose profile-based strategy recommendations.
All five resolved by the user on PR #193 (2026-04-22). Decisions below are load-bearing for implementation sub-cards.
See ADR 0015 for the decision record. See mbt-paper-trading-engine.md §14 for the original open questions — Qs 1, 2, and 3 are resolved by this doc.