diff --git a/client/src/app/App.tsx b/client/src/app/App.tsx index 35af4cb..987ed92 100644 --- a/client/src/app/App.tsx +++ b/client/src/app/App.tsx @@ -7,6 +7,7 @@ import { SignalsPage } from '../routes/SignalsPage'; import { PositionsPage } from '../routes/PositionsPage'; import { AnalyticsPage } from '../routes/AnalyticsPage'; import { SettingsPage } from '../routes/SettingsPage'; +import { MethodologyPage } from '../routes/MethodologyPage'; function AuthGate() { const { user, isLoading } = useAuth(); @@ -27,6 +28,7 @@ function AuthGate() { +

Page not found.

diff --git a/client/src/app/AppShell.tsx b/client/src/app/AppShell.tsx index f647cac..677aefd 100644 --- a/client/src/app/AppShell.tsx +++ b/client/src/app/AppShell.tsx @@ -10,6 +10,7 @@ import { ConnectionStatus } from '../features/realtime/ConnectionStatus'; import { ErrorBoundary } from './ErrorBoundary'; import { MobileNavDrawer } from './MobileNavDrawer'; import { NAV_ITEMS } from '../routes/nav'; +import { Disclosure } from '../components/Disclosure'; function truncateAddress(address: string): string { return `${address.slice(0, 6)}…${address.slice(-4)}`; @@ -52,10 +53,11 @@ function SidebarContent({ onNavigate }: { onNavigate?: () => void }) { })} -
+
Paper Trading +
); diff --git a/client/src/app/ConnectScreen.tsx b/client/src/app/ConnectScreen.tsx index 9c04977..78896f6 100644 --- a/client/src/app/ConnectScreen.tsx +++ b/client/src/app/ConnectScreen.tsx @@ -3,6 +3,7 @@ import { isPhantomInstalledWithoutEvmProvider } from '../features/auth/eip6963'; import { WalletList } from '../features/auth/WalletList'; import { Badge } from '../components/ui/badge'; import { Button } from '../components/ui/button'; +import { Disclosure } from '../components/Disclosure'; export function ConnectScreen() { const { @@ -47,6 +48,8 @@ export function ConnectScreen() { Paper Trading environment
+ + {accountChangedNotice && (

{ui}); +} + +describe('Disclosure', () => { + it('renders the compact primary copy with a link to the methodology page', () => { + renderWithRouter(); + expect(screen.getByText(/Paper trading only/i)).toBeInTheDocument(); + expect(screen.getByRole('link', { name: /learn more/i })).toHaveAttribute('href', '/methodology'); + }); + + it('renders the detailed signals copy, distinct from the primary copy', () => { + renderWithRouter(); + expect(screen.getByText(/Signal strength/)).toBeInTheDocument(); + expect(screen.getByText(/not a calibrated probability/i)).toBeInTheDocument(); + expect(screen.getByRole('link', { name: /read the full methodology/i })).toHaveAttribute('href', '/methodology'); + }); + + it('renders distinct copy for every context, never sharing text across contexts', () => { + const contexts = ['primary', 'signals', 'backtesting', 'paper-fills'] as const; + const compactTexts = contexts.map((context) => { + const { container, unmount } = renderWithRouter(); + const text = container.querySelector('p')?.textContent; + unmount(); + return text; + }); + expect(new Set(compactTexts).size).toBe(contexts.length); + }); + + it('never uses banned phrases in any context/variant combination', () => { + const banned = [ + 'guaranteed', + 'safe profit', + 'high-confidence winner', + 'best trade', + 'cannot lose', + 'proven return', + 'buy now', + 'sell now', + ]; + const contexts = ['primary', 'signals', 'backtesting', 'paper-fills'] as const; + for (const context of contexts) { + for (const variant of ['compact', 'detailed'] as const) { + const { container, unmount } = renderWithRouter(); + const text = container.textContent?.toLowerCase() ?? ''; + for (const phrase of banned) { + expect(text).not.toContain(phrase); + } + unmount(); + } + } + }); +}); diff --git a/client/src/components/Disclosure.tsx b/client/src/components/Disclosure.tsx new file mode 100644 index 0000000..8c044f5 --- /dev/null +++ b/client/src/components/Disclosure.tsx @@ -0,0 +1,83 @@ +import { Info, TriangleAlert } from 'lucide-react'; +import { Link } from 'wouter'; +import { cn } from '../lib/utils'; + +/** + * DISCLOSURE-001's single reusable disclosure component. Copy here must + * match docs/product/paper-trading-and-educational-scope.md exactly -- + * that document is the source of truth; this file implements it. + * + * `compact` is a persistent, low-visual-weight reminder (rendered once in + * the app shell sidebar, not duplicated per-page). `detailed` is a fuller + * explanation placed near the specific surface it contextualizes (Signals + * page, order ticket). Both use an icon *and* text, never color alone, to + * convey "this is informational/cautionary" -- the mission's explicit + * accessibility requirement for disclosure text. + */ + +export type DisclosureContext = 'primary' | 'signals' | 'backtesting' | 'paper-fills'; + +const COPY: Record = { + primary: { + compact: 'Paper trading only -- simulated fills, real market data, no real money.', + detailed: + 'LiquidAlpha is an educational paper-trading simulator. Prices are sourced live from Hyperliquid; every trade, fill, and result is simulated. Nothing here is financial advice, and no path in this product can place a real order on any exchange.', + }, + signals: { + compact: 'Signal strength reflects indicator agreement, not a probability of winning.', + detailed: + 'Signals are generated from real technical indicators (EMA, MACD, RSI, ADX, Fisher Transform, Keltner Channel), composed into a versioned, explainable 0-100 Signal strength score. This score measures how strongly the indicators agree with each other -- it is not a calibrated probability, a win-rate estimate, or investment advice. Past indicator agreement does not predict future price movement.', + }, + backtesting: { + compact: 'Backtest results are historical simulations, not a guarantee of future performance.', + detailed: + "Backtests replay historical Hyperliquid candles through this platform's real signal-generation logic, with documented, conservative assumptions about entry timing, slippage, fees, and funding. A strategy's historical simulated performance is not a guarantee, promise, or reliable predictor of how it would perform going forward -- markets change, and this engine cannot account for conditions it hasn't seen.", + }, + 'paper-fills': { + compact: 'Simulated fill -- not a real exchange execution.', + detailed: + 'Every paper fill is priced from a real Hyperliquid reference price, with documented simulated slippage and fees applied on top. No real order is ever sent to Hyperliquid or any other exchange.', + }, +}; + +export interface DisclosureProps { + variant: 'compact' | 'detailed'; + context: DisclosureContext; + className?: string; +} + +export function Disclosure({ variant, context, className }: DisclosureProps) { + const copy = COPY[context]; + + if (variant === 'compact') { + return ( +

+ +

+ {copy.compact}{' '} + + Learn more + +

+
+ ); + } + + return ( +
+
+ + Educational simulation +
+

{copy.detailed}

+ + Read the full methodology + +
+ ); +} diff --git a/client/src/features/execution/OrderTicket.tsx b/client/src/features/execution/OrderTicket.tsx index 5702abf..c08b7f0 100644 --- a/client/src/features/execution/OrderTicket.tsx +++ b/client/src/features/execution/OrderTicket.tsx @@ -11,6 +11,7 @@ import { formatPrice } from '../../lib/format'; import { ApiError } from '../../lib/api'; import { submitOrder as submitOrderRequest } from './api'; import { useMarkets } from '../markets/useMarkets'; +import { Disclosure } from '../../components/Disclosure'; import type { Side, OrderType } from './types'; const ASSETS = ['BTC', 'ETH', 'SOL'] as const; @@ -218,6 +219,8 @@ export function OrderTicket() { Paper Trading -- simulated fill, no real exchange + +
+

{title}

+
{children}
+ + ); +} + +export function MethodologyPage() { + return ( +
+
+ + + Back + +

Methodology

+

+ What LiquidAlpha is, what every number on this platform means, and exactly which assumptions produced it. +

+
+ +
+

+ An educational paper-trading simulator for Hyperliquid perpetuals. Every price is real -- sourced live from + Hyperliquid, with an explicitly-labeled CoinGecko fallback. Every trade, fill, fee, funding charge, and + backtest result is simulated. No real money, no real orders, no real exchange, ever -- this platform has no + code path that can sign or submit a real order. +

+

Not financial advice. Not a signal service. Not a broker, exchange, or custodian.

+
+ +
+

+ Hyperliquid is the primary source for prices, candles, and funding history. If Hyperliquid becomes + unreachable, market data falls back to CoinGecko, and every price/candle carries an explicit source label so + it's never presented as Hyperliquid-sourced when it isn't. The platform's own health status --{' '} + live,{' '} + degraded,{' '} + fallback, or{' '} + unavailable -- reflects which of these is + currently true. +

+
+ +
+

+ Signals are generated from real technical indicators -- EMA50/EMA200, MACD, RSI, ADX, Fisher Transform, and + Keltner Channel -- computed from historical price data. A signal only fires when the underlying trend (EMA) + and momentum (MACD) agree on direction; disagreement produces no signal at all, never a forced call. +

+
+ +
+

+ Signal strength is a deterministic, versioned 0-100 score composed of six weighted components: trend + agreement, momentum agreement, trend-strength confirmation, volatility suitability, data freshness, and + indicator availability. It measures how strongly the available indicators agree with each other and with the + underlying trend -- it is not a probability of winning, a confidence level, or an expected return. Nothing on + this platform has been backtested to establish a real relationship between this score and actual trade + outcomes. +

+
+ +
+

+ Backtests replay historical Hyperliquid candles through the exact same signal-generation logic that produces + live signals, with a strict no-lookahead guarantee: a signal decision at any point in history only ever sees + data that would genuinely have been available at that moment. A fired signal enters at the next candle's open + (never the signal candle's own close), with documented slippage, fees, and (optionally) funding applied. + Same-candle stop/target collisions resolve conservatively as a loss. Results below 10 trades show nothing; + results below 30 show only basic figures; full statistics require 30+ trades -- the same sample-adequacy + discipline applied to live paper-trading performance metrics. +

+
+ +
+

+ Every paper fill is priced from a real Hyperliquid (or labeled-fallback) reference price, with documented + simulated slippage and a flat fee assumption applied on top. Open positions accrue real Hyperliquid funding + rates over time, pro-rated by how long the position has actually been open. A liquidation price shown for a + leveraged position is an estimate using a single flat maintenance-margin assumption -- not Hyperliquid's real, + per-asset, tiered margin schedule, and not accounting for funding already paid. +

+
+ +
+
    +
  • Perpetuals only -- no spot-market simulation.
  • +
  • No reduce-only order behavior.
  • +
  • No cross-margin portfolio simulation -- each position is modeled independently.
  • +
  • + A single flat fee and a single flat maintenance-margin assumption, not Hyperliquid's real tiered schedules. +
  • +
  • + Backtests use one documented entry-timing/exit assumption set -- not every possible execution strategy. +
  • +
+
+ +
+

+ Every number this platform shows is traceable to the exact model version that produced it: a signal-engine + rule version, a Signal-strength score-model version, a paper-fill model version, and a backtest engine + version. When any of these change, results computed under the old and new versions remain distinguishable from + each other. +

+
+
+ ); +} diff --git a/client/src/routes/SignalsPage.tsx b/client/src/routes/SignalsPage.tsx index e4d2cd4..96474a1 100644 --- a/client/src/routes/SignalsPage.tsx +++ b/client/src/routes/SignalsPage.tsx @@ -6,6 +6,7 @@ import { queryKeys } from '../lib/queryKeys'; import { fetchSignals } from '../features/signals/api'; import { SignalCard } from '../features/signals/SignalCard'; import { useMarkets } from '../features/markets/useMarkets'; +import { Disclosure } from '../components/Disclosure'; const PAGE_SIZE = 12; @@ -30,6 +31,8 @@ export function SignalsPage() {

+ + {signals.isLoading &&

Loading signals…

} {signals.isError &&

Could not load signals.

} diff --git a/docs/architecture/current-state.md b/docs/architecture/current-state.md index 9e164ff..960a6f4 100644 --- a/docs/architecture/current-state.md +++ b/docs/architecture/current-state.md @@ -122,7 +122,18 @@ PR #6), `db/schema.ts` (Drizzle schema, hardened FKs/indexes/enums, PR #8). `SettingsPage` (+ `nav.ts` for route/nav config). `AnalyticsPage` (PR for `feat/analytics-integrity`, migration step 15) and `SettingsPage`'s risk-limits form (PR #25) were the two client surfaces still outstanding as of earlier revisions of - this doc -- both now exist. + this doc -- both now exist. `MethodologyPage` (`DISCLOSURE-001`, issue #40) added as + a route not in the main sidebar nav -- reachable only via a `Disclosure` component's + "Learn more"/"Read the full methodology" link, matching the issue's "not one giant + warning box everywhere" scoping. +- `components/Disclosure.tsx` (`DISCLOSURE-001`, issue #40) — the single reusable + disclosure component (`variant="compact" | "detailed"`, `context="primary" | "signals" + | "backtesting" | "paper-fills"`), icon + text (never color alone). `compact` renders + once in the `AppShell` sidebar footer (visible on every authenticated page) and on + `ConnectScreen`; `detailed` is placed contextually on `SignalsPage` and in + `OrderTicket`'s confirmation step. `backtesting` copy exists but has nowhere to render + yet -- no backtest results UI exists in the client (`BACKTEST-001` scoped that out). + Copy source of truth: `docs/product/paper-trading-and-educational-scope.md`. - `features/{auth,execution,markets,positions,realtime,risk,settings,signals}/` — feature-scoped hooks and logic (the pattern the migration plan explicitly modeled on Replit's `features/trade`/`features/markets` shape, minus the duplication issues diff --git a/docs/product/paper-trading-and-educational-scope.md b/docs/product/paper-trading-and-educational-scope.md new file mode 100644 index 0000000..afe8569 --- /dev/null +++ b/docs/product/paper-trading-and-educational-scope.md @@ -0,0 +1,148 @@ +# Paper Trading & Educational Scope (DISCLOSURE-001, issue #40) + +This is the single source of truth for what LiquidAlpha is, what it is +not, and the exact copy used across the product's disclosure surfaces +(`client/src/components/Disclosure.tsx`) and its methodology page +(`client/src/routes/MethodologyPage.tsx`). If either of those drifts from +this document, this document wins -- update the code to match it, not the +other way around. + +## What this product is + +An educational paper-trading simulator for Hyperliquid perpetuals. Every +price is real (sourced live from Hyperliquid, with an explicitly-labeled +CoinGecko fallback -- see `docs/architecture/market-data.md`). Every trade, +fill, fee, funding charge, and backtest result is simulated. **No real +money, no real orders, no real exchange, ever** -- this is a structural +property of the codebase (see `hyperliquid-real.ts`: it fetches public +market data only and never initializes a signed execution client), not +just a policy. + +## What this product is not + +- Not financial advice. +- Not a signal service claiming any indicator combination predicts + future price movement. +- Not a broker, exchange, or custodian. +- Not a guarantee, projection, or promise of any trading outcome. + +## Banned phrases + +None of the following (or close paraphrases) may appear in user-facing +copy anywhere in the client: **guaranteed, safe profit, high-confidence +winner, best trade, cannot lose, proven return, buy now, sell now.** +Enforced by a repo-wide grep audit (`docs/product/paper-trading-and-educational-scope.md`'s +own banned-phrase list, checked in CI-adjacent review, not automated lint +today). + +Preferred vocabulary instead: +- "Signal strength" (a heuristic agreement score), never "confidence" or + "probability of winning" -- see `docs/product/signal-strength.md`. +- "Simulated fill" / "paper fill", never "executed" or "filled on + Hyperliquid." +- "Backtest result" / "historical simulation," never "proven" or + "verified profitable." +- Imperative trade language ("Buy now", "Sell now") is replaced with + descriptive/neutral phrasing ("Submit a LONG order", "This signal + suggests a SHORT setup") -- the product never tells a user what to do. + +## Disclosure component + +``. + +- **`compact`** -- a single line plus an icon (never color alone, per the + mission's accessibility requirement), used as a persistent, low-visual- + weight reminder. Appears in the app shell sidebar (visible on every + authenticated page) and on the guest/wallet sign-in screen. +- **`detailed`** -- a fuller explanation with its own bordered card, + placed near the specific surface it's contextualizing (the Signals page, + the order ticket) rather than duplicated as one giant warning block + repeated everywhere. + +### Copy: `primary` + +- Compact: "Paper trading only -- simulated fills, real market data, no + real money." +- Detailed: "LiquidAlpha is an educational paper-trading simulator. Prices + are sourced live from Hyperliquid; every trade, fill, and result is + simulated. Nothing here is financial advice, and no path in this + product can place a real order on any exchange." + +### Copy: `signals` + +- Compact: "Signal strength reflects indicator agreement, not a + probability of winning." +- Detailed: "Signals are generated from real technical indicators + (EMA, MACD, RSI, ADX, Fisher Transform, Keltner Channel), composed into + a versioned, explainable 0-100 Signal strength score. This score + measures how strongly the indicators agree with each other -- it is not + a calibrated probability, a win-rate estimate, or investment advice. + Past indicator agreement does not predict future price movement." + +### Copy: `backtesting` + +- Compact: "Backtest results are historical simulations, not a guarantee + of future performance." +- Detailed: "Backtests replay historical Hyperliquid candles through this + platform's real signal-generation logic, with documented, conservative + assumptions about entry timing, slippage, fees, and funding (see + `docs/product/backtesting-methodology.md`). A strategy's historical + simulated performance is not a guarantee, promise, or reliable predictor + of how it would perform going forward -- markets change, and this + engine cannot account for conditions it hasn't seen." + +### Copy: `paper-fills` + +- Compact: "Simulated fill -- not a real exchange execution." +- Detailed: "Every paper fill is priced from a real Hyperliquid reference + price, with documented simulated slippage and fees applied on top (see + `docs/architecture/paper-execution.md`). No real order is ever sent to + Hyperliquid or any other exchange." + +## Methodology page + +`client/src/routes/MethodologyPage.tsx`, linked from every `Disclosure` +component's "Learn more" link and from the app shell sidebar. Covers, in +order: + +1. **Data sources** -- Hyperliquid primary, CoinGecko explicitly-labeled + fallback; what "live" vs "degraded" vs "fallback" vs "unavailable" + mean (`docs/architecture/market-data.md`). +2. **Signal calculation** -- which indicators, the trend+momentum gate, + what `ruleAlignmentScore` and `RULE_VERSION` mean + (`technical-analysis.ts`). +3. **Signal strength meaning** -- the six weighted components, why it's + not a probability (`docs/product/signal-strength.md`). +4. **Backtesting assumptions** -- no-lookahead guarantee, entry/exit + assumptions, sample-adequacy tiers (`docs/product/backtesting-methodology.md`). +5. **Paper-fill assumptions** -- fees, slippage, funding, liquidation + estimate (`docs/architecture/paper-execution.md`). +6. **Limitations** -- perp-only (no spot), no reduce-only orders, no + cross-margin portfolio simulation, single flat maintenance-margin + assumption, single flat fee assumption. +7. **Data freshness behavior** -- the `live | degraded | fallback | unavailable` + modes and what a user should expect to see in each. +8. **Versioning** -- `RULE_VERSION` (signal engine), `SCORE_MODEL_VERSION` + (signal strength), `FILL_MODEL_VERSION` (paper fills), + `BACKTEST_ENGINE_VERSION` (backtesting) -- every number the product + shows is traceable to the exact model version that produced it. + +## Placement checklist (acceptance criteria) + +| Surface | Variant | Context | +|---|---|---| +| Guest onboarding / wallet sign-in (`ConnectScreen`) | detailed | primary | +| App shell sidebar (every authenticated page) | compact | primary | +| Signals page | detailed | signals | +| Order ticket (`OrderTicket`) | detailed | paper-fills | +| Positions, Analytics, Settings, Overview | compact | primary (via app shell; no per-page duplicate) | +| Backtesting results UI | detailed | backtesting | +| Exported reports | -- | + +**Backtesting results UI and exported reports do not exist as client +surfaces yet** (`BACKTEST-001` explicitly scoped its results UI out, per +its own Accessibility Review section; no export/report feature exists +anywhere in this codebase). The `backtesting` disclosure copy is written +and ready in `Disclosure.tsx` for whichever issue builds that UI to wire +in -- there is nowhere to place it today without inventing UI beyond this +issue's scope.