dexterm-cli 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/agents/src/index.js +2 -0
- package/dist/packages/agents/src/schema.js +22 -1
- package/dist/packages/agents/src/spectator.js +54 -0
- package/dist/packages/agents/src/story.js +532 -0
- package/dist/packages/agents/src/tiers.js +28 -2
- package/dist/packages/agents/src/tools.js +31 -2
- package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
- package/dist/packages/autotrade/assets/profiles/moonbag.json +46 -0
- package/dist/packages/autotrade/assets/profiles/runner.json +88 -0
- package/dist/packages/autotrade/assets/profiles/scalp.json +88 -0
- package/dist/packages/autotrade/assets/profiles/swing.json +88 -0
- package/dist/packages/autotrade/assets/strategies/moonbag.json +101 -0
- package/dist/packages/autotrade/assets/strategies/runner.json +101 -0
- package/dist/packages/autotrade/assets/strategies/sample-dip-ladder.json +188 -0
- package/dist/packages/autotrade/assets/strategies/scalp.json +98 -0
- package/dist/packages/autotrade/assets/strategies/swing.json +97 -0
- package/dist/packages/autotrade/assets/strategies/yolo.json +157 -0
- package/dist/packages/autotrade/assets/tapes/yolo-rehearsal.json +1 -0
- package/dist/packages/autotrade/src/account.js +74 -0
- package/dist/packages/autotrade/src/backtest.js +357 -0
- package/dist/packages/autotrade/src/basis-text.js +32 -0
- package/dist/packages/autotrade/src/chat/approvals.js +484 -0
- package/dist/packages/autotrade/src/chat/audit.js +140 -0
- package/dist/packages/autotrade/src/chat/config.js +93 -0
- package/dist/packages/autotrade/src/chat/connector.js +24 -0
- package/dist/packages/autotrade/src/chat/control.js +103 -0
- package/dist/packages/autotrade/src/chat/fake.js +100 -0
- package/dist/packages/autotrade/src/chat/format.js +71 -0
- package/dist/packages/autotrade/src/chat/hub.js +187 -0
- package/dist/packages/autotrade/src/chat/index.js +18 -0
- package/dist/packages/autotrade/src/chat/notify.js +292 -0
- package/dist/packages/autotrade/src/chat/pairing.js +46 -0
- package/dist/packages/autotrade/src/chat/redact.js +48 -0
- package/dist/packages/autotrade/src/chat/secrets.js +99 -0
- package/dist/packages/autotrade/src/chat/service.js +248 -0
- package/dist/packages/autotrade/src/chat/telegram.js +290 -0
- package/dist/packages/autotrade/src/desk-executor.js +188 -0
- package/dist/packages/autotrade/src/engine.js +387 -0
- package/dist/packages/autotrade/src/execution-port.js +97 -0
- package/dist/packages/autotrade/src/fleet-config.js +169 -0
- package/dist/packages/autotrade/src/fleet-control.js +276 -0
- package/dist/packages/autotrade/src/fleet.js +639 -0
- package/dist/packages/autotrade/src/index.js +53 -0
- package/dist/packages/autotrade/src/ledger.js +108 -0
- package/dist/packages/autotrade/src/live-source.js +182 -0
- package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
- package/dist/packages/autotrade/src/monitor.js +190 -0
- package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
- package/dist/packages/autotrade/src/paper-accounts.js +72 -0
- package/dist/packages/autotrade/src/replay.js +198 -0
- package/dist/packages/autotrade/src/runner-config.js +99 -0
- package/dist/packages/autotrade/src/runner.js +872 -0
- package/dist/packages/autotrade/src/safety.js +243 -0
- package/dist/packages/autotrade/src/session.js +269 -0
- package/dist/packages/autotrade/src/sessions.js +503 -0
- package/dist/packages/autotrade/src/store.js +250 -0
- package/dist/packages/autotrade/src/strategy.js +256 -0
- package/dist/packages/autotrade/src/trade-ledger.js +241 -0
- package/dist/packages/autotrade/src/yolo.js +140 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
- package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +1 -1
- package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +15 -8
- package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
- package/dist/packages/cli/src/agent-cmd.js +6 -4
- package/dist/packages/cli/src/agent-spectator.js +17 -1
- package/dist/packages/cli/src/ai-cmd.js +412 -0
- package/dist/packages/cli/src/analyst/backend.js +105 -15
- package/dist/packages/cli/src/analyst/codex.js +14 -9
- package/dist/packages/cli/src/analyst/larp-gauge.js +60 -0
- package/dist/packages/cli/src/analyst/ollama.js +263 -0
- package/dist/packages/cli/src/analyst/session.js +17 -3
- package/dist/packages/cli/src/app-help-gen.js +51 -15
- package/dist/packages/cli/src/autotrade-cmd/audit.js +57 -0
- package/dist/packages/cli/src/autotrade-cmd/backtest.js +199 -0
- package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
- package/dist/packages/cli/src/autotrade-cmd/fleet.js +397 -0
- package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
- package/dist/packages/cli/src/autotrade-cmd/run.js +413 -0
- package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
- package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
- package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
- package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
- package/dist/packages/cli/src/autotrade-cmd.js +168 -0
- package/dist/packages/cli/src/backtest-cmd.js +13 -2
- package/dist/packages/cli/src/display.js +208 -23
- package/dist/packages/cli/src/doctor.js +62 -3
- package/dist/packages/cli/src/export.js +10 -2
- package/dist/packages/cli/src/index.js +1 -1
- package/dist/packages/cli/src/ledger-cmd.js +288 -0
- package/dist/packages/cli/src/live/chart-fills.js +46 -0
- package/dist/packages/cli/src/live/dev-history.js +31 -15
- package/dist/packages/cli/src/live/index.js +28 -90
- package/dist/packages/cli/src/live/screens/newpairs-live.js +1 -1
- package/dist/packages/cli/src/live/screens/token-live.js +48 -15
- package/dist/packages/cli/src/live/screens/watchlist-live.js +1 -1
- package/dist/packages/cli/src/live/trade.js +30 -453
- package/dist/packages/cli/src/live/watchlist.js +1 -1
- package/dist/packages/cli/src/main.js +290 -199
- package/dist/packages/cli/src/mcp-cmd.js +7 -1
- package/dist/packages/cli/src/mcp-follow.js +103 -6
- package/dist/packages/cli/src/plan-cmd.js +15 -5
- package/dist/packages/cli/src/pnl-app.js +31 -21
- package/dist/packages/cli/src/pnl-card.js +158 -21
- package/dist/packages/cli/src/popup.js +38 -6
- package/dist/packages/cli/src/record-cmd.js +141 -20
- package/dist/packages/cli/src/scan/ui.js +7 -1
- package/dist/packages/cli/src/screens/agent-hud.js +773 -0
- package/dist/packages/cli/src/screens/analyst-session.js +11 -4
- package/dist/packages/cli/src/screens/analyst.js +3 -2
- package/dist/packages/cli/src/screens/autotrade-keys.js +169 -0
- package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
- package/dist/packages/cli/src/screens/autotrade-screen.js +725 -0
- package/dist/packages/cli/src/screens/autotrade-ui.js +640 -0
- package/dist/packages/cli/src/screens/autotrade-worker.js +89 -0
- package/dist/packages/cli/src/screens/brain.js +634 -0
- package/dist/packages/cli/src/screens/newpairs.js +1 -1
- package/dist/packages/cli/src/screens/overlay.js +5 -2
- package/dist/packages/cli/src/screens/plans.js +5 -17
- package/dist/packages/cli/src/screens/pnl-chip.js +144 -0
- package/dist/packages/cli/src/screens/positions.js +6 -28
- package/dist/packages/cli/src/screens/screener.js +1 -0
- package/dist/packages/cli/src/screens/ticket.js +38 -205
- package/dist/packages/cli/src/screens/token.js +18 -9
- package/dist/packages/cli/src/screens/trending.js +1 -1
- package/dist/packages/cli/src/state.js +88 -18
- package/dist/packages/cli/src/telegram-cmd.js +451 -0
- package/dist/packages/cli/src/trade.js +3 -128
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/cli/src/wallet-cmd.js +39 -340
- package/dist/packages/cli/src/wallet-command.js +8 -11
- package/dist/packages/cli/src/widgets.js +0 -12
- package/dist/packages/custom/src/config.js +20 -2
- package/dist/packages/custom/src/schema.js +41 -10
- package/dist/packages/custom/src/workspace.js +3 -3
- package/dist/packages/data/src/basis.js +191 -0
- package/dist/packages/data/src/detail.js +53 -2
- package/dist/packages/data/src/index.js +2 -14
- package/dist/packages/data/src/larp/score.js +220 -0
- package/dist/packages/data/src/larp/x-account.js +316 -0
- package/dist/packages/data/src/store.js +14 -5
- package/dist/packages/data/src/trade/agent/paper-desk.js +300 -23
- package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
- package/dist/packages/data/src/trade/bridge.js +4 -102
- package/dist/packages/data/src/trade/ledger/export.js +101 -0
- package/dist/packages/data/src/trade/ledger/file.js +135 -0
- package/dist/packages/data/src/trade/ledger/fold.js +191 -0
- package/dist/packages/data/src/trade/ledger/index.js +9 -0
- package/dist/packages/data/src/trade/ledger/schema.js +83 -0
- package/dist/packages/data/src/trade/ledger/sources.js +307 -0
- package/dist/packages/data/src/trade/ledger/stats.js +123 -0
- package/dist/packages/data/src/trade/ledger/tags.js +116 -0
- package/dist/packages/data/src/trade/ledger/view.js +51 -0
- package/dist/packages/data/src/trade/plans/engine.js +7 -0
- package/dist/packages/data/src/trade/plans/index.js +0 -1
- package/dist/packages/data/src/trade/plans/mandate.js +28 -15
- package/dist/packages/data/src/trade/plans/service.js +4 -28
- package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
- package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/dev-history.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/larp-check.md +5 -1
- package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
- package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
- package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
- package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +217 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-token.md +6 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +2 -1
- package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
- package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +7 -3
- package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +49 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +9 -7
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-larp-score.md +60 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +0 -2
- package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +4 -1
- package/dist/packages/mcp/assets/kb/docs/docs-larp.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +4 -13
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +40 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-dev-history.md +10 -7
- package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-agent-hud-spectator-v2.md +58 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +46 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
- package/dist/packages/mcp/assets/kb/entries/autotrade.md +38 -0
- package/dist/packages/mcp/src/app-help.js +1 -1
- package/dist/packages/mcp/src/autotrade-tools.js +944 -0
- package/dist/packages/mcp/src/backtest-requests.js +71 -0
- package/dist/packages/mcp/src/engine/config.js +159 -0
- package/dist/packages/mcp/src/engine/index.js +10 -0
- package/dist/packages/mcp/src/engine/loop.js +101 -0
- package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
- package/dist/packages/mcp/src/engine/ollama.js +258 -0
- package/dist/packages/mcp/src/engine/probe.js +110 -0
- package/dist/packages/mcp/src/engine/reason.js +35 -0
- package/dist/packages/mcp/src/engine/restrictions.js +111 -0
- package/dist/packages/mcp/src/index.js +7 -1
- package/dist/packages/mcp/src/ledger-tools.js +346 -0
- package/dist/packages/mcp/src/paper-agent.js +5 -2
- package/dist/packages/mcp/src/server.js +285 -36
- package/dist/packages/mcp/src/spectator-sink.js +85 -0
- package/dist/packages/packs/src/backtest.js +3 -1
- package/dist/packages/packs/src/index.js +1 -0
- package/dist/packages/packs/src/larp-score-backtest.js +119 -0
- package/dist/packages/popup/src/pages/chart-lib.js +169 -35
- package/dist/packages/popup/src/pages/chart.css +18 -0
- package/dist/packages/popup/src/pages/chart.html +6 -1
- package/dist/packages/popup/src/pages/chart.js +136 -10
- package/dist/packages/popup/src/pages/spectator.css +45 -0
- package/dist/packages/popup/src/pages/spectator.html +1 -0
- package/dist/packages/popup/src/pages/spectator.js +69 -0
- package/dist/packages/popup/src/protocol.js +4 -0
- package/dist/packages/popup/src/server.js +9 -1
- package/dist/packages/recorder/src/dev-history.js +7 -3
- package/dist/packages/recorder/src/index.js +2 -0
- package/dist/packages/recorder/src/recorder.js +63 -6
- package/dist/packages/recorder/src/retention.js +100 -0
- package/dist/packages/recorder/src/schema.js +34 -1
- package/dist/packages/recorder/src/ticks.js +237 -0
- package/dist/packages/tui/src/canvas.js +7 -0
- package/dist/packages/tui/src/fmt.js +53 -0
- package/dist/packages/tui/src/index.js +1 -0
- package/dist/packages/tui/src/keymap.js +25 -1
- package/dist/packages/tui/src/markers.js +219 -0
- package/dist/packages/tui/src/widgets.js +158 -6
- package/package.json +2 -1
- package/dist/packages/data/src/trade/caps.js +0 -323
- package/dist/packages/data/src/trade/executor.js +0 -933
- package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
- package/dist/packages/data/src/trade/rpc-exec.js +0 -229
- package/dist/packages/data/src/trade/signer/client.js +0 -197
- package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
- package/dist/packages/data/src/trade/signer/host.js +0 -97
- package/dist/packages/data/src/trade/signer/main.js +0 -121
- package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
- package/dist/packages/data/src/trade/signer/protocol.js +0 -92
- package/dist/packages/data/src/trade/signer/service.js +0 -400
- package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
- package/dist/packages/data/src/trade/sol-usd.js +0 -86
- package/dist/packages/data/src/trade/swap-build.js +0 -108
- package/dist/packages/data/src/trade/token-delta.js +0 -201
- package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
- package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
- package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
|
@@ -302,7 +302,13 @@ const USAGE = `dexterm mcp [<command>]
|
|
|
302
302
|
--print only print the snippet, never write
|
|
303
303
|
--node emit a "node <path> mcp" command (when dexterm is not on PATH)
|
|
304
304
|
--yes write without the confirmation prompt
|
|
305
|
-
doctor spawn the server, do the MCP handshake, and list the tools/prompts/resources
|
|
305
|
+
doctor spawn the server, do the MCP handshake, and list the tools/prompts/resources
|
|
306
|
+
|
|
307
|
+
for agent authors: \`dexterm mcp doctor\` lists every tool with its description. Reads (market data, the trade
|
|
308
|
+
ledger: ledger_trades / positions / summary / equity / compare, candidate_dossier, outcomes_lookup, trading_status,
|
|
309
|
+
telegram_status) never change anything; strategy_propose_* and backtest_request only file a proposal the user applies
|
|
310
|
+
or runs; the paper_* tools trade the SIMULATED wallet and only inside a mandate the user armed. No tool can sign, send,
|
|
311
|
+
enable live trading or raise a cap. Docs: https://dexterm.xyz/docs/agents/`;
|
|
306
312
|
/** `dexterm mcp …`. With no subcommand (or an arg the server consumes, e.g. --replay) it runs the server. */
|
|
307
313
|
export async function runMcp(rest, io) {
|
|
308
314
|
const sub = rest[0];
|
|
@@ -14,11 +14,52 @@
|
|
|
14
14
|
// kill" are local follow controls: pause/take-over stop the terminal mirroring the agent (the external agent
|
|
15
15
|
// keeps running — we simply stop moving the screen), and kill removes the rendezvous so the server stops
|
|
16
16
|
// emitting. Any user keypress during an agent move takes control, so the human and the agent never fight.
|
|
17
|
-
import { writeFileSync, readFileSync, rmSync, mkdirSync, statSync } from 'node:fs';
|
|
17
|
+
import { writeFileSync, readFileSync, rmSync, mkdirSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
18
18
|
import { dirname } from 'node:path';
|
|
19
19
|
import { randomBytes } from 'node:crypto';
|
|
20
|
-
import { SpectatorHub, FollowController, SpectatorServer, } from '../../agents/src/index.js';
|
|
20
|
+
import { SpectatorHub, FollowController, SpectatorServer, AgentStory, sanitizeDetail, } from '../../agents/src/index.js';
|
|
21
21
|
import { rendezvousPath, writeFollowScreen, clearFollowScreen } from '../../mcp/src/index.js';
|
|
22
|
+
/**
|
|
23
|
+
* task-1466: the agent's own words in one JSONL line of a thoughts feed. Understands Claude Code stream-json
|
|
24
|
+
* (`assistant` messages: text and thinking blocks), the capture harness's brain feed (`{tEpoch, ev}` wrapping one),
|
|
25
|
+
* and plain `{"thought": "…"}` / `{"text": "…"}` lines. Anything else yields nothing. Text only; never acted on.
|
|
26
|
+
*/
|
|
27
|
+
export function thoughtsOf(line) {
|
|
28
|
+
const s = line.trim();
|
|
29
|
+
if (!s || s[0] !== '{' || s.length > 200_000)
|
|
30
|
+
return [];
|
|
31
|
+
let o;
|
|
32
|
+
try {
|
|
33
|
+
o = JSON.parse(s);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
let t = typeof o.tEpoch === 'number' && Number.isFinite(o.tEpoch) ? o.tEpoch : null;
|
|
39
|
+
if (o.ev && typeof o.ev === 'object')
|
|
40
|
+
o = o.ev;
|
|
41
|
+
if (t === null && typeof o.t === 'number' && Number.isFinite(o.t) && o.t > 1e12)
|
|
42
|
+
t = o.t;
|
|
43
|
+
// task-1473: a demo / stand-in feed marks its scripted lines `"demo": true` (the brain view tags them "demo text")
|
|
44
|
+
if (typeof o.thought === 'string')
|
|
45
|
+
return [{ text: o.thought, t, ...(o.demo === true ? { demo: true } : {}) }];
|
|
46
|
+
if (o.type === 'assistant' && o.message && typeof o.message === 'object') {
|
|
47
|
+
const content = o.message.content;
|
|
48
|
+
if (!Array.isArray(content))
|
|
49
|
+
return [];
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const b of content) {
|
|
52
|
+
if (b?.type === 'text' && typeof b.text === 'string')
|
|
53
|
+
out.push({ text: b.text, t });
|
|
54
|
+
else if (b?.type === 'thinking' && typeof b.thinking === 'string' && b.thinking.trim())
|
|
55
|
+
out.push({ text: b.thinking, t });
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
if (typeof o.text === 'string' && !o.type)
|
|
60
|
+
return [{ text: o.text, t }];
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
22
63
|
function navLabel(agent, nav) {
|
|
23
64
|
const coin = nav.mint ? ` ${nav.mint.length > 10 ? nav.mint.slice(0, 4) + '…' + nav.mint.slice(-4) : nav.mint}` : '';
|
|
24
65
|
if (nav.screen === 'token')
|
|
@@ -45,6 +86,8 @@ function navLabel(agent, nav) {
|
|
|
45
86
|
export class McpFollowSession {
|
|
46
87
|
hub;
|
|
47
88
|
follow;
|
|
89
|
+
/** task-1466: the story the spectator v2 HUD draws (phase rail, timeline, thoughts, PAPER trade arc). */
|
|
90
|
+
story;
|
|
48
91
|
opts;
|
|
49
92
|
rvPath;
|
|
50
93
|
channelPath;
|
|
@@ -52,6 +95,7 @@ export class McpFollowSession {
|
|
|
52
95
|
agentLabel;
|
|
53
96
|
currentAgent;
|
|
54
97
|
offset = 0;
|
|
98
|
+
thoughtsOffset = 0;
|
|
55
99
|
timer = null;
|
|
56
100
|
stream = null;
|
|
57
101
|
armed = false;
|
|
@@ -71,7 +115,8 @@ export class McpFollowSession {
|
|
|
71
115
|
kickAgent: () => { this.disarm(); return 1; },
|
|
72
116
|
};
|
|
73
117
|
this.follow = new FollowController({ controls, activityCap: Math.max(1, opts.activityCap ?? 200) });
|
|
74
|
-
this.
|
|
118
|
+
this.story = new AgentStory(this.agentLabel);
|
|
119
|
+
this.hub.on((ev) => { this.follow.apply(ev); this.story.apply(ev); });
|
|
75
120
|
}
|
|
76
121
|
/** Arm the rendezvous (so a `dexterm mcp` server starts emitting) and begin tailing. Idempotent. */
|
|
77
122
|
async start() {
|
|
@@ -133,6 +178,51 @@ export class McpFollowSession {
|
|
|
133
178
|
}
|
|
134
179
|
/** Read and fold any records appended since the last pump. Public so tests can drive it without timers. */
|
|
135
180
|
pump() {
|
|
181
|
+
this.pumpThoughts();
|
|
182
|
+
this.pumpChannel();
|
|
183
|
+
}
|
|
184
|
+
/** task-1466: new lines of the agent's own words → the story's thought ribbon (bounded: ≤ 256 KB per pump). */
|
|
185
|
+
pumpThoughts() {
|
|
186
|
+
const file = this.opts.thoughtsFile;
|
|
187
|
+
if (!file)
|
|
188
|
+
return;
|
|
189
|
+
let text;
|
|
190
|
+
try {
|
|
191
|
+
const st = statSync(file);
|
|
192
|
+
if (st.size < this.thoughtsOffset)
|
|
193
|
+
this.thoughtsOffset = 0;
|
|
194
|
+
if (st.size === this.thoughtsOffset)
|
|
195
|
+
return;
|
|
196
|
+
if (st.size - this.thoughtsOffset > 256 * 1024)
|
|
197
|
+
this.thoughtsOffset = st.size - 256 * 1024; // skip a backlog, keep the tail
|
|
198
|
+
const fd = openSync(file, 'r');
|
|
199
|
+
try {
|
|
200
|
+
const buf = Buffer.alloc(st.size - this.thoughtsOffset);
|
|
201
|
+
readSync(fd, buf, 0, buf.length, this.thoughtsOffset);
|
|
202
|
+
text = buf.toString('utf8');
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
closeSync(fd);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const nl = text.lastIndexOf('\n');
|
|
212
|
+
if (nl < 0)
|
|
213
|
+
return;
|
|
214
|
+
this.thoughtsOffset += Buffer.byteLength(text.slice(0, nl + 1), 'utf8');
|
|
215
|
+
const now = (this.opts.now ?? Date.now)();
|
|
216
|
+
for (const line of text.slice(0, nl).split('\n')) {
|
|
217
|
+
for (const th of thoughtsOf(line))
|
|
218
|
+
this.story.thought(th.text, th.t ?? now, 'agent', th.demo === true);
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
this.opts.onEvent?.();
|
|
222
|
+
}
|
|
223
|
+
catch { /* ignore */ }
|
|
224
|
+
}
|
|
225
|
+
pumpChannel() {
|
|
136
226
|
let text;
|
|
137
227
|
try {
|
|
138
228
|
const st = statSync(this.channelPath);
|
|
@@ -184,6 +274,7 @@ export class McpFollowSession {
|
|
|
184
274
|
ms: rec.ms ?? null,
|
|
185
275
|
dataAgeSec: rec.dataAgeSec ?? null,
|
|
186
276
|
nav: rec.nav ?? null,
|
|
277
|
+
detail: sanitizeDetail(rec.detail),
|
|
187
278
|
ok: rec.ok ?? true,
|
|
188
279
|
t: typeof rec.t === 'number' ? rec.t : undefined,
|
|
189
280
|
});
|
|
@@ -225,10 +316,14 @@ export class McpFollowSession {
|
|
|
225
316
|
takeOver() { const l = this.follow.takeOver(this.currentAgent); this.paused = false; this.control('takeover'); this.opts.onStatus?.(l); return l; }
|
|
226
317
|
resume() { const l = this.follow.resume(this.currentAgent); this.paused = false; this.control('running'); this.opts.onStatus?.(l); return l; }
|
|
227
318
|
kill() { const l = this.follow.kill(this.currentAgent); this.paused = false; this.control('stopped'); this.opts.onStatus?.(l); return l; }
|
|
228
|
-
control(state) {
|
|
229
|
-
this.opts.
|
|
319
|
+
control(state) {
|
|
320
|
+
const now = (this.opts.now ?? Date.now)();
|
|
321
|
+
this.story.setControl(state === 'stopped' ? 'killed' : state, now);
|
|
322
|
+
try {
|
|
323
|
+
this.opts.onControl?.(state);
|
|
324
|
+
}
|
|
325
|
+
catch { /* never breaks the key loop */ }
|
|
230
326
|
}
|
|
231
|
-
catch { /* never breaks the key loop */ } }
|
|
232
327
|
/** Any physical keypress while the agent is driving: the human takes over (no fight over the screen). */
|
|
233
328
|
userKey() { if (this.follow.controlState === 'agent')
|
|
234
329
|
this.takeOver(); }
|
|
@@ -237,6 +332,8 @@ export class McpFollowSession {
|
|
|
237
332
|
get screenModel() { return this.follow.screenModel; }
|
|
238
333
|
get streamPort() { return this.stream?.port ?? 0; }
|
|
239
334
|
view() { return this.follow.view(); }
|
|
335
|
+
/** task-1466: the story at `now` for the HUD. */
|
|
336
|
+
storyView(now = (this.opts.now ?? Date.now)()) { return this.story.view(now); }
|
|
240
337
|
/** Tear down: stop the timer, remove the rendezvous, close the stream. Safe to call more than once. */
|
|
241
338
|
async stop() {
|
|
242
339
|
if (this.timer) {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// dexterm plan journal [--verify] [--export]
|
|
10
10
|
// dexterm mandate draft [--total 100] [--per-trade 20] [--per-coin 40] [--daily-loss 50]
|
|
11
11
|
// [--max-tier keypress|ptt-held|hands-free] [--ttl-hours 4]
|
|
12
|
+
// [--max-open 20] [--max-impact 3] [--paper]
|
|
12
13
|
// dexterm mandate list [--json] | arm <id> | revoke <id>
|
|
13
14
|
//
|
|
14
15
|
// Arming is the only gate: `arm` reads ONE real keystroke in raw mode (ttyKeypress) and refuses a pipe,
|
|
@@ -44,7 +45,10 @@ const PLAN_USAGE = `dexterm plan <command>
|
|
|
44
45
|
journal [--verify] [--export]`;
|
|
45
46
|
const MANDATE_USAGE = `dexterm mandate <command>
|
|
46
47
|
draft [--total 100] [--per-trade 20] [--per-coin 40] [--daily-loss 50]
|
|
47
|
-
[--max-tier keypress|ptt-held|hands-free] [--ttl-hours 4] [--
|
|
48
|
+
[--max-tier keypress|ptt-held|hands-free] [--ttl-hours 4] [--max-open 20] [--max-impact 3]
|
|
49
|
+
[--paper] [--json]
|
|
50
|
+
(--paper: a PAPER-ONLY mandate for the agent paper desk, with its own
|
|
51
|
+
paper ceilings; no plan, copy trade or signer can ever use it)
|
|
48
52
|
list [--json]
|
|
49
53
|
arm <id> (needs a physical keypress on a TTY; a pipe/agent is refused)
|
|
50
54
|
revoke <id>`;
|
|
@@ -203,16 +207,20 @@ export async function runMandate(rest, io) {
|
|
|
203
207
|
const num = (n) => (flag(rest, n) !== undefined ? parseAmount(flag(rest, n)) : undefined);
|
|
204
208
|
const maxTier = flag(rest, '--max-tier');
|
|
205
209
|
const ttlHours = flag(rest, '--ttl-hours') ? Number(flag(rest, '--ttl-hours')) : undefined;
|
|
210
|
+
const plain = (n) => (flag(rest, n) !== undefined ? Number(flag(rest, n)) : undefined);
|
|
206
211
|
const m = svc.draftMandate({
|
|
207
212
|
totalUsd: num('--total'), perTradeUsd: num('--per-trade'), perCoinUsd: num('--per-coin'),
|
|
208
213
|
dailyLossUsd: num('--daily-loss'), maxTier, ttlMs: ttlHours ? ttlHours * 3600_000 : undefined,
|
|
214
|
+
maxOpenPositions: plain('--max-open'), maxPriceImpactPct: plain('--max-impact'),
|
|
215
|
+
...(has(rest, '--paper') ? { paperOnly: true } : {}),
|
|
209
216
|
});
|
|
210
217
|
if (has(rest, '--json')) {
|
|
211
|
-
out(JSON.stringify({ id: m.id, budget: m.budget, maxTier: m.maxTier }));
|
|
218
|
+
out(JSON.stringify({ id: m.id, budget: m.budget, maxTier: m.maxTier, paperOnly: m.paperOnly === true }));
|
|
212
219
|
return 0;
|
|
213
220
|
}
|
|
214
|
-
out(`drafted ${m.id}`);
|
|
221
|
+
out(`drafted ${m.id}${m.paperOnly ? ' (PAPER-ONLY: agent paper desk only)' : ''}`);
|
|
215
222
|
out(` total ${fmtUsd(m.budget.totalUsd)} · per-trade ${fmtUsd(m.budget.perTradeUsd)} · per-coin ${fmtUsd(m.budget.perCoinUsd)} · daily-loss ${fmtUsd(m.budget.dailyLossUsd)} · maxTier ${m.maxTier}`);
|
|
223
|
+
out(` max open ${m.limits.maxOpenPositions} · max price impact ${m.limits.maxPriceImpactPct}%`);
|
|
216
224
|
out(' DRAFT only — not armed. Arm with: dexterm mandate arm ' + m.id + ' (physical keypress)');
|
|
217
225
|
return 0;
|
|
218
226
|
}
|
|
@@ -228,7 +236,7 @@ export async function runMandate(rest, io) {
|
|
|
228
236
|
return 0;
|
|
229
237
|
}
|
|
230
238
|
for (const r of rows)
|
|
231
|
-
out(`${r.id.padEnd(24)} ${r.status.padEnd(7)} total ${fmtUsd(r.totalUsd)} · reserved ${fmtUsd(r.reservedUsd)} · spent ${fmtUsd(r.spentUsd)} · maxTier ${r.maxTier}`);
|
|
239
|
+
out(`${r.id.padEnd(24)} ${r.status.padEnd(7)} total ${fmtUsd(r.totalUsd)} · reserved ${fmtUsd(r.reservedUsd)} · spent ${fmtUsd(r.spentUsd)} · maxTier ${r.maxTier}${r.paperOnly ? ' · PAPER-ONLY' : ''}`);
|
|
232
240
|
return 0;
|
|
233
241
|
}
|
|
234
242
|
case 'arm': {
|
|
@@ -243,7 +251,9 @@ export async function runMandate(rest, io) {
|
|
|
243
251
|
errOut(`no such mandate ${id}`);
|
|
244
252
|
return 1;
|
|
245
253
|
}
|
|
246
|
-
out(
|
|
254
|
+
out(row.paperOnly
|
|
255
|
+
? `Arm PAPER-ONLY mandate ${id}? This lets the agent paper desk book simulated fills under these caps. No plan, copy trade or signer can use it.`
|
|
256
|
+
: `Arm standing mandate ${id}? This lets armed plans fire at machine speed on PAPER (simulated fills; dexterm places no live order).`);
|
|
247
257
|
out(` total ${fmtUsd(row.totalUsd)} · per-trade ${fmtUsd(row.perTradeUsd)} · per-coin ${fmtUsd(row.perCoinUsd)} · maxTier ${row.maxTier}`);
|
|
248
258
|
out('Hold any key to confirm (physical keypress). A pipe or agent cannot arm. Waiting 15s…');
|
|
249
259
|
const key = await ttyKeypress(io.stdin, 15_000);
|
|
@@ -4,17 +4,26 @@
|
|
|
4
4
|
// positions → every coin with fills (newest activity first; ← → cycle), LIVE or PAPER as above
|
|
5
5
|
// follows → the selected followed wallet's coins (TRACKED), newest first
|
|
6
6
|
// In the preview: x share to X · t copy as text · s save only · a show/hide wallet · z exact size/band ·
|
|
7
|
-
//
|
|
7
|
+
// m market cap ⇄ SOL price (task-1518; mcap is the default when the coin's supply is known) · f 16:9 ⇄ 1:1 ·
|
|
8
|
+
// ← → other coin · esc close. Share = save + clipboard image + X compose intent in the user's own
|
|
8
9
|
// browser (pnl-card.ts shareCard); dexterm posts nothing. Nothing here trades, signs or edits a number.
|
|
9
10
|
import { mix } from '../../tui/src/index.js';
|
|
10
|
-
import { computeCard, renderCard, previewCanvas, cardText, paperCardInput,
|
|
11
|
+
import { computeCard, renderCard, previewCanvas, cardText, paperCardInput, trackedCardInput, cardSupply, mcapView, CardError, } from "./pnl-card.js";
|
|
11
12
|
const MARK_VIA = 'dexscreener';
|
|
12
13
|
function coinMeta(state, mint) {
|
|
13
14
|
const tv = state.live?.token?.view;
|
|
14
15
|
if (tv && tv.mint === mint)
|
|
15
|
-
return { symbol: tv.symbol, name: tv.name, image: tv.image };
|
|
16
|
+
return { symbol: tv.symbol, name: tv.name, image: tv.image, launchpad: tv.launchpad ?? null, fdv: tv.fdv ?? null, price: tv.price ?? null };
|
|
16
17
|
const row = state.screener.rows.find((r) => r.mint === mint);
|
|
17
|
-
return { symbol: row?.symbol ?? null, name: row?.name ?? null, image: row?.image ?? null };
|
|
18
|
+
return { symbol: row?.symbol ?? null, name: row?.name ?? null, image: row?.image ?? null, launchpad: row?.launchpad ?? null, fdv: row?.fdv ?? null, price: row?.price ?? null };
|
|
19
|
+
}
|
|
20
|
+
/** The market-cap inputs for a coin: its recorded supply (FDV ÷ price, else pump.fun's 1B) and today's SOL/USD. */
|
|
21
|
+
function mcapInputs(state, mint, meta) {
|
|
22
|
+
const solUsd = state.live?.positions.solUsd() ?? null;
|
|
23
|
+
return {
|
|
24
|
+
supply: cardSupply({ mint, launchpad: meta.launchpad, fdv: meta.fdv, price: meta.price, via: 'market data' }),
|
|
25
|
+
usdPerSol: solUsd && solUsd > 0 ? { usd: solUsd, source: `$${solUsd.toFixed(2)}/SOL now (display conversion)` } : null,
|
|
26
|
+
};
|
|
18
27
|
}
|
|
19
28
|
function imageOf(state, url) {
|
|
20
29
|
if (!url || !state.images)
|
|
@@ -35,7 +44,7 @@ export function markFor(state, mint) {
|
|
|
35
44
|
return { priceSol: tv.price / solUsd, at: m.fetchedAt, via: `${m.via ?? 'market'} ÷ SOL/USD` };
|
|
36
45
|
return null;
|
|
37
46
|
}
|
|
38
|
-
/** Coins with paper
|
|
47
|
+
/** Coins with paper fills, newest activity first. */
|
|
39
48
|
function tradedCoins(state) {
|
|
40
49
|
const live = state.live;
|
|
41
50
|
if (!live)
|
|
@@ -45,8 +54,6 @@ function tradedCoins(state) {
|
|
|
45
54
|
last.set(mint, { t, symbol }); };
|
|
46
55
|
for (const r of live.wallet.trades(1000))
|
|
47
56
|
see(r.mint, r.t, r.symbol);
|
|
48
|
-
for (const r of live.livePositions?.records(1000) ?? [])
|
|
49
|
-
see(r.mint, r.t, r.symbol);
|
|
50
57
|
return [...last.entries()].sort((a, b) => b[1].t - a[1].t).map(([mint, v]) => ({ mint, symbol: v.symbol }));
|
|
51
58
|
}
|
|
52
59
|
/** Open the preview for the current screen. Returns a reason when there is nothing to card. */
|
|
@@ -75,15 +82,15 @@ export function openPnl(state) {
|
|
|
75
82
|
const mint = state.live.token?.view?.mint ?? state.token?.mint ?? null;
|
|
76
83
|
if (!mint)
|
|
77
84
|
return 'open a coin first';
|
|
78
|
-
const has =
|
|
85
|
+
const has = state.live.wallet.trades(1000).some((r) => r.mint === mint);
|
|
79
86
|
if (!has)
|
|
80
|
-
return 'no paper
|
|
87
|
+
return 'no paper fills for this coin yet: PnL cards come from your own recorded trades';
|
|
81
88
|
state.pnl = { origin: 'token', choices: [{ mint, symbol: coinMeta(state, mint).symbol ?? mint.slice(0, 4) }], wallet: null, ...base };
|
|
82
89
|
}
|
|
83
90
|
else if (state.screen === 'positions') {
|
|
84
91
|
const choices = tradedCoins(state);
|
|
85
92
|
if (!choices.length)
|
|
86
|
-
return 'no paper
|
|
93
|
+
return 'no paper fills yet: B / S on a token to paper-trade, then H here';
|
|
87
94
|
state.pnl = { origin: 'positions', choices, wallet: null, ...base };
|
|
88
95
|
}
|
|
89
96
|
else
|
|
@@ -111,16 +118,12 @@ export async function pnlInput(state, ui) {
|
|
|
111
118
|
const solUsd = live.positions.solUsd();
|
|
112
119
|
if (!mark && m.priceUsd && m.priceAt && solUsd)
|
|
113
120
|
mark = { priceSol: m.priceUsd / solUsd, at: m.priceAt, via: `${MARK_VIA} ÷ SOL/USD` };
|
|
114
|
-
return trackedCardInput(copy.trades(ui.wallet), {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
const liveRows = live.livePositions?.fills(ch.mint) ?? [];
|
|
120
|
-
if (liveRows.length) {
|
|
121
|
-
return liveCardInput(liveRows, { mint: ch.mint, symbol: meta.symbol ?? undefined, name: meta.name, wallet: live.linkedWallet?.publicKey ?? null, mark: markFor(state, ch.mint), image });
|
|
121
|
+
return { ...trackedCardInput(copy.trades(ui.wallet), {
|
|
122
|
+
wallet: ui.wallet, mint: ch.mint, symbol: meta.symbol ?? m.symbol ?? ch.symbol, name: meta.name, decimals, mark,
|
|
123
|
+
historyAt: st.lastPollAt, complete: stats.sample.complete, image,
|
|
124
|
+
}), ...mcapInputs(state, ch.mint, meta) };
|
|
122
125
|
}
|
|
123
|
-
return paperCardInput(live.wallet.trades(1000), { mint: ch.mint, symbol: meta.symbol ?? undefined, name: meta.name, mark: markFor(state, ch.mint), image });
|
|
126
|
+
return { ...paperCardInput(live.wallet.trades(1000), { mint: ch.mint, symbol: meta.symbol ?? undefined, name: meta.name, mark: markFor(state, ch.mint), image }), ...mcapInputs(state, ch.mint, meta) };
|
|
124
127
|
}
|
|
125
128
|
/** (Re)build the card for the current choice; on refusal the preview shows the reason instead of a card. */
|
|
126
129
|
export async function loadPnl(state, now = Date.now()) {
|
|
@@ -167,6 +170,13 @@ export function pnlKey(ui, key) {
|
|
|
167
170
|
ui.notice = ui.opts.exactSize ? 'exact SOL size SHOWN on the card' : 'size shown as a band';
|
|
168
171
|
return { kind: 'none' };
|
|
169
172
|
}
|
|
173
|
+
if (ch === 'm') {
|
|
174
|
+
const now = ui.stats ? mcapView(ui.stats, ui.opts).basis : (ui.opts.basis ?? 'mcap');
|
|
175
|
+
ui.opts.basis = now === 'mcap' ? 'price' : 'mcap';
|
|
176
|
+
const v = ui.stats ? mcapView(ui.stats, ui.opts) : null;
|
|
177
|
+
ui.notice = ui.opts.basis === 'price' ? 'entry / exit as SOL price' : v?.fallback ?? 'entry / exit as market cap';
|
|
178
|
+
return { kind: 'none' };
|
|
179
|
+
}
|
|
170
180
|
if (ch === 'f') {
|
|
171
181
|
ui.opts.aspect = ui.opts.aspect === 'wide' ? 'square' : 'wide';
|
|
172
182
|
return { kind: 'none' };
|
|
@@ -181,7 +191,7 @@ export function pnlKey(ui, key) {
|
|
|
181
191
|
return { kind: 'share', action: 'save' };
|
|
182
192
|
return { kind: 'none' };
|
|
183
193
|
}
|
|
184
|
-
const HINTS = 'x share to X · t copy text · s save · a wallet · z size · f 16:9/1:1 · ←→ coin · esc';
|
|
194
|
+
const HINTS = 'x share to X · t copy text · s save · a wallet · z size · m mcap/price · f 16:9/1:1 · ←→ coin · esc';
|
|
185
195
|
/** The preview overlay: the card itself at cell size when the terminal fits it, else its text; then the keys. */
|
|
186
196
|
export function renderPnlOverlay(c, state) {
|
|
187
197
|
const ui = state.pnl;
|
|
@@ -191,7 +201,7 @@ export function renderPnlOverlay(c, state) {
|
|
|
191
201
|
c.noRain(0, 0, c.w, c.h);
|
|
192
202
|
c.fill(0, 0, c.w, c.h, ' ', { bg: mix(t.bg, '#000000', 0.5) });
|
|
193
203
|
const ch = ui.choices[ui.idx];
|
|
194
|
-
const head = `PnL CARD · $${ch?.symbol ?? '?'}${ui.choices.length > 1 ? ` (${ui.idx + 1}/${ui.choices.length})` : ''} · ${ui.opts.aspect === 'wide' ? '16:9' : '1:1'} · wallet ${ui.opts.showWallet ? 'SHOWN' : 'hidden'} · size ${ui.opts.exactSize ? 'EXACT' : 'band'}`;
|
|
204
|
+
const head = `PnL CARD · $${ch?.symbol ?? '?'}${ui.choices.length > 1 ? ` (${ui.idx + 1}/${ui.choices.length})` : ''} · ${ui.opts.aspect === 'wide' ? '16:9' : '1:1'} · wallet ${ui.opts.showWallet ? 'SHOWN' : 'hidden'} · size ${ui.opts.exactSize ? 'EXACT' : 'band'}${ui.stats ? ` · ${mcapView(ui.stats, ui.opts).basis === 'mcap' ? 'mcap' : 'SOL price'}` : ''}`;
|
|
195
205
|
c.text(1, 0, head, { fg: t.text, bg: t.headBg, b: true }, c.w - 2);
|
|
196
206
|
c.fill(0, c.h - 1, c.w, 1, ' ', { bg: t.headBg });
|
|
197
207
|
c.text(1, c.h - 1, ui.busy ? 'working… (esc closes)' : (ui.notice ?? HINTS), { fg: ui.notice ? t.warn : t.muted, bg: t.headBg }, c.w - 2);
|
|
@@ -7,12 +7,16 @@
|
|
|
7
7
|
// Honesty rules, enforced by construction:
|
|
8
8
|
// - The stamp (PAPER / LIVE / TRACKED WALLET) comes from `CardInput.source` and is drawn by the renderer in three
|
|
9
9
|
// places (the stamp block, the frame colour, the stamp strip). No option turns it off; an unknown source throws.
|
|
10
|
-
// - Numbers come only from fills (the paper ledger, the
|
|
10
|
+
// - Numbers come only from fills (the paper ledger, the trade ledger, a tracked wallet's decoded history).
|
|
11
11
|
// There is no API that takes a PnL, a price or a size; every figure is computed here from those fills, with
|
|
12
12
|
// the time window, the fee basis, what was excluded and the data ages printed on the card.
|
|
13
13
|
// - Wallet addresses (and LIVE tx signatures, which point at the wallet) are hidden unless `showWallet`; size is a
|
|
14
14
|
// band unless `exactSize`; no balance, no key, no file path is ever drawn.
|
|
15
15
|
// - Footer: "not financial advice" and the dexterm URL. No performance claims beyond this one position's fills.
|
|
16
|
+
// - Market cap (task-1518): ENTRY / EXIT are shown as market cap when the token supply is known (`basis: 'mcap'`,
|
|
17
|
+
// the default then; `basis: 'price'` keeps the SOL price view). Each mcap is a fill-derived price × the supply,
|
|
18
|
+
// never a quoted mcap, and the card names the supply source (recorded FDV ÷ price, or pump.fun's fixed 1B) and
|
|
19
|
+
// the USD conversion. Exit mcap is the blended average of every sell; the last sell's mcap is its own labelled row.
|
|
16
20
|
//
|
|
17
21
|
// Rendering reuses the task-1401 exporter stack: a cell Canvas rasterized by @dexterm/tui (the 5×9 bitmap font) and
|
|
18
22
|
// encoded by @dexterm/data. Big type (the stamp, the ticker, the PnL %) is the same font rasterized at a larger
|
|
@@ -20,7 +24,7 @@
|
|
|
20
24
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
21
25
|
import { join } from 'node:path';
|
|
22
26
|
import { spawn } from 'node:child_process';
|
|
23
|
-
import { Canvas, THEMES, rasterizeCanvas, fmtPriceBare, mix } from '../../tui/src/index.js';
|
|
27
|
+
import { Canvas, THEMES, rasterizeCanvas, fmtPriceBare, fmtUsd, fmtNum, mix } from '../../tui/src/index.js';
|
|
24
28
|
import { encodePng, lamportsToSol } from '../../data/src/index.js';
|
|
25
29
|
import { exportsDir, fmtAge } from "./export.js";
|
|
26
30
|
import { cleanText } from "./screener/rows.js";
|
|
@@ -41,18 +45,6 @@ export function paperCardInput(records, o) {
|
|
|
41
45
|
excluded: [], fees: 'simulated network fee included', image: o.image ?? null,
|
|
42
46
|
};
|
|
43
47
|
}
|
|
44
|
-
/** LIVE: the signer's ledger rows (confirmed on-chain fills, swaps only) for one mint. */
|
|
45
|
-
export function liveCardInput(rows, o) {
|
|
46
|
-
const rs = rows.filter((r) => r.mint === o.mint && r.kind === 'swap');
|
|
47
|
-
const nets = new Set(rs.map((r) => r.network));
|
|
48
|
-
const network = nets.size > 1 ? 'mixed' : rs[0]?.network === 'devnet' ? 'devnet' : 'mainnet';
|
|
49
|
-
return {
|
|
50
|
-
source: 'live', network, mint: o.mint, symbol: o.symbol ?? rs[0]?.symbol ?? '?', name: o.name ?? null, decimals: rs[0]?.decimals ?? null,
|
|
51
|
-
fills: rs.map((r) => ({ t: r.t, side: r.side === 'sell' ? 'sell' : 'buy', solDelta: lamportsToSol(BigInt(r.solDelta)), tokenDelta: BigInt(r.tokenDelta), feeSol: lamportsToSol(BigInt(r.feeLamports)), sig: r.signature })),
|
|
52
|
-
ledger: 'signer live ledger', wallet: o.wallet ?? null, mark: o.mark ?? null,
|
|
53
|
-
excluded: network === 'mixed' ? ['devnet and mainnet fills are both in the ledger for this coin'] : [], fees: 'network + priority fees included', image: o.image ?? null,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
48
|
/** TRACKED: a followed wallet's decoded swaps for one mint (read-only public history). */
|
|
57
49
|
export function trackedCardInput(trades, o) {
|
|
58
50
|
const all = trades.filter((t) => t.mint === o.mint && t.wallet === o.wallet);
|
|
@@ -99,6 +91,74 @@ export function trackedCardInput(trades, o) {
|
|
|
99
91
|
excluded, fees: 'network fees + tips included (balance deltas)', image: o.image ?? null,
|
|
100
92
|
};
|
|
101
93
|
}
|
|
94
|
+
/** pump.fun mints a fixed 1,000,000,000 supply per coin (none is minted later), so its market cap is price × 1B. */
|
|
95
|
+
export const PUMP_FUN_SUPPLY = 1_000_000_000;
|
|
96
|
+
/**
|
|
97
|
+
* The supply for the market-cap view: the token's recorded FDV ÷ price when both are known (total supply, so on a
|
|
98
|
+
* non-pump coin this is a fully diluted cap and says so), else pump.fun's fixed 1B for a pump.fun coin, else null
|
|
99
|
+
* (the card then stays on the price view; no supply is ever guessed).
|
|
100
|
+
*/
|
|
101
|
+
export function cardSupply(o) {
|
|
102
|
+
if (o.fdv && o.price && Number.isFinite(o.fdv / o.price) && o.fdv > 0 && o.price > 0) {
|
|
103
|
+
return { tokens: o.fdv / o.price, source: `recorded FDV ÷ price${o.via ? ` (${o.via})` : ''}` };
|
|
104
|
+
}
|
|
105
|
+
if (o.launchpad === 'pump.fun' || ((!o.launchpad || o.launchpad === 'unknown') && o.mint.endsWith('pump'))) {
|
|
106
|
+
return { tokens: PUMP_FUN_SUPPLY, source: 'pump.fun fixed supply' };
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* AUTOTRADE PAPER: one position's fills from an autotrade run ledger. The desk books in USD, so each fill's SOL leg
|
|
112
|
+
* is its USD at `solUsd` (said on the card); the market caps come straight from the USD fills × supply, so the SOL
|
|
113
|
+
* rate changes only the size band, never a cap or the return.
|
|
114
|
+
*/
|
|
115
|
+
export function autotradeCardInput(events, o) {
|
|
116
|
+
if (!(o.solUsd.usd > 0))
|
|
117
|
+
throw new CardError('an autotrade card needs a SOL/USD rate to show the USD desk in SOL');
|
|
118
|
+
const fs = events.filter((e) => e.kind === 'fill' && e.positionId === o.positionId && (e.side === 'buy' || e.side === 'sell') && (e.usd ?? 0) > 0 && (e.tokens ?? 0) > 0);
|
|
119
|
+
const mint = fs[0]?.mint ?? '';
|
|
120
|
+
const closed = events.find((e) => e.kind === 'trade_closed' && e.trade?.id === o.positionId);
|
|
121
|
+
const decimals = o.decimals ?? 6;
|
|
122
|
+
return {
|
|
123
|
+
source: 'paper', mint, symbol: o.symbol ?? closed?.trade?.symbol ?? mint.slice(0, 4), name: o.name ?? null, decimals,
|
|
124
|
+
fills: fs.map((e) => ({
|
|
125
|
+
t: e.t, side: e.side, solDelta: (e.side === 'buy' ? -1 : 1) * e.usd / o.solUsd.usd,
|
|
126
|
+
tokenDelta: BigInt(Math.round(e.tokens * 10 ** decimals)) * (e.side === 'buy' ? 1n : -1n), feeSol: null, sig: null,
|
|
127
|
+
})),
|
|
128
|
+
ledger: o.runId ? `autotrade paper run ${o.runId}` : 'autotrade paper ledger', wallet: null, mark: o.mark ?? null, excluded: [],
|
|
129
|
+
fees: `USD paper desk, impact in fills · SOL $${o.solUsd.usd.toFixed(2)} (${o.solUsd.source})`, image: o.image ?? null,
|
|
130
|
+
supply: o.supply === undefined ? cardSupply({ mint }) : o.supply,
|
|
131
|
+
usdPerSol: { usd: o.solUsd.usd, source: 'the desk\'s USD fills', native: true },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* TRADE LEDGER (task-1530): one round trip of the unified ledger as a card. The stamp follows the wallet mode
|
|
136
|
+
* (paper → PAPER; devnet / live → LIVE with its network), and ENTRY / EXIT use the market-cap basis whenever the
|
|
137
|
+
* ledger recorded a supply (the same mcap code as every other card). A fill with no SOL leg (the autotrade USD desk)
|
|
138
|
+
* needs `solUsd`; a fill with no token amount refuses the card rather than guess.
|
|
139
|
+
*/
|
|
140
|
+
export function ledgerCardInput(row, o = {}) {
|
|
141
|
+
const decimals = row.decimals ?? 6;
|
|
142
|
+
const usdDesk = row.fills.some((f) => f.sol === null);
|
|
143
|
+
if (usdDesk && !(o.solUsd && o.solUsd.usd > 0))
|
|
144
|
+
throw new CardError('this trade was booked in USD: a card needs a SOL/USD rate (--sol-usd)');
|
|
145
|
+
const fills = row.fills.map((f) => {
|
|
146
|
+
if (!(f.tokens !== null && f.tokens > 0))
|
|
147
|
+
throw new CardError(`fill ${f.t} has no token amount in the ledger`);
|
|
148
|
+
const sol = f.sol !== null && !usdDesk ? f.sol : (f.usd ?? 0) / o.solUsd.usd;
|
|
149
|
+
return { t: f.t, side: f.side, solDelta: (f.side === 'buy' ? -1 : 1) * Math.abs(sol), tokenDelta: BigInt(Math.round(f.tokens * 10 ** decimals)) * (f.side === 'buy' ? 1n : -1n), feeSol: f.feeSol, sig: row.walletMode === 'paper' ? null : f.txSig };
|
|
150
|
+
});
|
|
151
|
+
const where = row.source === 'autotrade' ? `autotrade paper run ${row.runId ?? ''}`.trim() : row.source === 'desk' ? 'agent paper desk' : row.source === 'live' ? 'signer live ledger' : 'dexterm paper ledger';
|
|
152
|
+
return {
|
|
153
|
+
source: row.walletMode === 'paper' ? 'paper' : 'live', network: row.walletMode === 'paper' ? null : row.walletMode === 'devnet' ? 'devnet' : 'mainnet',
|
|
154
|
+
mint: row.mint, symbol: row.symbol, name: o.name ?? null, decimals, fills,
|
|
155
|
+
ledger: `trade ledger · ${where} · ${row.tradeId}`, wallet: null, mark: o.mark ?? null, excluded: [],
|
|
156
|
+
fees: usdDesk ? `USD paper desk, impact in fills · SOL $${o.solUsd.usd.toFixed(2)} (${o.solUsd.source})` : row.walletMode === 'paper' ? 'simulated network fee included' : 'network + priority fees included',
|
|
157
|
+
image: o.image ?? null,
|
|
158
|
+
supply: row.supply ?? cardSupply({ mint: row.mint }),
|
|
159
|
+
...(usdDesk ? { usdPerSol: { usd: o.solUsd.usd, source: 'the desk\'s USD fills', native: true } } : o.solUsd ? { usdPerSol: o.solUsd } : {}),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
102
162
|
/** Cost-basis maths over the fills (average cost, the paper and live ledgers' own rule). Throws CardError when a card would mislead. */
|
|
103
163
|
export function computeCard(input, now) {
|
|
104
164
|
if (input.source !== 'paper' && input.source !== 'live' && input.source !== 'tracked')
|
|
@@ -109,7 +169,7 @@ export function computeCard(input, now) {
|
|
|
109
169
|
if (!fills.some((f) => f.side === 'buy'))
|
|
110
170
|
throw new CardError('no recorded buy for this coin, so there is no cost basis to measure against');
|
|
111
171
|
let held = 0n, peak = 0n, cost = 0, costIn = 0, proceeds = 0, realized = 0, boughtRaw = 0n, soldRaw = 0n, buys = 0, sells = 0, unmatched = 0;
|
|
112
|
-
let fees = 0, feesKnown = false, firstBuy = Infinity, lastSell = -Infinity;
|
|
172
|
+
let fees = 0, feesKnown = false, firstBuy = Infinity, lastSell = -Infinity, lastSellAvg = null;
|
|
113
173
|
for (const f of fills) {
|
|
114
174
|
if (f.feeSol !== null) {
|
|
115
175
|
fees += f.feeSol;
|
|
@@ -147,6 +207,8 @@ export function computeCard(input, now) {
|
|
|
147
207
|
cost -= removed;
|
|
148
208
|
held -= matched;
|
|
149
209
|
soldRaw += matched;
|
|
210
|
+
if (input.decimals !== null)
|
|
211
|
+
lastSellAvg = got / (Number(matched) / 10 ** input.decimals);
|
|
150
212
|
}
|
|
151
213
|
}
|
|
152
214
|
if (!buys)
|
|
@@ -174,7 +236,7 @@ export function computeCard(input, now) {
|
|
|
174
236
|
input, buys, sells, firstT: fills[0].t, lastT: fills[fills.length - 1].t, open,
|
|
175
237
|
costInSol: costIn, proceedsSol: proceeds, realizedSol: realized, unrealizedSol: unrealized, pnlSol: pnl, pnlPct: costIn > 0 ? pnl / costIn : 0,
|
|
176
238
|
kind: !open ? 'realized' : sells ? 'realized + unrealized' : 'unrealized',
|
|
177
|
-
entryAvg: boughtW ? costIn / boughtW : null, exitAvg: soldW ? proceeds / soldW : null,
|
|
239
|
+
entryAvg: boughtW ? costIn / boughtW : null, exitAvg: soldW ? proceeds / soldW : null, lastSellAvg,
|
|
178
240
|
holdMs: Math.max(0, (open ? now : lastSell === -Infinity ? now : lastSell) - (firstBuy === Infinity ? fills[0].t : firstBuy)),
|
|
179
241
|
feesSol: feesKnown ? fees : null, now,
|
|
180
242
|
};
|
|
@@ -217,6 +279,66 @@ export function fmtWhen(t) {
|
|
|
217
279
|
}
|
|
218
280
|
const signedSol = (n) => `${n >= 0 ? '+' : '-'}${Math.abs(n).toFixed(4)} SOL`;
|
|
219
281
|
const short = (a) => (a.length > 10 ? `${a.slice(0, 4)}…${a.slice(-4)}` : a);
|
|
282
|
+
/** `1B` / `998.8M` / `21,000,000` → a short supply figure for the provenance line. */
|
|
283
|
+
const fmtSupply = (n) => (n === PUMP_FUN_SUPPLY ? '1B' : fmtNum(n));
|
|
284
|
+
/** Resolve the basis and the market caps for a card: every figure is a fill-derived SOL price × supply (× USD/SOL). */
|
|
285
|
+
export function mcapView(s, o = {}) {
|
|
286
|
+
const sup = s.input.supply;
|
|
287
|
+
const want = o.basis ?? (sup ? 'mcap' : 'price');
|
|
288
|
+
const none = { basis: 'price', fallback: null, unit: 'SOL', entry: null, exitBlended: null, lastSell: null, mark: null };
|
|
289
|
+
if (want === 'price')
|
|
290
|
+
return none;
|
|
291
|
+
if (!sup || !(sup.tokens > 0))
|
|
292
|
+
return { ...none, fallback: 'market cap not shown: no token supply recorded, so prices are in SOL' };
|
|
293
|
+
if (s.input.decimals === null)
|
|
294
|
+
return { ...none, fallback: 'market cap not shown: token decimals unknown, so prices are in SOL' };
|
|
295
|
+
const k = sup.tokens * (s.input.usdPerSol && s.input.usdPerSol.usd > 0 ? s.input.usdPerSol.usd : 1);
|
|
296
|
+
const m = (p) => (p === null || p === undefined || !Number.isFinite(p) ? null : p * k);
|
|
297
|
+
return {
|
|
298
|
+
basis: 'mcap', fallback: null, unit: s.input.usdPerSol && s.input.usdPerSol.usd > 0 ? 'USD' : 'SOL',
|
|
299
|
+
entry: m(s.entryAvg), exitBlended: m(s.exitAvg), lastSell: m(s.lastSellAvg), mark: s.open ? m(s.input.mark?.priceSol) : null,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/** `$69.7K` / `$1.23M` in USD, `412.3 SOL` / `1.20K SOL` in SOL. */
|
|
303
|
+
export function fmtMcap(v, unit) {
|
|
304
|
+
if (v === null)
|
|
305
|
+
return '—';
|
|
306
|
+
return unit === 'USD' ? fmtUsd(v) : `${fmtNum(v)} SOL`;
|
|
307
|
+
}
|
|
308
|
+
/** The ENTRY / EXIT rows for the market-cap view (closed: entry, blended exit, last sell when there are 2+ sells). */
|
|
309
|
+
function mcapRows(s, v) {
|
|
310
|
+
const f = (x) => fmtMcap(x, v.unit);
|
|
311
|
+
const rows = [['ENTRY mcap', `${f(v.entry)} (avg fill)`]];
|
|
312
|
+
if (s.open)
|
|
313
|
+
rows.push(['MARK mcap', f(v.mark)]);
|
|
314
|
+
else
|
|
315
|
+
rows.push(['EXIT mcap', s.sells > 1 ? `${f(v.exitBlended)} (blended)` : f(v.exitBlended)]);
|
|
316
|
+
if (s.sells > 1 || (s.open && s.sells))
|
|
317
|
+
rows.push(['LAST SELL', `${f(v.lastSell)} mcap`]);
|
|
318
|
+
return rows;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* The card's own statement of where the caps come from, as two stat rows (the provenance block is full on a LIVE
|
|
322
|
+
* card with the wallet shown, and OPENED / CLOSED repeat its window line, so these two take their place).
|
|
323
|
+
*/
|
|
324
|
+
function mcapSourceRows(s, v) {
|
|
325
|
+
const sup = s.input.supply, usd = s.input.usdPerSol;
|
|
326
|
+
const src = sup.source.startsWith('pump.fun') ? 'pump.fun fixed' : sup.source.startsWith('recorded FDV') ? 'FDV/price' : cleanText(sup.source, 12);
|
|
327
|
+
return [
|
|
328
|
+
['SUPPLY', `${fmtSupply(sup.tokens)} · ${src}`],
|
|
329
|
+
['MCAP IN', v.unit === 'SOL' ? 'SOL (no USD rate)' : usd?.native ? 'USD (desk fills)' : `USD at $${usd.usd.toFixed(2)}/SOL`],
|
|
330
|
+
];
|
|
331
|
+
}
|
|
332
|
+
/** The provenance line of the market-cap view (or of a fallback to the price view). */
|
|
333
|
+
function mcapLine(s, v) {
|
|
334
|
+
if (v.fallback)
|
|
335
|
+
return v.fallback;
|
|
336
|
+
if (v.basis !== 'mcap' || !s.input.supply)
|
|
337
|
+
return null;
|
|
338
|
+
const sup = s.input.supply, usd = s.input.usdPerSol;
|
|
339
|
+
const conv = v.unit === 'USD' ? ` · USD: ${usd.source}` : ' · in SOL';
|
|
340
|
+
return `mcap = fill price × ${fmtSupply(sup.tokens)} supply (${sup.source})${conv}`;
|
|
341
|
+
}
|
|
220
342
|
/** The stamp of a source. Exhaustive: anything else throws, so a card can never be drawn without one. */
|
|
221
343
|
export function stampOf(s, t) {
|
|
222
344
|
const src = s.input.source;
|
|
@@ -251,6 +373,10 @@ export function provenance(s, o, mode = 'text') {
|
|
|
251
373
|
if (mode === 'text')
|
|
252
374
|
ages.push(`card made ${fmtWhen(s.now)}`);
|
|
253
375
|
L.push(`data ages: ${ages.join(' · ')}`);
|
|
376
|
+
// the card states the market-cap source in its stat rows (mcapSourceRows); the text spells it out here
|
|
377
|
+
const ml = mode === 'text' ? mcapLine(s, mcapView(s, o)) : null;
|
|
378
|
+
if (ml)
|
|
379
|
+
L.push(ml);
|
|
254
380
|
if (o.showWallet && i.wallet)
|
|
255
381
|
L.push(`wallet: ${i.wallet}`);
|
|
256
382
|
if (i.source === 'live' && o.showWallet) {
|
|
@@ -351,15 +477,22 @@ export function renderCard(s, o = {}) {
|
|
|
351
477
|
: 'return on SOL spent · net of fees · size shown as a band';
|
|
352
478
|
const dec = s.input.decimals !== null;
|
|
353
479
|
const px = (p) => (p === null ? (dec ? '—' : 'n/a (decimals unknown)') : `${fmtPriceBare(p)} SOL`);
|
|
480
|
+
const mv = mcapView(s, o);
|
|
354
481
|
const stats = [
|
|
355
|
-
|
|
356
|
-
|
|
482
|
+
...(mv.basis === 'mcap' ? mcapRows(s, mv) : [
|
|
483
|
+
['ENTRY avg', px(s.entryAvg)],
|
|
484
|
+
s.open ? ['MARK', px(s.input.mark?.priceSol ?? null)] : ['EXIT avg', px(s.exitAvg)],
|
|
485
|
+
]),
|
|
357
486
|
['HOLD', `${fmtHold(s.holdMs)}${s.open ? ' (open)' : ''}`],
|
|
358
487
|
['SIZE', o.exactSize ? `${s.costInSol.toFixed(4)} SOL in` : `${sizeBand(s.costInSol)} (band)`],
|
|
359
488
|
['FILLS', `${s.buys} buy${s.buys === 1 ? '' : 's'} · ${s.sells} sell${s.sells === 1 ? '' : 's'}`],
|
|
360
|
-
|
|
361
|
-
|
|
489
|
+
...(mv.basis === 'mcap' ? mcapSourceRows(s, mv) : [
|
|
490
|
+
['OPENED', fmtWhen(s.firstT)],
|
|
491
|
+
['CLOSED', s.open ? 'still open' : fmtWhen(s.lastT)],
|
|
492
|
+
]),
|
|
362
493
|
];
|
|
494
|
+
if (mv.fallback)
|
|
495
|
+
stats.push(['MCAP', mv.fallback.startsWith('market cap not shown: no token supply') ? 'n/a: no supply known' : 'n/a: decimals unknown']);
|
|
363
496
|
const prov = [...provenance(s, o, 'card'), stamp.note];
|
|
364
497
|
const coinTile = (x, y, w, h) => {
|
|
365
498
|
if (s.input.image && s.input.image.w > 0 && s.input.image.h > 0) {
|
|
@@ -510,7 +643,11 @@ export function cardText(s, o = {}) {
|
|
|
510
643
|
L.push(`${s.kind} PnL ${fmtPnlPct(s.pnlPct)} (net of fees)${o.exactSize ? ` · ${signedSol(s.pnlSol)} on ${s.costInSol.toFixed(4)} SOL in` : ` · size ${sizeBand(s.costInSol)} (band)`}`);
|
|
511
644
|
const dec = s.input.decimals !== null;
|
|
512
645
|
const p = (v) => (v === null ? (dec ? '—' : 'n/a') : `${fmtPriceBare(v)} SOL`);
|
|
513
|
-
|
|
646
|
+
const mv = mcapView(s, o);
|
|
647
|
+
const head = mv.basis === 'mcap'
|
|
648
|
+
? mcapRows(s, mv).map(([k, v]) => `${k.toLowerCase()} ${v}`).join(' · ')
|
|
649
|
+
: `entry avg ${p(s.entryAvg)} · ${s.open ? `mark ${p(s.input.mark?.priceSol ?? null)}` : `exit avg ${p(s.exitAvg)}`}`;
|
|
650
|
+
L.push(`${head} · hold ${fmtHold(s.holdMs)}${s.open ? ' (open)' : ''} · ${s.buys} buys · ${s.sells} sells`);
|
|
514
651
|
L.push(...provenance(s, o));
|
|
515
652
|
L.push(st.note + ' Trading in dexterm is experimental.');
|
|
516
653
|
L.push(`not financial advice · ${DEXTERM_URL}`);
|