Prompt-persona paper trading league for MOC — summon agent personas from prompts, compete in auto-provisioned weekly seasons, and replay every decision with full PnL attribution.
Many "AI trading" projects over-index on prediction claims and under-invest in explainability. Promptfolio was designed as an explicit simulation-first sandbox:
- no real-money execution,
- no hidden custody mechanics,
- no pseudo-guarantee framing.
It is a system for strategy behavior experimentation, not financial promises. Every trade carries a human-readable reason, every prompt change is versioned, and every season can be replayed tick by tick.
- Weekly seasons, self-healing — the current ISO-week season (e.g.
season_2026w07) is auto-created on every tick and on visits to the home, leaderboard, season, and replay pages; each desk starts with $1,000 paper cash (DEFAULT_STARTING_CASH_USDto override). - Deterministic strategy engine (no LLM yet) — prompts drive allocation through keyword profiles:
degen/all in/올인→ 90% MOC,monk/no trade/금욕→ 0%, anything else → 50/50. Trades only fire when drift exceeds 2% of equity, with hard rails: no leverage, no shorting. - Replay with real accounting — per-trade realized/unrealized PnL via weighted-average cost basis, plus an all-desk
/replayindex with URL-driven filters (sort, text, equity range, winners/underwater) and shareable view links. - Operator dashboard — the home page derives live telemetry: radar alerts, operator brief, priority queue (NOW/GO/WATCH), readiness checklist, shift handoff, pulse board (buy/sell pressure), market regime (NO FLOW/RISK-ON/RISK-OFF/WAIT-AND-SEE/MIXED TAPE), desk watchlist (WAKE UP/STALE/NO MEMO/HOT/WATCH), and a 15-minute feed-freshness budget.
- Keyboard-first navigation —
Alt+0–9/D/L/P/U/Rsection jumps,/fuzzy filter, vim-styleJ/K, pinboard (max 4), recent trail,?shortcut guide — persisted inlocalStorage, shareable via?jump=URLs. - Copyable briefs — one-click plain-text blocks (
PROMPTFOLIO BRIEF,SHIFT HANDOFF,OPERATOR RADAR, …) built for pasting into chat tools. - Bilingual UI (EN/KO) — cookie-persisted locale toggle, server-rendered; strategy keywords work in both languages.
- Prompt governance — prompt edits are limited to once per calendar day, with full version history.
- Live health surface —
GET /api/healthruns a real DB readiness probe (HTTP 503 +db:"down"if the schema is missing), plus a footer badge that polls it every 30s with age counter and manual refresh. - Hardened write path — every mutating endpoint is atomic (SQLite transactions), same-origin-guarded (cross-origin POSTs get
403), and per-IP rate-limited (429+Retry-After). Runs on Next.js 16 with a clean productionnpm audit.
| Route | What it shows |
|---|---|
/ |
Operator command deck: terminal log, next-action card, radar, briefs, quick jumps |
/agents |
Agent Lab — create personas (name, avatar emoji, prompt), search the roster |
/agents/[id] |
Agent detail — prompt editing (1/day lock) + prompt history |
/agents/[id]/replay |
Per-agent trade timeline with realized/unrealized PnL per row |
/leaderboard |
Equity ranking with leader gap, field spread, equity bands, underdog watch |
/replay |
All-desk replay index with power filters and shareable views |
/season |
Season HQ — season lifecycle, run ticks, market snapshot, recent tick history |
All pages are server-rendered on every request (force-dynamic) against local SQLite.
flowchart LR
Persona[Prompt persona] -->|keyword profile| Engine[Rule engine]
CG[CoinGecko MOC/USD] -->|live price| Tick[POST /api/tick]
Tick --> Engine
Engine -->|"drift > 2% → BUY/SELL, else HOLD"| Store[(SQLite ×6 tables)]
Store --> UI[Leaderboard · Replay · Terminal log]
POST /api/tickensures the weekly season exists, then fetches the live MOC/USD price from CoinGecko (simple/price, no synthetic fallback — feed failures surface as retryable errors with operator-friendly copy).- Each agent's prompt resolves to a target MOC allocation (90% / 0% / 50%).
- If the portfolio drifts more than 2% of equity from target, the engine rebalances — BUY capped at available cash, SELL capped at held units — and records the trade with a reason string.
- The terminal log renders each trade with a deterministic meme one-liner (FNV-1a-hashed, so replays are stable).
| Endpoint | Method | Purpose |
|---|---|---|
/api/health |
GET | Readiness + liveness (ok, db, version, uptimeSec, …); 200 when the DB is reachable, 503 when it is not; Cache-Control: no-store |
/api/agents |
POST | Create agent (name ≤ 40, avatar ≤ 8, prompt ≤ 2,000 chars, zod-validated) |
/api/agents/[id]/update-prompt |
POST | Update prompt — rejected if already edited today |
/api/season |
POST | Create season (starting cash $1–$1,000,000) |
/api/tick |
POST | Run one simulation tick; JSON with x-pf-ajax: 1 header, otherwise redirects to /leaderboard |
/api/locale |
POST | Persist en | ko in a 1-year pf_locale cookie |
Form endpoints use POST-redirect-GET (303 See Other); validation errors return 400 with flattened zod issues. Every POST is guarded: cross-origin requests are rejected with 403, and each is per-IP rate-limited (429 + Retry-After) — budgets per minute: agents 10, season 6, tick 30, update-prompt 10, locale 30.
npm install
cp .env.example .env.local
npm run db:init # create the SQLite schema (idempotent, WAL mode)
npm run devOpen http://localhost:6200 (dev and production both bind port 6200).
| Variable | Default | Purpose |
|---|---|---|
DB_PATH |
.data/promptfolio.sqlite |
SQLite file location (directory is git-ignored) |
COINGECKO_BASE_URL |
https://api.coingecko.com/api/v3 |
Price feed base URL |
COINGECKO_COIN_ID |
mossland |
CoinGecko coin id for the feed |
DEFAULT_STARTING_CASH_USD |
1000 |
Starting paper cash for auto-created weekly seasons |
WRITE_RATE_LIMIT |
30 |
Default per-IP write budget per window (per route can be stricter) |
WRITE_RATE_WINDOW_MS |
60000 |
Rate-limit window length in ms |
OPERATIONS_BASE_URL |
https://pf.moss.land |
Target of npm run ops:check |
PROMPTFOLIO_STALE_HOURS |
168 |
Repo-staleness threshold for ops checks (warn-only unless PROMPTFOLIO_STRICT_STALE_FAIL=1) |
OPENAI_API_KEY / ANTHROPIC_API_KEY / LOCAL_LLM_ENDPOINT are reserved placeholders for the future LLM strategy engine and are currently unused.
| Command | What it does |
|---|---|
npm run dev |
Dev server on port 6200 |
npm run build / npm start |
Production build / serve |
npm test |
92 unit tests via Node's built-in node:test runner (no Jest/Vitest) |
npm run typecheck |
tsc --noEmit — strict type check across app + tests |
npm run lint |
eslint . (ESLint 9 flat config, eslint-config-next core-web-vitals) |
npm run db:init |
Create/upgrade the SQLite schema — safe to re-run |
npm run ops:check |
Probe the deployed site (/, /api/health, /season) with retries + repo-staleness check, emitting a JSON summary |
scripts/start-with-db.sh is the deployment entrypoint (PM2): filesystem diagnostics → db:init → next start.
CI (.github/workflows/ci.yml) runs typecheck → lint → test → build on every push and PR to main.
Six SQLite tables (better-sqlite3, WAL): agents, seasons, portfolios (PK season_id + agent_id), ticks, trades (BUY|SELL|HOLD + reason), and prompt_history (backs the daily edit lock and version trail).
- Next.js 16 (App Router, Turbopack) + React 18, TypeScript strict
- better-sqlite3 local persistence, zod validation
- CoinGecko price feed (simulation context)
- Node built-in test runner; ESLint 9 flat config; no external test framework
- PRD — product goals, shipped scope, next features
- Architecture — runtime shape, data model, request flow
- Roadmap — version-by-version progress + deferred future plan (한국어)
- Security — current posture, write-path protections, and known limitations
This project is a paper trading game and strategy sandbox. No real funds, no custody, no execution. Nothing here is financial advice.
No open-source license has been declared yet (package.json is private).
All rights reserved until a license file is added.