- Prereqs: Node 20+, pnpm 9+, Expo CLI.
- Install deps:
make setup - Configure API env: copy
services/api/.env.exampletoservices/api/.envand fill keys. - Run API:
make dev-api(listens on:4000). - Run Mobile (separate terminal):
make dev-mobilethen open in iOS/Android or web. - Useful:
make devruns both via Turbo;make test/make lint/make fmtplaceholders.
Networking tip
- Android emulator uses
10.0.2.2for host machine. The app auto-targetshttp://10.0.2.2:4000on Android andhttp://localhost:4000on iOS/web. If using a real device via LAN, set a tunnel or adjustBASE_URLinapps/mobile/src/api/client.ts.
This repository tracks the requirements for a mobile app where players plan a trade on a hidden future chart, reveal bars, and score P/L. The PRD below is adapted for a React Native + Expo stack with a Node/TypeScript backend, TradingView Lightweight Charts via WebView, and yfinance/CCXT data sources.
Blind Chart Trader — plan a trade on a hidden future chart, reveal bars, and score P/L.
Deliver a mobile app where players: 1. opens a game with chosen market universe (equities, indices, forex, crypto, or a mix), 2. view a historical chart slice (future hidden), 3. switch timeframes safely, 4. place entry, stop-loss, position size, and expiry, 5. reveal future bars, 6. get profit/loss (P/L) as the score, see the outcome chart, and immediately play the next round.
⸻
• Charting: TradingView Lightweight Charts (open-source renderer; we control the data feed & reveal) via React Native WebView.
• Data sources:
• Equities & Indices & (optionally) some FX: yfinance (Yahoo Finance) for OHLCV.
• Crypto (major coins): CCXT (exchange APIs, e.g., Binance, Kraken, Coinbase).
• Game fairness: No “time travel” leakage. We only send bars up to the reveal cursor.
• Score = P/L: The score for a round is the monetary P/L derived from user inputs (entry, stop, size, expiry). No extra multipliers in v1 (keep it transparent).
• Multi-timeframe (MTF) switching: Allowed without future leakage (see §4.3).
⸻
• Solo Player: Practices trade planning and risk control.
• Coach/Streamer (later): Runs rooms where everyone sees the same round (out of scope for v1, keep data model ready).
1. Start a game with chosen universe & TF.
2. Inspect context bars (future hidden).
3. Switch TFs (e.g., 5m ↔ 15m ↔ 1h) without revealing future aggregates.
4. Place entry, stop, position size, and expiry (time budget until the trade auto-exits).
5. Hit Reveal to simulate forward bars to TP/SL/Expiry.
6. Receive P/L, see outcome, save/share, Play Next.
⸻
• Player selects:
• Universe: Equities, Indices, Forex, Crypto, or Mixed (random across chosen classes).
• Timeframes available: e.g., 1m (crypto), 5m, 15m, 1h, 4h, 1D (configurable per asset class/data depth).
• Lookback bars (context): default 150 (configurable).
• Forward window (max future bars engine will simulate): default 300 (configurable).
• System randomizes symbol, start index/time, base timeframe (see §4), ensuring:
• Sufficient historical depth before and after start.
• Forward window fully exists in the past (no near-real-time bars).
• Liquidity/session sanity (skip illiquid gaps, out-of-session equities bars).
• Render only lookback bars at first; future hidden.
• Show OHLCV candles with volume (optional toggle).
• Disable panning beyond the right-edge “now”; clamp zoom to not skip beyond.
• Provide TF switcher UI (hotkeys & dropdown).
• Players can set:
• Side: long/short.
• Entry: price level (limit) or use “market at next bar open” (toggle).
• Stop-loss: price level.
• Position size: units (default), or notional; allow a simple “risk % of account” calculator (optional v1.1).
• Expiry: max bars until the trade closes at last price if neither SL/TP hit (default e.g. 100 bars).
• Take-profit (optional): If absent, P/L at SL or expiry only.
• Orders are anchored to price, not pixels. UX: draggable horizontal lines with numeric input.
• On Reveal:
• Engine advances bars from the canonical base series, checking:
• Entry fill logic (see §4.5).
• SL/TP hit logic with deterministic intrabar ordering.
• Expiry (trade exits at bar close when expiry counter hits zero).
• When trade closes:
• Compute P/L = (exit_price − entry_fill) × signed_size (fees/slippage optional v1.1).
• Show result with summary (see §3.5).
• Display:
• Final P/L (score), % return relative to notional or account (if risk calc enabled).
• Bars to outcome, RR snapshot if TP provided.
• Outcome chart with full revealed bars up to exit/expiry.
• Buttons: Play Next, Replay Round, Share Link (deep link with seed; replay is view-only).
• Daily/weekly aggregates by total P/L, # rounds, average risk, etc.
⸻
• Each round runs on one base TF:
• Crypto: 1m or 5m depending on data availability.
• Equities/Indices/FX: 5m/15m/1h/1D based on chosen TF menu and source depth.
• The base TF is the truth for simulation. All other TFs are views aggregated from base.
• yfinance:
• OHLCV download for equities/indices/FX (note FX availability varies).
• Intraday limits: 1m history ~30–60 days; 5m/15m longer; daily: years.
• Use adjusted prices for equities; normalize timezones to UTC.
• CCXT:
• Exchanges: Binance, Kraken, Coinbase (configurable).
• Use exchange-specific OHLCV endpoints with rate limiting/backoff.
• Normalize symbols and timezones to UTC.
• Caching:
• Pre-cache popular symbols & TFs.
• Store canonical OHLC arrays per symbol/TF/day in Postgres or object storage to avoid repeated API calls.
• Persist round slices by seed for perfect reproducibility.
• Let ratio = viewTF / baseTF.
• Only show completed aggregate bars:
• Visible aggregates = floor((revealed_base_bars) / ratio).
• Do not render the partially forming aggregate; optionally show a ghost placeholder (no values).
• If lower-than-base TF is requested, it must be backed by real data and aligned to base time; only display bars with timestamp ≤ current base “now”.
• Maintain reveal_cursor (index into base series).
• All TF views read from this cursor; right edge is consistent.
• Clamp pan/zoom so the user cannot move the right edge to expose unrevealed samples.
• Entry (limit): first bar where price touches (H/L intrabar) the level after Reveal starts.
• Market at next open: entry fills at next bar open after Reveal.
• Intrabar priority on a bar crossing both SL & TP:
• Order: Open → nearest level to open first (distance check) → then other level if still valid.
• Gaps:
• If price gaps through entry, fill at first tradable price (bar open).
• If gap skips SL/TP, assume worst-case fill at first tradable price beyond the level (conservative).
• Expiry:
• Countdown in bars. On expiry, exit at bar close (or bar mid if configured).
• Optional v1.1: Slippage & fees configs.
⸻
• Mobile App: React Native + Expo (TypeScript, expo-router) + TradingView Lightweight Charts via WebView; state via Zustand.
• Backend: Node/TypeScript service for OHLCV, seeding, simulation, and persistence.
• Data Layer: Postgres (users, rounds, seeds); Redis (rate limits, ephemeral rooms/cache).
• Data Fetchers: yfinance for equities/indices/FX; CCXT for crypto; schedule pre-caching jobs.
• Auth: Auth0 or Supabase Auth (mobile-friendly OAuth).
• Hosting: Expo EAS for mobile builds/updates; Fly.io/Render/GCP/AWS for backend; object storage for cached OHLC blobs.
• Round Service: symbol/TF randomization, seed creation, data slicing, rule validation.
• Sim Engine: bar-by-bar reveal, fill/hit computation, P/L math.
• Data Service: fetch/cache/normalize OHLCV, corporate actions adj., session filters.
• Leaderboard Service (v1.1): aggregations and ranks.
• Telemetry: event logs for UX funnels and auditing (see §9).
⸻
users(
id UUID PK,
handle TEXT UNIQUE,
email TEXT UNIQUE,
created_at TIMESTAMP
)
rounds(
id UUID PK,
user_id UUID FK users,
seed TEXT, -- deterministic replay key
asset_class TEXT, -- equities|indices|forex|crypto|mixed
symbol TEXT,
base_tf TEXT, -- e.g., "1m","5m","15m","1h","1d"
start_ts TIMESTAMP, -- first bar of lookback
lookback INT,
forward_max INT,
created_at TIMESTAMP
)
plans(
id UUID PK,
round_id UUID FK rounds,
side TEXT, -- long|short
entry_type TEXT, -- limit|market_next_open
entry_price NUMERIC, -- nullable if market_next_open
stop_price NUMERIC,
take_profit NUMERIC NULL,
size NUMERIC, -- positive number; sign implied by side
expiry_bars INT
)
executions(
id UUID PK,
round_id UUID FK rounds,
plan_id UUID FK plans,
entry_fill NUMERIC,
exit_price NUMERIC,
exit_reason TEXT, -- tp|sl|expiry|invalid
bars_elapsed INT,
pl NUMERIC,
rr NUMERIC NULL,
completed_at TIMESTAMP
)
ohlc_blobs(
id UUID PK,
symbol TEXT,
tf TEXT,
session_date DATE NULL,
data_json JSONB, -- compressed array [{time,open,high,low,close,volume}]
source TEXT, -- yfinance|ccxt:binance...
created_at TIMESTAMP
)
⸻
All responses return { ok: boolean, data?: any, error?: {code, message} }
Body
{
"universe": ["equities","indices","forex","crypto"],
"view_tfs": ["5m","15m","1h","1d"],
"lookback": 150,
"forward_max": 300
}
Response
{
"ok": true,
"data": {
"round_id": "UUID",
"seed": "string",
"asset_class": "crypto",
"symbol": "BTC/USDT",
"base_tf": "1m",
"lookback": 150,
"forward_max": 300,
"ohlcv_pre": [ { "time": 1685577600, "open":..., "high":..., "low":..., "close":..., "volume":... }, ... ],
"rules": { "fill": "open-then-nearest", "gap": "first-tradable", "expiry_exit": "close" }
}
}
Body
{
"round_id": "UUID",
"side": "long",
"entry_type": "limit",
"entry_price": 42350.0,
"stop_price": 42050.0,
"take_profit": 43150.0,
"size": 0.5,
"expiry_bars": 100
}
Response
{ "ok": true, "data": { "plan_id": "UUID" } }
Starts simulation for this plan, returns either a stream or a single result + optional steps.
Body
{ "round_id": "UUID", "plan_id": "UUID" }
Response (non-stream)
{
"ok": true,
"data": {
"revealed_bars": [ ],
"execution": {
"entry_fill": 42352.0,
"exit_price": 43150.0,
"exit_reason": "tp",
"bars_elapsed": 37,
"pl": 399.0,
"rr": 3.0
}
}
}
Returns replay info + bars to visualize final state.
{
"ok": true,
"data": {
"meta": { ... },
"plan": { ... },
"execution": { ... },
"ohlcv_full": [ ]
}
}
Return curated symbol list per asset class for the randomizer (to avoid illiquid instruments).
{
"ok": true,
"data": {
"equities": ["AAPL","MSFT","NVDA"],
"indices": ["^GSPC","^NDX","^DJI","^FTSE"],
"forex": ["EURUSD","GBPUSD","USDJPY","XAUUSD"],
"crypto": ["BTC/USDT","ETH/USDT","SOL/USDT"]
}
}
⸻
• Header: logo, Profile, Settings.
• Config panel (drawer or screen):
• Universe selector (multi-select).
• TF switcher (buttons: 1m/5m/15m/1h/4h/1D depending on asset).
• Lookback and Expiry (advanced).
• Chart area (Lightweight Charts in WebView):
• Candle + optional Volume.
• Price-level handles for Entry/Stop/TP (draggable; numeric inputs).
• Right-edge lock icon + tooltip “future hidden.”
• Trade controls:
• Side toggle (Long/Short).
• Entry type (Limit / Market next open).
• Inputs: Entry, Stop, TP (optional), Size, Expiry.
• CTA: Reveal (disabled until plan valid).
• Results modal/panel:
• Big P/L number, exit reason (TP/SL/Expiry), bars elapsed.
• RR snapshot if TP was set.
• Buttons: Play Next, Replay (read-only), Share.
• Validation: Side-aware constraints (for long: TP > Entry > SL; for short: TP < Entry < SL).
• Error states: Clear, inline errors on invalid prices/size/expiry.
• Keyboard/gesture shortcuts: TF hotkeys where available, reveal after valid plan.
• Loading/Empty: Skeleton chart states; friendly copy.
⸻
• Performance:
• Initial load < 2s for cached rounds; chart FPS 30+ on mid devices.
• Payloads: compress OHLC arrays (gzip/br).
• Reliability: Gracefully degrade if a data source rate-limits; fall back to cached blobs.
• Security:
• Auth via OAuth; HTTPS everywhere; input validation server-side.
• Anti-cheat: server is source of truth for outcome; client cannot override.
• Privacy:
• Store minimal PII; comply with GDPR (export/delete on request).
• Licensing:
• Lightweight Charts (MIT) OK for commercial.
• yfinance/CCXT terms respected; do not redistribute vendor data in bulk.
• Observability:
• Structured logs, metrics: rounds_started, rounds_completed, avg_P/L, avg_bars_to_outcome, errors by type.
• Client events for funnel analysis (config opened → plan valid → reveal → next).
⸻
• Maintain curated lists to avoid illiquid assets and broken sessions.
• For Mixed, pick class with uniform probability or weighted (configurable).
• Enforce:
• Lookback L_pre bars exist before t0.
• Forward L_fwd bars exist after t0.
• Exclude near-real-time windows (e.g., buffer ≥ 7 days from “today” for equities intraday).
• Respect exchange sessions for equities; skip pre/post if configured.
• seed = hash(symbol | base_tf | start_ts | L_pre | L_fwd | rule_version).
• Deterministic reproduction of the round across users & devices.
⸻
Group every r base bars:
open = first.open
high = max(highs)
low = min(lows)
close = last.close
vol = sum(volumes)
Only show full groups ≤ reveal_cursor. Never render the forming bar.
• Determine if entry is active:
• If market_next_open and position not opened: fill at current bar open, first bar after Reveal.
• If limit:
• If (low ≤ entry ≤ high) after position not opened → fill at entry (or open if gap through).
• If in position, check SL/TP on each new bar:
• If bar crosses both, hit the closest level to bar open first.
• Apply gap logic as per §4.5.
signed_size = size * (+1 for long, -1 for short)
P/L = (exit_price - entry_fill) * signed_size
(Optionally, scale by contract multiplier for indices/futures in v1.1)
⸻
• A1: Switching TFs never reveals any future bar (QA: try edge zooms & pan).
• A2: Given a fixed seed, two users produce identical outcomes with identical plans.
• A3: Entry/SL/TP edge cases (gap through, both-hit bar) match rule spec across 50 curated tests.
• A4: Expiry exits at bar close with correct bar count.
• A5: P/L matches manual spreadsheet calc for 20 sample rounds across asset classes.
• A6: “Play Next” produces a new seed; “Replay” loads same seed read-only.
• A7: Rate limits from sources don’t crash the app; cached data used instead.
⸻
• Unit: aggregation, fill/hit, expiry, P/L.
• Property tests: invariants under TF switches (no bar count mismatch; no future exposure).
• Integration: end-to-end with mock OHLC feeds; then with yfinance/CCXT sandboxes.
• Golden cases: JSON fixtures for rounds with known outcomes (gap up/down, wick-tap, tight stops).
• Cross-platform: iOS and Android; WebView chart interactions.
⸻
• v1.1:
• Leaderboards, shareable replays.
• Fees & slippage model.
• “Risk % of account” position sizing helper.
• Multi-TP ladder, partial exits.
• Rooms/head-to-head with same seed.
• v1.2:
• Basic indicators (MA/EMA/ATR) computed on base TF (legend shows TF).
• Coaching notes overlay & export.
⸻
• Base TF: True simulation timeframe; all logic runs here.
• Reveal Cursor: Index of last revealed base bar.
• Expiry: Max bars allowed in position; auto-exit at close when reached.
• P/L: Profit or loss in instrument units (or currency if sized as notional).
⸻
• Provide wireframes for: Config → Chart (MTF switcher, price handles) → Results modal.
• States: invalid plan, pending reveal, outcome TP/SL/Expiry, no-volume overlays (indices).
• Implement cursor-locked Lightweight Charts wrapper in WebView.
• Price-line components with drag + numeric input.
• Strict pan/zoom clamps; TF aggregation view.
• Call backend API for rounds/seeds.