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