dexterm-cli 0.1.6 → 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/schema.js +22 -1
- package/dist/packages/agents/src/story.js +11 -2
- package/dist/packages/agents/src/tiers.js +27 -2
- package/dist/packages/agents/src/tools.js +30 -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/token-dossier/SKILL.md +1 -1
- package/dist/packages/cli/src/ai-cmd.js +412 -0
- package/dist/packages/cli/src/analyst/backend.js +82 -15
- package/dist/packages/cli/src/analyst/codex.js +14 -9
- package/dist/packages/cli/src/analyst/ollama.js +263 -0
- package/dist/packages/cli/src/analyst/session.js +5 -2
- package/dist/packages/cli/src/app-help-gen.js +44 -8
- 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/display.js +156 -17
- 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/index.js +23 -89
- package/dist/packages/cli/src/live/screens/token-live.js +9 -6
- package/dist/packages/cli/src/live/trade.js +12 -457
- package/dist/packages/cli/src/live/watchlist.js +1 -1
- package/dist/packages/cli/src/main.js +147 -190
- package/dist/packages/cli/src/mcp-cmd.js +7 -1
- package/dist/packages/cli/src/plan-cmd.js +1 -1
- 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 +22 -6
- package/dist/packages/cli/src/record-cmd.js +141 -20
- package/dist/packages/cli/src/screens/agent-hud.js +34 -2
- package/dist/packages/cli/src/screens/analyst-session.js +7 -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 +37 -11
- 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 +9 -8
- package/dist/packages/cli/src/screens/positions.js +6 -28
- package/dist/packages/cli/src/screens/ticket.js +38 -205
- package/dist/packages/cli/src/screens/token.js +3 -3
- package/dist/packages/cli/src/state.js +25 -19
- 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 +35 -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 +0 -14
- package/dist/packages/data/src/store.js +14 -5
- package/dist/packages/data/src/trade/agent/paper-desk.js +228 -16
- 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/index.js +0 -1
- package/dist/packages/data/src/trade/plans/service.js +3 -27
- 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/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/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 +3 -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/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 +4 -1
- 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-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 +1 -1
- 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 +8 -6
- 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.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 +3 -4
- 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 +6 -7
- 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-what-it-shows.md +2 -1
- package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
- 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-the-brain-view-and-the-pnl-chip.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 +2 -0
- package/dist/packages/mcp/src/server.js +127 -19
- package/dist/packages/packs/src/backtest.js +3 -1
- package/dist/packages/popup/src/pages/chart-lib.js +134 -21
- package/dist/packages/popup/src/pages/chart.css +9 -0
- package/dist/packages/popup/src/pages/chart.html +3 -1
- package/dist/packages/popup/src/pages/chart.js +104 -7
- package/dist/packages/popup/src/protocol.js +2 -0
- package/dist/packages/popup/src/server.js +4 -0
- 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/index.js +1 -0
- package/dist/packages/tui/src/keymap.js +21 -1
- package/dist/packages/tui/src/markers.js +219 -0
- package/dist/packages/tui/src/widgets.js +149 -2
- 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
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
// dexterm autotrade — backtest + parameter sweep (task-1498 spike, AUTOTRADE-SPEC §3).
|
|
2
|
+
//
|
|
3
|
+
// A backtest is the run loop over a recorded tape with the same engine, clamp and monitor a paper run uses
|
|
4
|
+
// (no separate "backtest logic" to drift). The report gives P/L, win rate, drawdown and per-rule attribution
|
|
5
|
+
// (which exit rule closed what, which trigger opened it, which clamp fired), and always prints the limits of
|
|
6
|
+
// the data next to the numbers.
|
|
7
|
+
//
|
|
8
|
+
// Sweeps follow the skill-care methodology: split the tape CHRONOLOGICALLY by first sighting (the newest
|
|
9
|
+
// 30 % of coins is the held-out TEST, fixed before any tuning; no coin is in both halves), tune on TRAIN only,
|
|
10
|
+
// then report TEST for the winner against the unchanged strategy. Below SKILL_MIN_N test trades the verdict
|
|
11
|
+
// is "too few trades" whatever the numbers say.
|
|
12
|
+
//
|
|
13
|
+
// task-1503 (AT-1, spec §3a): the moonbag lives on a rare tail, so the report also carries the per-entry
|
|
14
|
+
// expectancy with a bootstrap 95% interval, the exit-multiple quantiles, an every-candidate evaluation copy
|
|
15
|
+
// (fixed unit size, book caps lifted, so every entry is an independent sample) and runner recall (of the coins
|
|
16
|
+
// that later ran >= Nx, which did the entry rules pass, what refused the rest, which exit closed the entered).
|
|
17
|
+
// Verdict rule 6: no tuning claim unless train and test both have >= SKILL_MIN_N trades AND the held-out net is
|
|
18
|
+
// above 0 (a loser that loses less does not "win").
|
|
19
|
+
import { SKILL_MIN_N, wilson } from '../../packs/src/index.js';
|
|
20
|
+
import { EntryRules } from "./engine.js";
|
|
21
|
+
import { runAutotrade } from "./runner.js";
|
|
22
|
+
import { validateStrategy, round } from "./strategy.js";
|
|
23
|
+
export const DATA_LIMITS = [
|
|
24
|
+
'Price path = recorder snapshots (~2-min cadence while listed, written on >= 5% moves or every 30 min), not candles or trades: intrabar spikes and dips are invisible, so stops and take-profits fill at the next recorded price.',
|
|
25
|
+
'Fills are modelled (constant-product impact on half the recorded liquidity + fee + slippage + network fee), not Jupiter quotes.',
|
|
26
|
+
'Survivorship: only coins the app recorded (on a screen or in a feed) are in the tape; coins nobody saw are absent.',
|
|
27
|
+
'Rugs: when liquidity falls under the exit floor the bag cannot be sold (stuck) and is valued at 0 unless liquidity returns; a rug between two rows fills at whatever the next row says (often -99%), never at the level a rule wanted.',
|
|
28
|
+
'Early entries look later than they are: a young pair enters the tape only once a feed lists it, and pairs that rugged before any feed saw them are missing, so the measured rug rate is a floor.',
|
|
29
|
+
'Narrative, followed-wallet, social and dev-sell signals are not in the recorded history, so those triggers and exits never fire in a backtest.',
|
|
30
|
+
'Mechanical entries (no agent): a lockstep backtest has no agent latency and no agent judgement; agent backtests cost Claude usage and stay small-n.',
|
|
31
|
+
];
|
|
32
|
+
/** A closed trade at or below this is counted as a rug (the challenge's rugAtPct). */
|
|
33
|
+
export const RUG_PCT = -80;
|
|
34
|
+
export async function backtest(strategy, tape, sessions = null) {
|
|
35
|
+
const run = await runAutotrade({ strategy, tape, sessions, pacing: { mode: 'lockstep' } });
|
|
36
|
+
return { report: report(strategy, tape, run), run };
|
|
37
|
+
}
|
|
38
|
+
export function report(s, tape, run) {
|
|
39
|
+
// Positions still open at the end are closed at their liquidation mark (0 when stuck) for the totals.
|
|
40
|
+
const recovered = (fills) => fills.some((f) => f.reason === 'cost-recovery');
|
|
41
|
+
const closed = run.trades.map((t) => ({ pnl: t.pnlUsd, pct: t.pnlPct, exit: t.exitReason, profile: t.profileId, trig: t.entryTrigger, stuck: t.stuck, cost: t.costUsd, recovered: recovered(t.fills) }));
|
|
42
|
+
for (const p of run.openAtEnd) {
|
|
43
|
+
const pos = run.book.open.get(p.mint);
|
|
44
|
+
const pnl = p.markUsd + pos.realizedUsd - pos.costUsd;
|
|
45
|
+
closed.push({ pnl, pct: round((pnl / pos.costUsd) * 100), exit: p.stuck ? 'end-stuck' : 'end-of-tape', profile: pos.profileId, trig: [], stuck: p.stuck, cost: pos.costUsd, recovered: recovered(pos.fills) });
|
|
46
|
+
}
|
|
47
|
+
const best = closed.length ? closed.reduce((a, t) => (t.pnl > a.pnl ? t : a)) : null;
|
|
48
|
+
const wins = closed.filter((t) => t.pnl > 0);
|
|
49
|
+
const losses = closed.filter((t) => t.pnl <= 0);
|
|
50
|
+
const net = closed.reduce((a, t) => a + t.pnl, 0);
|
|
51
|
+
const gw = wins.reduce((a, t) => a + t.pnl, 0);
|
|
52
|
+
const gl = -losses.reduce((a, t) => a + t.pnl, 0);
|
|
53
|
+
const bucket = (key) => {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const t of closed)
|
|
56
|
+
for (const k of key(t)) {
|
|
57
|
+
const b = (out[k] ??= { n: 0, pnlUsd: 0 });
|
|
58
|
+
b.n++;
|
|
59
|
+
b.pnlUsd = round(b.pnlUsd + t.pnl);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
};
|
|
63
|
+
const avg = (xs) => (xs.length ? round(xs.reduce((a, b) => a + b, 0) / xs.length) : null);
|
|
64
|
+
const w = wilson(wins.length, closed.length);
|
|
65
|
+
const first = tape.ticks[0]?.t, last = tape.ticks[tape.ticks.length - 1]?.t;
|
|
66
|
+
return {
|
|
67
|
+
strategy: { id: s.id, version: s.version, hash: s.hash },
|
|
68
|
+
tape: { source: tape.source, mints: tape.mints.length, ticks: tape.ticks.length, from: first ? new Date(first).toISOString() : null, to: last ? new Date(last).toISOString() : null },
|
|
69
|
+
trades: closed.length, wins: wins.length, losses: losses.length,
|
|
70
|
+
winRate: w ? { value: round(w.value * 100, 1), lo: round(w.lo * 100, 1), hi: round(w.hi * 100, 1) } : null,
|
|
71
|
+
netUsd: round(net), netPct: round((net / s.bankroll.startUsd) * 100),
|
|
72
|
+
profitFactor: gl > 0 ? round(gw / gl) : null,
|
|
73
|
+
avgWinPct: avg(wins.map((t) => t.pct)), avgLossPct: avg(losses.map((t) => t.pct)),
|
|
74
|
+
worstPct: closed.length ? Math.min(...closed.map((t) => t.pct)) : null, bestPct: closed.length ? Math.max(...closed.map((t) => t.pct)) : null,
|
|
75
|
+
maxDrawdownPct: run.maxDrawdownPct,
|
|
76
|
+
tail: {
|
|
77
|
+
rugs: closed.filter((t) => t.pct <= RUG_PCT || t.stuck).length, costRecovered: closed.filter((t) => t.recovered).length, netWithoutBestUsd: round(net - (best?.pnl ?? 0)), bestMultiple: best ? round((best.pnl + best.cost) / best.cost) : null,
|
|
78
|
+
perEntry: bootstrapMean(closed.map((t) => (t.pnl / t.cost) * 100)),
|
|
79
|
+
multiples: closed.length ? (() => { const m = closed.map((t) => (t.pnl + t.cost) / t.cost).sort((a, b) => a - b); return { p50: round(quantile(m, 0.5)), p90: round(quantile(m, 0.9)), max: round(m[m.length - 1]) }; })() : null,
|
|
80
|
+
},
|
|
81
|
+
stuck: closed.filter((t) => t.stuck).length, openAtEnd: run.openAtEnd.length,
|
|
82
|
+
candidates: run.candidates, sessions: run.sessions, sessionCostUsd: round(run.sessionCostUsd, 4),
|
|
83
|
+
byExit: bucket((t) => [t.exit]), byProfile: bucket((t) => [t.profile]), byTrigger: bucket((t) => (t.trig.length ? t.trig : ['(open at end)'])),
|
|
84
|
+
clamps: run.clamps, refusals: run.refusals,
|
|
85
|
+
limits: DATA_LIMITS,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function renderReport(r) {
|
|
89
|
+
const usd = (x) => `${x >= 0 ? '+' : '-'}$${Math.abs(x).toFixed(2)}`;
|
|
90
|
+
const rows = (o) => Object.entries(o).sort((a, b) => b[1].n - a[1].n).map(([k, b]) => ` ${k.padEnd(22)} ${String(b.n).padStart(4)} ${usd(b.pnlUsd)}`).join('\n');
|
|
91
|
+
return [
|
|
92
|
+
`BACKTEST ${r.strategy.id} v${r.strategy.version} (${r.strategy.hash}) · PAPER · recorded data`,
|
|
93
|
+
`tape: ${r.tape.mints} coins, ${r.tape.ticks} rows, ${r.tape.from ?? '—'} → ${r.tape.to ?? '—'}`,
|
|
94
|
+
`trades ${r.trades} · wins ${r.wins} · losses ${r.losses} · win rate ${r.winRate ? `${r.winRate.value}% (95% ${r.winRate.lo}–${r.winRate.hi}%)` : '—'}`,
|
|
95
|
+
`net ${usd(r.netUsd)} (${r.netPct}% of the bankroll) · profit factor ${r.profitFactor ?? '—'} · avg win ${r.avgWinPct ?? '—'}% · avg loss ${r.avgLossPct ?? '—'}% · best ${r.bestPct ?? '—'}% · worst ${r.worstPct ?? '—'}%`,
|
|
96
|
+
`tail: rugs (<= ${RUG_PCT}% or stuck) ${r.tail.rugs} · cost taken back ${r.tail.costRecovered} · net without the best trade ${usd(r.tail.netWithoutBestUsd)} · best trade ${r.tail.bestMultiple ?? '—'}x its cost`,
|
|
97
|
+
` per entry ${r.tail.perEntry ? `${r.tail.perEntry.meanPct}% of the stake (bootstrap 95% ${r.tail.perEntry.lo}% to ${r.tail.perEntry.hi}%)` : '—'} · exit multiple P50 ${r.tail.multiples?.p50 ?? '—'}x · P90 ${r.tail.multiples?.p90 ?? '—'}x · max ${r.tail.multiples?.max ?? '—'}x`,
|
|
98
|
+
`max drawdown ${r.maxDrawdownPct}% · stuck (unsellable) ${r.stuck} · open at end ${r.openAtEnd} · candidates ${r.candidates} · sessions ${r.sessions} ($${r.sessionCostUsd})`,
|
|
99
|
+
' by exit rule:', rows(r.byExit),
|
|
100
|
+
' by profile:', rows(r.byProfile),
|
|
101
|
+
' by entry trigger:', rows(r.byTrigger),
|
|
102
|
+
` clamps fired: ${Object.entries(r.clamps).map(([k, n]) => `${k} ×${n}`).join(', ') || 'none'}`,
|
|
103
|
+
` entries refused: ${Object.entries(r.refusals).sort((a, b) => b[1] - a[1]).slice(0, 8).map(([k, n]) => `${k} ×${n}`).join(', ') || 'none'}`,
|
|
104
|
+
r.trades < SKILL_MIN_N ? ` NOTE: ${r.trades} trades is below n = ${SKILL_MIN_N}: read the counts, not the rates.` : '',
|
|
105
|
+
' limits of this data:', ...r.limits.map((l) => ` - ${l}`),
|
|
106
|
+
].filter(Boolean).join('\n');
|
|
107
|
+
}
|
|
108
|
+
// ───────────────────────────── held-out split + sweep ─────────────────────────────
|
|
109
|
+
/** Chronological split by each coin's first row: the newest `testFrac` of coins is TEST. */
|
|
110
|
+
export function splitTape(tape, testFrac = 0.3) {
|
|
111
|
+
const first = new Map();
|
|
112
|
+
for (const k of tape.ticks)
|
|
113
|
+
if (!first.has(k.mint))
|
|
114
|
+
first.set(k.mint, k.t);
|
|
115
|
+
const order = [...first.entries()].sort((a, b) => a[1] - b[1] || (a[0] < b[0] ? -1 : 1));
|
|
116
|
+
const nTest = Math.max(1, Math.round(order.length * testFrac));
|
|
117
|
+
const testSet = new Set(order.slice(order.length - nTest).map(([m]) => m));
|
|
118
|
+
const cutoff = order[order.length - nTest]?.[1] ?? 0;
|
|
119
|
+
const pick = (inTest) => ({
|
|
120
|
+
source: `${tape.source} [${inTest ? 'test' : 'train'}]`,
|
|
121
|
+
ticks: tape.ticks.filter((k) => testSet.has(k.mint) === inTest),
|
|
122
|
+
mints: tape.mints.filter((m) => testSet.has(m) === inTest),
|
|
123
|
+
});
|
|
124
|
+
return { train: pick(false), test: pick(true), cutoff };
|
|
125
|
+
}
|
|
126
|
+
export function withParams(s, params) {
|
|
127
|
+
const doc = JSON.parse(JSON.stringify({ ...s, hash: undefined, exits: { default: s.exits.default, profiles: Object.values(s.exits.profiles) } }));
|
|
128
|
+
delete doc.hash;
|
|
129
|
+
for (const [path, v] of Object.entries(params)) {
|
|
130
|
+
const parts = path.split('.');
|
|
131
|
+
let o = doc;
|
|
132
|
+
if (parts[0] === 'profile') {
|
|
133
|
+
o = doc.exits.profiles.find((p) => p.id === s.exits.default);
|
|
134
|
+
parts.shift();
|
|
135
|
+
}
|
|
136
|
+
for (const p of parts.slice(0, -1)) {
|
|
137
|
+
const next = o[p];
|
|
138
|
+
if (next === null || typeof next !== 'object')
|
|
139
|
+
throw new Error(`sweep path ${path}: ${p} is not an object`);
|
|
140
|
+
o = next;
|
|
141
|
+
}
|
|
142
|
+
o[parts[parts.length - 1]] = v;
|
|
143
|
+
}
|
|
144
|
+
const r = validateStrategy(doc);
|
|
145
|
+
if (!r.ok)
|
|
146
|
+
throw new Error(`sweep params ${JSON.stringify(params)} make the strategy invalid: ${r.errors.map((e) => `${e.path} ${e.msg}`).join('; ')}`);
|
|
147
|
+
return r.strategy;
|
|
148
|
+
}
|
|
149
|
+
const pickR = (r) => ({ trades: r.trades, netUsd: r.netUsd, maxDrawdownPct: r.maxDrawdownPct, winRate: r.winRate });
|
|
150
|
+
/** Seeded PRNG (mulberry32) so a random sweep is reproducible. */
|
|
151
|
+
export function rng(seed) {
|
|
152
|
+
let a = seed >>> 0;
|
|
153
|
+
return () => { a = (a + 0x6d2b79f5) >>> 0; let t = a; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
|
|
154
|
+
}
|
|
155
|
+
export function combos(grid, mode, max, seed = 1) {
|
|
156
|
+
const keys = Object.keys(grid);
|
|
157
|
+
let all = [{}];
|
|
158
|
+
for (const k of keys)
|
|
159
|
+
all = all.flatMap((c) => grid[k].map((v) => ({ ...c, [k]: v })));
|
|
160
|
+
if (mode === 'grid')
|
|
161
|
+
return all.slice(0, max);
|
|
162
|
+
const r = rng(seed);
|
|
163
|
+
const out = [];
|
|
164
|
+
const pool = [...all];
|
|
165
|
+
while (out.length < max && pool.length)
|
|
166
|
+
out.push(pool.splice(Math.floor(r() * pool.length), 1)[0]);
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
export async function sweep(s, tape, grid, o = {}) {
|
|
170
|
+
const { train, test, cutoff } = splitTape(tape, o.testFrac ?? 0.3);
|
|
171
|
+
const rows = [];
|
|
172
|
+
const invalid = [];
|
|
173
|
+
for (const params of combos(grid, o.mode ?? 'grid', o.max ?? 64, o.seed ?? 1)) {
|
|
174
|
+
let st;
|
|
175
|
+
// A combination the validator refuses (e.g. TP1 above TP2) is skipped and counted, never run.
|
|
176
|
+
try {
|
|
177
|
+
st = withParams(s, params);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
invalid.push({ params, error: e.message.replace(/^.*invalid: /, '') });
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
rows.push({ params, train: pickR((await backtest(st, train)).report) });
|
|
184
|
+
}
|
|
185
|
+
if (!rows.length)
|
|
186
|
+
throw new Error('every sweep combination is invalid');
|
|
187
|
+
const minN = o.minTrainTrades ?? 5;
|
|
188
|
+
const eligible = rows.filter((r) => r.train.trades >= minN);
|
|
189
|
+
const best = [...(eligible.length ? eligible : rows)].sort((a, b) => b.train.netUsd - a.train.netUsd || a.train.maxDrawdownPct - b.train.maxDrawdownPct)[0];
|
|
190
|
+
// TEST is touched exactly twice: the unchanged strategy and the train winner.
|
|
191
|
+
const baseline = { params: {}, train: pickR((await backtest(s, train)).report), test: pickR((await backtest(s, test)).report) };
|
|
192
|
+
best.test = pickR((await backtest(withParams(s, best.params), test)).report);
|
|
193
|
+
const verdict = sweepVerdict(best, baseline);
|
|
194
|
+
return {
|
|
195
|
+
split: { trainMints: train.mints.length, testMints: test.mints.length, cutoff: new Date(cutoff).toISOString() },
|
|
196
|
+
combos: rows.length, invalid, baseline, best, top: [...rows].sort((a, b2) => b2.train.netUsd - a.train.netUsd).slice(0, 5), verdict,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Verdict rule 6 (§3a): gain wording only when TRAIN and TEST both have >= SKILL_MIN_N trades, the held-out net
|
|
201
|
+
* is above 0, the winner beats the unchanged strategy on TEST, and train agrees. A loser that loses less is
|
|
202
|
+
* reported as exactly that, never as a gain.
|
|
203
|
+
*/
|
|
204
|
+
export function sweepVerdict(best, baseline) {
|
|
205
|
+
const t = best.test, b = baseline.test;
|
|
206
|
+
if (best.train.trades < SKILL_MIN_N || t.trades < SKILL_MIN_N)
|
|
207
|
+
return `too few trades (train ${best.train.trades}, held-out ${t.trades}; both need >= ${SKILL_MIN_N}): no tuning claim`;
|
|
208
|
+
if (t.netUsd <= 0) {
|
|
209
|
+
return t.netUsd > b.netUsd
|
|
210
|
+
? `no edge: the tuned strategy still loses on the held-out coins (${t.netUsd.toFixed(2)} USD; it only loses ${(t.netUsd - b.netUsd).toFixed(2)} USD less than unchanged): no tuning claim`
|
|
211
|
+
: `no edge: the held-out net is ${t.netUsd.toFixed(2)} USD (unchanged ${b.netUsd.toFixed(2)}): keep the strategy as is`;
|
|
212
|
+
}
|
|
213
|
+
if (t.netUsd > b.netUsd && best.train.netUsd > baseline.train.netUsd)
|
|
214
|
+
return `held-out gain ${(t.netUsd - b.netUsd).toFixed(2)} USD over the unchanged strategy, held-out net ${t.netUsd.toFixed(2)} USD (train and test agree)`;
|
|
215
|
+
return `no held-out gain (test ${t.netUsd.toFixed(2)} vs unchanged ${b.netUsd.toFixed(2)}): keep the strategy as is`;
|
|
216
|
+
}
|
|
217
|
+
export function renderSweep(r) {
|
|
218
|
+
const usd = (x) => `${x >= 0 ? '+' : '-'}$${Math.abs(x).toFixed(2)}`;
|
|
219
|
+
const row = (x) => `${JSON.stringify(x.params)} train ${x.train.trades} trades ${usd(x.train.netUsd)} dd ${x.train.maxDrawdownPct}%${x.test ? ` · held-out ${x.test.trades} trades ${usd(x.test.netUsd)} dd ${x.test.maxDrawdownPct}%` : ''}`;
|
|
220
|
+
return [
|
|
221
|
+
`SWEEP · PAPER · recorded data · split by first sighting: train ${r.split.trainMints} coins, held-out ${r.split.testMints} coins (newest, from ${r.split.cutoff})`,
|
|
222
|
+
`combinations run ${r.combos} · refused as invalid ${r.invalid.length}`,
|
|
223
|
+
` unchanged: ${row(r.baseline)}`,
|
|
224
|
+
` train best: ${row(r.best)}`,
|
|
225
|
+
' top 5 on train:', ...r.top.map((x) => ` ${row(x)}`),
|
|
226
|
+
`VERDICT: ${r.verdict}`,
|
|
227
|
+
' The held-out coins were touched twice (the unchanged strategy and the train winner); nothing was tuned on them.',
|
|
228
|
+
].join('\n');
|
|
229
|
+
}
|
|
230
|
+
// ───────────────────────────── tail statistics ─────────────────────────────
|
|
231
|
+
export function quantile(sorted, q) {
|
|
232
|
+
if (!sorted.length)
|
|
233
|
+
return NaN;
|
|
234
|
+
const i = (sorted.length - 1) * q;
|
|
235
|
+
const lo = Math.floor(i), hi = Math.ceil(i);
|
|
236
|
+
return sorted[lo] + (sorted[hi] - sorted[lo]) * (i - lo);
|
|
237
|
+
}
|
|
238
|
+
/** Mean with a seeded percentile-bootstrap 95% interval (deterministic: same trades → same interval). */
|
|
239
|
+
export function bootstrapMean(xs, resamples = 2000, seed = 1) {
|
|
240
|
+
if (!xs.length)
|
|
241
|
+
return null;
|
|
242
|
+
const mean = xs.reduce((a, b) => a + b, 0) / xs.length;
|
|
243
|
+
const r = rng(seed);
|
|
244
|
+
const means = [];
|
|
245
|
+
for (let b = 0; b < resamples; b++) {
|
|
246
|
+
let sum = 0;
|
|
247
|
+
for (let i = 0; i < xs.length; i++)
|
|
248
|
+
sum += xs[Math.floor(r() * xs.length)];
|
|
249
|
+
means.push(sum / xs.length);
|
|
250
|
+
}
|
|
251
|
+
means.sort((a, b) => a - b);
|
|
252
|
+
return { meanPct: round(mean), lo: round(quantile(means, 0.025)), hi: round(quantile(means, 0.975)) };
|
|
253
|
+
}
|
|
254
|
+
// ───────────────────────────── every-candidate evaluation copy ─────────────────────────────
|
|
255
|
+
/**
|
|
256
|
+
* The every-candidate copy (§3a run b): each candidate is entered at a fixed unit size with the book caps lifted
|
|
257
|
+
* (exposure, concurrency, day count, day-loss halt, drawdown size-down and halt, loss-streak cooldown), on a
|
|
258
|
+
* book large enough that cash never binds. The strategy's own entry rules, per-coin rules, exit profiles and the
|
|
259
|
+
* fill model are unchanged, so every entry is an independent sample and the result does not depend on which
|
|
260
|
+
* candidates happened to arrive first. An evaluation copy, never a strategy to run.
|
|
261
|
+
*/
|
|
262
|
+
export function everyCandidate(s, unitUsd = 20) {
|
|
263
|
+
const doc = JSON.parse(JSON.stringify({ ...s, hash: undefined, exits: { default: s.exits.default, profiles: Object.values(s.exits.profiles) } }));
|
|
264
|
+
doc.id = `${s.id.slice(0, 21)}-every`;
|
|
265
|
+
doc.name = 'Every candidate (evaluation copy)';
|
|
266
|
+
doc.description = `Evaluation copy of ${s.id}: every candidate entered at a fixed $${unitUsd} with the book caps lifted, so each entry is an independent sample.`;
|
|
267
|
+
doc.bankroll = { ...doc.bankroll, startUsd: 100_000 };
|
|
268
|
+
doc.sizing = { ...doc.sizing, mode: 'fixed', fixedUsd: unitUsd, maxUsd: unitUsd, minUsd: Math.min(doc.sizing.minUsd, unitUsd), ladder: undefined };
|
|
269
|
+
delete doc.sizing.pct;
|
|
270
|
+
delete doc.sizing.riskPct;
|
|
271
|
+
delete doc.sizing.confidence;
|
|
272
|
+
delete doc.sizing.ladder;
|
|
273
|
+
const r = doc.risk;
|
|
274
|
+
doc.risk = {
|
|
275
|
+
...r,
|
|
276
|
+
dailyLossHaltPct: 100,
|
|
277
|
+
drawdown: { sizeDownAtPct: 99, sizeDownMult: 1, recoverBelowPct: 0, haltAtPct: 100 },
|
|
278
|
+
maxConcurrent: 20, maxExposurePct: 100, maxEntriesPerDay: 500,
|
|
279
|
+
lossStreak: { losses: 50, cooldownMin: 0 },
|
|
280
|
+
...(r.recoveredBags ? { recoveredBags: { ...r.recoveredBags, maxBags: 50 } } : {}),
|
|
281
|
+
};
|
|
282
|
+
const v = validateStrategy(doc);
|
|
283
|
+
if (!v.ok)
|
|
284
|
+
throw new Error(`every-candidate copy of ${s.id} is invalid: ${v.errors.map((e) => `${e.path} ${e.msg}`).join('; ')}`);
|
|
285
|
+
return v.strategy;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Runner recall (§3a item 3). A coin is in the window while its age is inside the strategy's entry age range and
|
|
289
|
+
* its liquidity is at least the default profile's fit.minLiquidityUsd. Its peak is the best price at that
|
|
290
|
+
* liquidity from the first window row on, divided by the first window row's price. For each threshold: how many
|
|
291
|
+
* such coins ran >= Nx, how many the entry rules passed on some window row, which rule refused the others (a coin
|
|
292
|
+
* counts once per rule) and which exit closed each one the run entered.
|
|
293
|
+
*/
|
|
294
|
+
export function runnerRecall(s, tape, run, thresholds) {
|
|
295
|
+
const rules = new EntryRules(s);
|
|
296
|
+
const minLiq = s.exits.profiles[s.exits.default].fit.minLiquidityUsd;
|
|
297
|
+
const { min, max } = s.entry.ageHours;
|
|
298
|
+
const byMint = new Map();
|
|
299
|
+
for (const k of tape.ticks) {
|
|
300
|
+
const a = byMint.get(k.mint);
|
|
301
|
+
if (a)
|
|
302
|
+
a.push(k);
|
|
303
|
+
else
|
|
304
|
+
byMint.set(k.mint, [k]);
|
|
305
|
+
}
|
|
306
|
+
const trades = new Map();
|
|
307
|
+
for (const t of run.trades)
|
|
308
|
+
if (!trades.has(t.mint))
|
|
309
|
+
trades.set(t.mint, { exit: t.exitReason, pnlPct: t.pnlPct, sells: t.fills.slice(1).map((f) => `${f.reason} ${(f.price / t.fills[0].price).toFixed(2)}x`) });
|
|
310
|
+
for (const p of run.openAtEnd)
|
|
311
|
+
if (!trades.has(p.mint))
|
|
312
|
+
trades.set(p.mint, { exit: p.stuck ? 'end-stuck' : 'end-of-tape', pnlPct: null, sells: [] });
|
|
313
|
+
const coins = [];
|
|
314
|
+
for (const [mint, ks] of byMint) {
|
|
315
|
+
const win = ks.filter((k) => k.kind === 'live' && k.createdAt !== null && k.price && (k.liq ?? 0) >= minLiq && (k.t - k.createdAt) / 3600e3 >= min && (max === null || (k.t - k.createdAt) / 3600e3 <= max));
|
|
316
|
+
if (!win.length)
|
|
317
|
+
continue;
|
|
318
|
+
const p0 = win[0].price;
|
|
319
|
+
const peak = Math.max(...ks.filter((k) => k.t >= win[0].t && (k.liq ?? 0) >= minLiq && k.price).map((k) => k.price));
|
|
320
|
+
const res = win.map((k) => rules.screen(k));
|
|
321
|
+
const refusedBy = {};
|
|
322
|
+
for (const r of res)
|
|
323
|
+
if (!r.pass)
|
|
324
|
+
for (const f of r.failed)
|
|
325
|
+
refusedBy[f] = (refusedBy[f] ?? 0) + 1;
|
|
326
|
+
coins.push({ mint, symbol: ks[0].symbol, peakX: round(peak / p0, 1), windowRows: win.length, passed: res.some((r) => r.pass), refusedBy, trade: trades.get(mint) ?? null });
|
|
327
|
+
}
|
|
328
|
+
return thresholds.map((x) => {
|
|
329
|
+
const rs = coins.filter((c) => c.peakX >= x).sort((a, b) => b.peakX - a.peakX);
|
|
330
|
+
const refusedBy = {};
|
|
331
|
+
for (const c of rs)
|
|
332
|
+
if (!c.passed)
|
|
333
|
+
for (const f of Object.keys(c.refusedBy))
|
|
334
|
+
refusedBy[f] = (refusedBy[f] ?? 0) + 1;
|
|
335
|
+
const exits = {};
|
|
336
|
+
for (const c of rs)
|
|
337
|
+
if (c.trade)
|
|
338
|
+
exits[c.trade.exit] = (exits[c.trade.exit] ?? 0) + 1;
|
|
339
|
+
return { minX: x, runners: rs.length, passed: rs.filter((c) => c.passed).length, entered: rs.filter((c) => c.trade).length, closedGreen: rs.filter((c) => (c.trade?.pnlPct ?? 0) > 0).length, refusedBy, exits, coins: rs };
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
export function renderRecall(rs, s) {
|
|
343
|
+
const L = [];
|
|
344
|
+
const top = (o) => Object.entries(o).sort((a, b) => b[1] - a[1]).map(([k, n]) => `${k} ${n}`).join(', ') || 'none';
|
|
345
|
+
for (const r of rs) {
|
|
346
|
+
L.push(`RUNNER RECALL >= ${r.minX}x (${s.id}: coins in its entry window that later ran >= ${r.minX}x at sellable liquidity): ${r.runners} · entry rules passed ${r.passed} · entered ${r.entered} · closed green ${r.closedGreen}`);
|
|
347
|
+
L.push(` refused runners by rule (a coin counts once per rule): ${top(r.refusedBy)}`);
|
|
348
|
+
L.push(` how the entered runners closed: ${top(r.exits)}`);
|
|
349
|
+
for (const c of r.coins.slice(0, 25)) {
|
|
350
|
+
const short = `${c.mint.slice(0, 4)}…${c.mint.slice(-4)}`;
|
|
351
|
+
const why = c.passed ? 'PASSED ' : `refused ${Object.entries(c.refusedBy).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([f, n]) => `${f} ${n}/${c.windowRows}`).join(', ')}`;
|
|
352
|
+
const tr = c.trade ? ` · ${c.trade.sells.join(' → ') || '(no sell)'} · ${c.trade.pnlPct === null ? 'open at end' : `closed ${c.trade.pnlPct}% by ${c.trade.exit}`}` : '';
|
|
353
|
+
L.push(` ${short} ${String(c.peakX).padStart(6)}x window rows ${String(c.windowRows).padStart(3)} ${why}${tr}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return L.join('\n');
|
|
357
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// task-1537: the price basis in autotrade text (the runner's lines, the agents' DOSSIER / ENGINE notes, the ledger's
|
|
2
|
+
// fill JSON). Market cap by default, from the tick's own cap and price (the supply that source counts) or pump.fun's
|
|
3
|
+
// fixed 1B; no supply → the price and why. Display and wording only: the engine, exits and caps read prices.
|
|
4
|
+
import { DEFAULT_PRICE_BASIS, fmtCap, fmtUsdPrice, fmtX, knownSupply, withMcap } from '../../data/src/basis.js';
|
|
5
|
+
export { DEFAULT_PRICE_BASIS };
|
|
6
|
+
/** The supply hints one tick carries (the resolved cap when the task-1518 split rule picked one). */
|
|
7
|
+
export function tickHints(k) {
|
|
8
|
+
if (!k)
|
|
9
|
+
return {};
|
|
10
|
+
return { mint: k.mint, launchpad: k.launchpad, price: k.price, mcap: k.mcapResolved ?? k.mcap, via: 'tick' };
|
|
11
|
+
}
|
|
12
|
+
/** A level in the basis: `$69.7K mcap` / a raw price (price basis, unchanged) / `0.0000697 (price; supply unknown)`. */
|
|
13
|
+
export function levelText(basis, price, k) {
|
|
14
|
+
if (price === null || price === undefined || !Number.isFinite(price))
|
|
15
|
+
return '—';
|
|
16
|
+
if (basis !== 'mcap')
|
|
17
|
+
return String(price);
|
|
18
|
+
const sup = knownSupply(tickHints(k));
|
|
19
|
+
return sup ? `${fmtCap(price * sup.tokens)} mcap` : `${fmtUsdPrice(price)} (price; supply unknown)`;
|
|
20
|
+
}
|
|
21
|
+
/** A row of levels (history, ticks) in the basis: caps at the current supply, else raw prices. */
|
|
22
|
+
export function levelsText(basis, prices, k) {
|
|
23
|
+
const sup = basis === 'mcap' ? knownSupply(tickHints(k)) : null;
|
|
24
|
+
return prices.map((p) => (p === null || !Number.isFinite(p) ? '—' : sup ? fmtCap(p * sup.tokens) : String(p))).join(' ');
|
|
25
|
+
}
|
|
26
|
+
/** `x` multiple of the current level over the entry (same in either basis). */
|
|
27
|
+
export function multipleText(entry, now) { return fmtX(entry, now) ?? '—'; }
|
|
28
|
+
/** The ledger's fill JSON keeps BOTH: `price` / `avgPrice` and their caps `mcap` / `avgMcap` (null without a supply). */
|
|
29
|
+
export function fillMcaps(view, k) {
|
|
30
|
+
const h = tickHints(k);
|
|
31
|
+
return withMcap(view, { price: 'mcap', avgPrice: 'avgMcap' }, { mint: h.mint, launchpad: h.launchpad, mcap: h.mcap, price: h.price });
|
|
32
|
+
}
|