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,199 @@
|
|
|
1
|
+
// `dexterm autotrade backtest|sweep|extract` (task-1503 AT-1, AUTOTRADE-SPEC §3/§3a). PAPER, recorded data only:
|
|
2
|
+
// the recorder DB is opened read-only, fills are modelled, no network, no model, no keys. Mechanical entries
|
|
3
|
+
// (`auto`): the agent's judgement is what the runner card measures, so a backtest never spawns a session.
|
|
4
|
+
import { defaultDbPath } from '../../../data/src/index.js';
|
|
5
|
+
import { openHistoryReadOnly } from '../../../recorder/src/index.js';
|
|
6
|
+
import { backtest, everyCandidate, fmtIssue, loadTape, renderRecall, renderReport, renderSweep, renderYoloReplay, builtinTapePath, runnerRecall, saveTape, sweep, tapeFromDb, tapeFromDoc, yoloReplay, } from '../../../autotrade/src/index.js';
|
|
7
|
+
import { parseArgs, resolveStrategyArg, storeFor } from "./strategies.js";
|
|
8
|
+
const BOOL = ['json', 'every-candidate'];
|
|
9
|
+
function strategyFrom(pos, opts, io, usage, anyMode = false) {
|
|
10
|
+
const arg = typeof opts.strategy === 'string' ? opts.strategy : pos[0];
|
|
11
|
+
const r = resolveStrategyArg(arg, io);
|
|
12
|
+
if (!r) {
|
|
13
|
+
io.stderr.write(`usage: ${usage}\n`);
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
for (const w of r.warnings)
|
|
17
|
+
io.stderr.write(`warning ${fmtIssue(w)}\n`);
|
|
18
|
+
if (!r.ok) {
|
|
19
|
+
for (const e of r.errors)
|
|
20
|
+
io.stderr.write(`error ${fmtIssue(e)}\n`);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (r.strategy.mode !== 'paper' && !anyMode) {
|
|
24
|
+
io.stderr.write(`dexterm autotrade: ${r.strategy.id} is mode "${r.strategy.mode}": autotrade runs paper only\n`);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return r.strategy;
|
|
28
|
+
}
|
|
29
|
+
function numOpt(opts, k, dflt) {
|
|
30
|
+
const v = opts[k];
|
|
31
|
+
const n = typeof v === 'string' ? Number(v) : NaN;
|
|
32
|
+
return Number.isFinite(n) ? n : dflt;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* `dexterm autotrade yolo-replay <id|file>` (task-1532): a full-port paper replay of a strategy (a live-mode copy too:
|
|
36
|
+
* the replay always runs a paper copy) over recorded history, with the rugs, the loss spread and how often a run of
|
|
37
|
+
* entries zeroed the wallet. Read-only: recorded data, modelled fills, no network, no model, no keys.
|
|
38
|
+
*/
|
|
39
|
+
export async function yoloReplayCmd(argv, io) {
|
|
40
|
+
const { pos, opts } = parseArgs(argv, ['json']);
|
|
41
|
+
const usage = 'dexterm autotrade yolo-replay <id|file> [--tape FILE | --db PATH [--days N | --since T] [--until T]] [--seed N] [--json]';
|
|
42
|
+
const s = strategyFrom(pos, opts, io, usage, true);
|
|
43
|
+
if (!s)
|
|
44
|
+
return 2;
|
|
45
|
+
const tape = tapeFrom(opts, io);
|
|
46
|
+
if (!tape)
|
|
47
|
+
return 2;
|
|
48
|
+
const r = await yoloReplay(s, tape, { seed: numOpt(opts, 'seed', 1) });
|
|
49
|
+
io.stdout.write(opts.json ? JSON.stringify(r, null, 2) + '\n' : renderYoloReplay(r) + '\n');
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
/** `--since/--until`: epoch ms or an ISO time (pins a backtest to the exact tape window of an earlier run). */
|
|
53
|
+
function timeOpt(v) {
|
|
54
|
+
if (typeof v !== 'string')
|
|
55
|
+
return undefined;
|
|
56
|
+
const n = /^\d+$/.test(v) ? Number(v) : Date.parse(v);
|
|
57
|
+
return Number.isFinite(n) ? n : undefined;
|
|
58
|
+
}
|
|
59
|
+
/** The recorder DB, read-only; null when it is missing, in memory, or not a recorder DB. */
|
|
60
|
+
function openDb(path) {
|
|
61
|
+
if (path === ':memory:')
|
|
62
|
+
return null;
|
|
63
|
+
try {
|
|
64
|
+
return openHistoryReadOnly(path);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** A tape from --tape FILE, else read-only from the recorder DB (--db, DEXTERM_DB_PATH, the default path). */
|
|
71
|
+
export function tapeFrom(opts, io) {
|
|
72
|
+
if (typeof opts.tape === 'string') {
|
|
73
|
+
try {
|
|
74
|
+
return loadTape(builtinTapePath(opts.tape));
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
io.stderr.write(`dexterm autotrade: ${e.message}\n`);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const path = typeof opts.db === 'string' ? opts.db : (io.env.DEXTERM_DB_PATH || defaultDbPath(io.env));
|
|
82
|
+
const db = openDb(path);
|
|
83
|
+
if (!db) {
|
|
84
|
+
io.stderr.write(`dexterm autotrade: no recorded history in ${path} (run the app with the recorder on first, or pass --tape FILE)\n`);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const days = numOpt(opts, 'days', 0);
|
|
89
|
+
return tapeFromDoc(tapeFromDb(db, {
|
|
90
|
+
since: timeOpt(opts.since) ?? (days > 0 ? Date.now() - days * 86_400_000 : undefined),
|
|
91
|
+
until: timeOpt(opts.until),
|
|
92
|
+
minPeakLiqUsd: numOpt(opts, 'min-peak-liq', 20_000),
|
|
93
|
+
spanHours: numOpt(opts, 'span-hours', 80),
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
db.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function backtestCmd(argv, io) {
|
|
101
|
+
const { pos, opts } = parseArgs(argv, BOOL);
|
|
102
|
+
const usage = 'dexterm autotrade backtest <id|file> [--tape FILE | --db PATH] [--days N | --since T] [--until T] [--every-candidate [--unit-usd 20]] [--recall 5,10] [--json]';
|
|
103
|
+
const s0 = strategyFrom(pos, opts, io, usage);
|
|
104
|
+
if (!s0)
|
|
105
|
+
return 2;
|
|
106
|
+
const tape = tapeFrom(opts, io);
|
|
107
|
+
if (!tape)
|
|
108
|
+
return 2;
|
|
109
|
+
const s = opts['every-candidate'] ? everyCandidate(s0, numOpt(opts, 'unit-usd', 20)) : s0;
|
|
110
|
+
const { report, run } = await backtest(s, tape);
|
|
111
|
+
const recallAt = typeof opts.recall === 'string' ? opts.recall.split(',').map(Number).filter((x) => Number.isFinite(x) && x > 1) : [];
|
|
112
|
+
const recall = recallAt.length ? runnerRecall(s, tape, run, recallAt) : null;
|
|
113
|
+
if (!opts['every-candidate']) {
|
|
114
|
+
try {
|
|
115
|
+
storeFor(io).recordBacktest({ hash: s.hash, t: Date.now(), trades: report.trades, netUsd: report.netUsd, netPct: report.netPct, maxDrawdownPct: report.maxDrawdownPct, tape: `${report.tape.mints} coins ${report.tape.from ?? ''}..${report.tape.to ?? ''}` });
|
|
116
|
+
}
|
|
117
|
+
catch { /* the line is a convenience for `list`; never fail a backtest on it */ }
|
|
118
|
+
}
|
|
119
|
+
if (opts.json) {
|
|
120
|
+
io.stdout.write(JSON.stringify(recall ? { ...report, recall } : report, null, 2) + '\n');
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
io.stdout.write(renderReport(report) + '\n');
|
|
124
|
+
if (opts['every-candidate'])
|
|
125
|
+
io.stdout.write(` every-candidate copy of ${s0.id}: each candidate entered at $${numOpt(opts, 'unit-usd', 20)} with the book caps lifted (an evaluation, not a strategy to run)\n`);
|
|
126
|
+
if (recall)
|
|
127
|
+
io.stdout.write(renderRecall(recall, s) + '\n');
|
|
128
|
+
io.stdout.write(' Paper only, recorded data, not financial advice. A backtest is not a forecast.\n');
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
export async function sweepCmd(argv, io) {
|
|
132
|
+
const { pos, opts } = parseArgs(argv, BOOL);
|
|
133
|
+
const usage = 'dexterm autotrade sweep <id|file> [--tape FILE | --db PATH] [--grid JSON] [--mode grid|random] [--max N] [--seed N] [--test-frac 0.3] [--json]';
|
|
134
|
+
const s0 = strategyFrom(pos, opts, io, usage);
|
|
135
|
+
if (!s0)
|
|
136
|
+
return 2;
|
|
137
|
+
let grid;
|
|
138
|
+
try {
|
|
139
|
+
grid = typeof opts.grid === 'string' ? JSON.parse(opts.grid) : defaultGrid(s0);
|
|
140
|
+
if (!grid || typeof grid !== 'object' || !Object.values(grid).every((v) => Array.isArray(v) && v.every((x) => typeof x === 'number')))
|
|
141
|
+
throw new Error('the grid maps parameter paths to arrays of numbers');
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
io.stderr.write(`dexterm autotrade: --grid: ${e.message}\n`);
|
|
145
|
+
return 2;
|
|
146
|
+
}
|
|
147
|
+
const tape = tapeFrom(opts, io);
|
|
148
|
+
if (!tape)
|
|
149
|
+
return 2;
|
|
150
|
+
const s = opts['every-candidate'] ? everyCandidate(s0, numOpt(opts, 'unit-usd', 20)) : s0;
|
|
151
|
+
let r;
|
|
152
|
+
try {
|
|
153
|
+
r = await sweep(s, tape, grid, { mode: opts.mode === 'random' ? 'random' : 'grid', max: numOpt(opts, 'max', 64), seed: numOpt(opts, 'seed', 1), testFrac: numOpt(opts, 'test-frac', 0.3) });
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
io.stderr.write(`dexterm autotrade: ${e.message}\n`);
|
|
157
|
+
return 2;
|
|
158
|
+
}
|
|
159
|
+
io.stdout.write(opts.json ? JSON.stringify(r, null, 2) + '\n' : renderSweep(r) + '\n Paper only, recorded data, not financial advice.\n');
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
/** A small default grid on the default exit profile's own knobs (only the ones the profile has). */
|
|
163
|
+
export function defaultGrid(s) {
|
|
164
|
+
const p = s.exits.profiles[s.exits.default];
|
|
165
|
+
const g = {};
|
|
166
|
+
if (p.trailingStop)
|
|
167
|
+
g['profile.trailingStop.trailPct'] = [15, 25, 35];
|
|
168
|
+
if (p.tpLadder.length)
|
|
169
|
+
g['profile.tpLadder.0.atPct'] = [30, 50, 80];
|
|
170
|
+
if (p.costRecovery)
|
|
171
|
+
g['profile.costRecovery.atPct'] = [60, 100, 150];
|
|
172
|
+
if (p.bagTrail)
|
|
173
|
+
g['profile.bagTrail.trailPct'] = [35, 55, 75];
|
|
174
|
+
if (s.sizing.mode === 'pct' && s.sizing.pct !== undefined)
|
|
175
|
+
g['sizing.pct'] = [s.sizing.pct / 2, s.sizing.pct].map((x) => Math.round(x * 10) / 10);
|
|
176
|
+
return g;
|
|
177
|
+
}
|
|
178
|
+
export function extractCmd(argv, io) {
|
|
179
|
+
const { opts } = parseArgs(argv);
|
|
180
|
+
if (typeof opts.out !== 'string') {
|
|
181
|
+
io.stderr.write('usage: dexterm autotrade extract --out FILE [--db PATH] [--max-mints N] [--min-peak-liq USD] [--span-hours H]\n');
|
|
182
|
+
return 2;
|
|
183
|
+
}
|
|
184
|
+
const path = typeof opts.db === 'string' ? opts.db : (io.env.DEXTERM_DB_PATH || defaultDbPath(io.env));
|
|
185
|
+
const db = openDb(path);
|
|
186
|
+
if (!db) {
|
|
187
|
+
io.stderr.write(`dexterm autotrade: no recorded history in ${path}\n`);
|
|
188
|
+
return 2;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
const doc = tapeFromDb(db, { maxMints: typeof opts['max-mints'] === 'string' ? Number(opts['max-mints']) : undefined, minPeakLiqUsd: numOpt(opts, 'min-peak-liq', 20_000), spanHours: numOpt(opts, 'span-hours', 80) });
|
|
192
|
+
saveTape(opts.out, doc);
|
|
193
|
+
io.stdout.write(`wrote ${opts.out}: ${doc.mints.length} coins, ${doc.mints.reduce((a, m) => a + m.rows.length, 0)} rows (recorded market data only)\n`);
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
db.close();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// `dexterm autotrade flatten` (task-1509 AT-7, AUTOTRADE-SPEC §6.3). PAPER ONLY.
|
|
2
|
+
//
|
|
3
|
+
// A stop (no new entries, sessions closed, orders dropped), then every paper position sells at its next tick; one
|
|
4
|
+
// that cannot sell (a pulled pool) stays stuck and is retried every tick. The runner ends once nothing is held. It
|
|
5
|
+
// asks for a y keypress on a real terminal first, or takes `--yes` (selling only lowers risk; nothing here can buy,
|
|
6
|
+
// arm a mandate or sign). Nothing here touches a wallet, a key or the signer.
|
|
7
|
+
import { dextermHome } from '../../../custom/src/index.js';
|
|
8
|
+
import { activeRunner } from '../../../autotrade/src/index.js';
|
|
9
|
+
import { parseArgs } from "./strategies.js";
|
|
10
|
+
import { sendControl, waitArg } from "./stop.js";
|
|
11
|
+
export const FLATTEN_USAGE = 'dexterm autotrade flatten [--strategy ID] [--yes] [--wait SEC] [--json]';
|
|
12
|
+
export async function flattenCmd(argv, io, over = {}) {
|
|
13
|
+
const { pos, opts } = parseArgs(argv, ['json', 'yes']);
|
|
14
|
+
if (pos.length) {
|
|
15
|
+
io.stderr.write(`usage: ${FLATTEN_USAGE}\n`);
|
|
16
|
+
return 2;
|
|
17
|
+
}
|
|
18
|
+
const home = dextermHome(io.env);
|
|
19
|
+
const member = typeof opts.strategy === 'string' ? opts.strategy : null;
|
|
20
|
+
const r = activeRunner(home);
|
|
21
|
+
if (!r) {
|
|
22
|
+
if (opts.json)
|
|
23
|
+
io.stdout.write(`${JSON.stringify({ requested: false, runId: null, why: 'no autotrade runner is running' })}\n`);
|
|
24
|
+
else
|
|
25
|
+
io.stdout.write('no autotrade runner is running; nothing to flatten.\n');
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
if (!opts.yes) {
|
|
29
|
+
const stdin = io.stdin;
|
|
30
|
+
if (!stdin || stdin.isTTY !== true) {
|
|
31
|
+
io.stderr.write('dexterm autotrade flatten: confirm with y on a real terminal, or pass --yes\n');
|
|
32
|
+
return 2;
|
|
33
|
+
}
|
|
34
|
+
io.stdout.write(member ? `flatten strategy ${member} only: stop it, then sell its paper positions at their next tick (the other strategies keep running)? y to confirm, any other key cancels… `
|
|
35
|
+
: `flatten run ${r.runId}: stop, then sell every paper position at its next tick (${r.positions} open)? y to confirm, any other key cancels… `);
|
|
36
|
+
const { ttyKeypress } = await import("../wallet-cmd.js");
|
|
37
|
+
const key = await ttyKeypress(stdin, over.keypressTimeoutMs ?? 30_000);
|
|
38
|
+
io.stdout.write('\n');
|
|
39
|
+
if (key !== 'y' && key !== 'Y') {
|
|
40
|
+
io.stdout.write('flatten cancelled; nothing was sold\n');
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// task-1529: one strategy of a parallel run; the others keep running.
|
|
45
|
+
if (member)
|
|
46
|
+
return (await import("./strategy-control.js")).memberStopCmd(io, member, 'flatten', opts);
|
|
47
|
+
const res = await sendControl(io, 'flatten', waitArg(opts.wait), r);
|
|
48
|
+
if ('error' in res) {
|
|
49
|
+
io.stderr.write(`dexterm autotrade flatten: ${res.error}\n`);
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
if (opts.json) {
|
|
53
|
+
io.stdout.write(`${JSON.stringify(res)}\n`);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
io.stdout.write(`flatten ${res.already ? 'was already requested' : 'requested'} for run ${r.runId} (pid ${r.pid})\n`);
|
|
57
|
+
if (!res.acknowledged)
|
|
58
|
+
io.stdout.write('the runner has not confirmed yet: it reads the request on its next tick. Watch the Q screen, or run this again.\n');
|
|
59
|
+
else if (res.ended)
|
|
60
|
+
io.stdout.write('✓ flat: the runner sold what it held and stopped\n');
|
|
61
|
+
else
|
|
62
|
+
io.stdout.write(`✓ flattening: no new entries, sessions closed; ${res.positions} paper position${res.positions === 1 ? '' : 's'} sell${res.positions === 1 ? 's' : ''} at the next tick (a stuck one is retried every tick)\n`);
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
// `dexterm autotrade run --all --paper` (task-1529): every strategy of autotrade.json at the same time, on ONE data
|
|
2
|
+
// feed. PAPER ONLY.
|
|
3
|
+
//
|
|
4
|
+
// The host around @dexterm/autotrade runFleet (fleet.ts): one process, one runner lock under the fleet's run id, one
|
|
5
|
+
// live data layer (or one paced tape), one shared dossier cache, one machine-load check and the global caps of
|
|
6
|
+
// autotrade.json; each strategy gets its own run id, ledger (autotrade/runs/<runId>.jsonl), simulated book (its own
|
|
7
|
+
// bankroll), breaker, sessions and spend budget.
|
|
8
|
+
//
|
|
9
|
+
// Files it writes, every heartbeat (10 s) and on change (throttled to 1 s):
|
|
10
|
+
// autotrade/runner-status.json the runner rendezvous, as an AGGREGATE of every strategy (book summed, positions /
|
|
11
|
+
// candidates / sessions tagged with their strategy), under the fleet's run id: the
|
|
12
|
+
// Q screen's runner view, `dexterm autotrade stop|flatten`, the monitor's dead-man and
|
|
13
|
+
// the MCP autotrade_status read it unchanged
|
|
14
|
+
// autotrade/fleet-status.json each strategy's state, book, spend and stats, the global caps and their use today
|
|
15
|
+
// autotrade/runs/<id>.summary.json each strategy's run summary and the fleet's (for `dexterm autotrade audit`)
|
|
16
|
+
//
|
|
17
|
+
// Paper accounts (task-1538): autotrade.json `accounts` are shared simulated wallets; a strategy with `account` trades
|
|
18
|
+
// from that wallet's cash (and one strategy can run on several accounts). Printed as their own table at the end.
|
|
19
|
+
//
|
|
20
|
+
// Switches: the panic file, `dexterm autotrade stop | flatten` and the Q screen's `!` / `f` / `p` act on EVERY strategy
|
|
21
|
+
// (the fleet's RunnerSafety); `dexterm autotrade stop | flatten | pause | resume --strategy ID` and `start ID` act on one
|
|
22
|
+
// (fleet-control.ts). Nothing here arms a mandate, signs, holds a key or touches a wallet; `--live` and `mode: live`
|
|
23
|
+
// are refused (task-1571: dexterm places no live order).
|
|
24
|
+
import { appendFileSync } from 'node:fs';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import { dextermHome } from '../../../custom/src/index.js';
|
|
27
|
+
import { KillSwitch, createDataLayer, defaultDbPath, resolveRpcUrl } from '../../../data/src/index.js';
|
|
28
|
+
import { FleetGuard, HEARTBEAT_MS, accountTable, LiveSource, RUNNER_RENDEZVOUS_SCHEMA, RUN_SUMMARY_SCHEMA, RunnerSafety, acquireRunLock, bankrollUsd, claudeSessionFactory, clearControl, clearMemberControls, clearStaleControl, compareRows, compareTable, controlPath, fileLedger, fleetStatusPath, fleetTotals, loadFleetConfig, loadRunnerConfig, loadTape, memberStrategy, newRunId, prepareAccount, releaseRunLock, renderDossierRecord, runFleet, runFiles, runnerRendezvousPath, runnerSystemPrompt, sharedDossier, stubSession, writeJsonAtomic, writeRendezvous, } from '../../../autotrade/src/index.js';
|
|
29
|
+
import { parseArgs, resolveStrategyArg } from "./strategies.js";
|
|
30
|
+
import { larpVet } from "./run.js";
|
|
31
|
+
export const FLEET_RUN_USAGE = 'dexterm autotrade run --all --paper [--config FILE] [--replay TAPE [--ms-per-tick 2000]] [--sol-usd N] [--model M] [--max-minutes N] [--stub] [--raw] [--quiet]';
|
|
32
|
+
const BOOL = ['all', 'paper', 'live', 'stub', 'quiet', 'raw'];
|
|
33
|
+
const SOL_MINT = 'So11111111111111111111111111111111111111112';
|
|
34
|
+
function num(v, dflt) {
|
|
35
|
+
const n = typeof v === 'string' ? Number(v) : NaN;
|
|
36
|
+
return Number.isFinite(n) && n > 0 ? n : dflt;
|
|
37
|
+
}
|
|
38
|
+
const round2 = (v) => Math.round(v * 100) / 100;
|
|
39
|
+
/** The single-runner rendezvous shape, filled with every strategy together (the existing readers need no change). */
|
|
40
|
+
export function aggregateRendezvous(st, states, tails) {
|
|
41
|
+
const tot = fleetTotals(st);
|
|
42
|
+
const live = st.members.filter((m) => m.runId && m.state !== 'stopped' && m.state !== 'error' && m.state !== 'idle');
|
|
43
|
+
const runnerState = st.state === 'stopped' ? 'stopped' : st.state === 'halted' ? 'halted'
|
|
44
|
+
: live.length && live.every((m) => m.state === 'paused' || m.state === 'halted') ? 'paused' : 'running';
|
|
45
|
+
const each = (pick, m) => (states.get(m) ? pick(states.get(m)) : []);
|
|
46
|
+
const ids = st.members.map((m) => m.id);
|
|
47
|
+
return {
|
|
48
|
+
schema: RUNNER_RENDEZVOUS_SCHEMA, pid: st.pid, mode: 'paper', runId: st.fleetId, startedAt: st.startedAt, ledgerPath: st.ledgerPath,
|
|
49
|
+
state: runnerState, haltReason: st.haltReason ?? (st.global.blocked ? `fleet: ${st.global.blocked}` : st.global.load.paused ? 'fleet-load-high' : null),
|
|
50
|
+
strategy: { id: 'parallel', version: `${st.members.length} strategies`, hash: st.fleetId.slice(-4) },
|
|
51
|
+
book: { startUsd: tot.startUsd, equityUsd: tot.equityUsd, cashUsd: tot.cashUsd, realizedTodayUsd: round2(st.members.reduce((a, m) => a + (m.book?.realizedTodayUsd ?? 0), 0)) },
|
|
52
|
+
spend: {
|
|
53
|
+
todayUsd: st.global.today.modelUsd, dayCapUsd: st.global.caps.modelUsdPerDay, turnsToday: st.global.today.turns,
|
|
54
|
+
candidateSessionsToday: st.global.today.candidateSessions, positionSessionsToday: st.global.today.positionSessions, allowance: st.global.allowance,
|
|
55
|
+
},
|
|
56
|
+
candidates: ids.flatMap((m) => each((s) => s.candidates, m).map((c) => ({ ...c, strategy: m }))),
|
|
57
|
+
sessions: ids.flatMap((m) => each((s) => s.sessions, m).map((x) => ({ ...x, id: `${m}:${x.id}`, strategy: m }))),
|
|
58
|
+
positions: ids.flatMap((m) => each((s) => s.positions, m).map((p) => ({ ...p, strategy: m }))),
|
|
59
|
+
ledgerTail: tails.slice(-12).map((e, i) => ({ seq: i + 1, t: e.t, kind: e.kind, text: `[${e.member}] ${e.text}` })),
|
|
60
|
+
ledgerHead: '', events: st.members.reduce((a, m) => a + (states.get(m.id)?.events ?? 0), 0),
|
|
61
|
+
fleet: { fleetId: st.fleetId, strategies: st.members.length, statusPath: '' },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export async function fleetRunCmd(argv, io, over = {}) {
|
|
65
|
+
const { pos, opts } = parseArgs(argv, BOOL);
|
|
66
|
+
if (opts.live) {
|
|
67
|
+
io.stderr.write('dexterm autotrade run --all: live money is not available; parallel strategies run on paper only (--paper)\n');
|
|
68
|
+
return 2;
|
|
69
|
+
}
|
|
70
|
+
if (!opts.paper) {
|
|
71
|
+
io.stderr.write(`dexterm autotrade run --all: pass --paper (autotrade runs on paper only)\nusage: ${FLEET_RUN_USAGE}\n`);
|
|
72
|
+
return 2;
|
|
73
|
+
}
|
|
74
|
+
if (pos.length || typeof opts.strategy === 'string') {
|
|
75
|
+
io.stderr.write(`dexterm autotrade run --all runs every strategy listed in autotrade.json; name no strategy here\nusage: ${FLEET_RUN_USAGE}\n`);
|
|
76
|
+
return 2;
|
|
77
|
+
}
|
|
78
|
+
const home = dextermHome(io.env);
|
|
79
|
+
const kill = over.killSwitch ?? new KillSwitch();
|
|
80
|
+
const panic = kill.engaged();
|
|
81
|
+
if (panic) {
|
|
82
|
+
io.stderr.write(`dexterm autotrade run --all: the panic switch is engaged (${panic.reason}); nothing starts until you run \`dexterm wallet resume\` in a terminal\n`);
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
const rc = loadRunnerConfig(home);
|
|
86
|
+
if (!rc.ok) {
|
|
87
|
+
io.stderr.write(`dexterm autotrade run --all: ${rc.path} is invalid:\n${rc.errors.map((e) => ` ${e}`).join('\n')}\n`);
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
const runnerCfg = rc.config;
|
|
91
|
+
const fc = loadFleetConfig(home, typeof opts.config === 'string' ? opts.config : undefined);
|
|
92
|
+
if (!fc.ok) {
|
|
93
|
+
io.stderr.write(`dexterm autotrade run --all: ${fc.path}:\n${fc.errors.map((e) => ` ${e}`).join('\n')}\n`);
|
|
94
|
+
if ('missing' in fc)
|
|
95
|
+
io.stderr.write(' example:\n { "schema": "dexterm-autotrade/1",\n "strategies": [ { "id": "moon-a", "preset": "moonbag", "bankroll": { "usd": 500 } },\n { "id": "scalp-b", "preset": "scalp", "bankroll": { "sol": 1 } } ],\n "global": { "maxConcurrentSessions": 3, "modelUsdPerDay": 10 } }\n shared paper accounts (many strategies on one simulated wallet, one strategy on many):\n { "schema": "dexterm-autotrade/1",\n "accounts": [ { "id": "main", "bankroll": { "usd": 1000 } }, { "id": "yolo", "bankroll": { "sol": 1 } } ],\n "strategies": [ { "id": "moon", "preset": "moonbag", "account": "main" },\n { "id": "scalp", "preset": "scalp", "account": ["main", "yolo"] } ] }\n');
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
const fleetCfg = fc.config;
|
|
99
|
+
const enabled = fleetCfg.strategies.filter((m) => m.enabled);
|
|
100
|
+
if (!enabled.length) {
|
|
101
|
+
io.stderr.write(`dexterm autotrade run --all: every strategy in ${fc.path} is "enabled": false; nothing to run\n`);
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
const replay = typeof opts.replay === 'string' ? opts.replay : null;
|
|
105
|
+
const quiet = !!opts.quiet;
|
|
106
|
+
const say = (s) => { if (!quiet)
|
|
107
|
+
io.stdout.write(`${s}\n`); };
|
|
108
|
+
// The market first (a SOL bankroll needs the SOL price before the books are built).
|
|
109
|
+
let tape;
|
|
110
|
+
let data = null;
|
|
111
|
+
if (replay) {
|
|
112
|
+
try {
|
|
113
|
+
tape = loadTape(replay);
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
io.stderr.write(`dexterm autotrade run --all: ${e.message}\n`);
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const rpc = resolveRpcUrl({ env: io.env });
|
|
122
|
+
data = createDataLayer({ dbPath: io.env.DEXTERM_DB_PATH || defaultDbPath(io.env), rpcUrl: rpc.url, rpcFallbackUrl: rpc.fallbackUrl });
|
|
123
|
+
}
|
|
124
|
+
const closeData = () => { try {
|
|
125
|
+
data?.close();
|
|
126
|
+
}
|
|
127
|
+
catch { /* closing */ } };
|
|
128
|
+
let solUsd = typeof opts['sol-usd'] === 'string' ? num(opts['sol-usd'], NaN) : fleetCfg.solUsd ?? null;
|
|
129
|
+
if (Number.isNaN(solUsd)) {
|
|
130
|
+
io.stderr.write('dexterm autotrade run --all: --sol-usd needs a positive number\n');
|
|
131
|
+
closeData();
|
|
132
|
+
return 2;
|
|
133
|
+
}
|
|
134
|
+
if (solUsd === null && data && (fleetCfg.strategies.some((m) => m.bankroll && 'sol' in m.bankroll) || (fleetCfg.accounts ?? []).some((a) => 'sol' in a.bankroll))) {
|
|
135
|
+
try {
|
|
136
|
+
const p = (await data.dexscreener.tokens([SOL_MINT])).get(SOL_MINT)?.pair.priceUsd ?? null;
|
|
137
|
+
solUsd = p && p > 1 && p < 100_000 ? p : null;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
solUsd = null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Every strategy resolves, or nothing starts (fail closed: a typo never leaves one strategy silently out).
|
|
144
|
+
const members = [];
|
|
145
|
+
const problems = [];
|
|
146
|
+
// Shared paper accounts (task-1538): each one's starting balance in USD (a SOL balance at the SOL price).
|
|
147
|
+
const accounts = [];
|
|
148
|
+
for (const a of fleetCfg.accounts ?? []) {
|
|
149
|
+
const b = bankrollUsd(a.bankroll, solUsd);
|
|
150
|
+
if (b.error || b.usd === null) {
|
|
151
|
+
problems.push(`paper account ${a.id}: ${b.error ?? 'no balance'}`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
accounts.push({ id: a.id, startUsd: b.usd, ...('sol' in a.bankroll ? { sol: a.bankroll.sol, solUsd: solUsd } : {}) });
|
|
155
|
+
}
|
|
156
|
+
const accountOf = new Map(accounts.map((a) => [a.id, a]));
|
|
157
|
+
for (const m of fleetCfg.strategies) {
|
|
158
|
+
const r = resolveStrategyArg(m.preset ?? m.file, io);
|
|
159
|
+
if (!r || !r.ok) {
|
|
160
|
+
problems.push(`${m.id}: ${r ? r.errors.map((e) => `${e.path || '/'} ${e.msg}`).join('; ') : 'no strategy'}`);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (r.strategy.mode !== 'paper') {
|
|
164
|
+
problems.push(`${m.id}: ${r.strategy.id} is mode "${r.strategy.mode}"; the runner refuses anything but paper`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const acct = m.account ? accountOf.get(m.account) : undefined;
|
|
168
|
+
if (m.account && !acct)
|
|
169
|
+
continue; // its account already failed above
|
|
170
|
+
const b = acct ? { usd: acct.startUsd } : bankrollUsd(m.bankroll, solUsd);
|
|
171
|
+
if ('error' in b && b.error) {
|
|
172
|
+
problems.push(`${m.id}: ${b.error}`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const { strategy, basedOn } = memberStrategy(r.strategy, m, b.usd);
|
|
176
|
+
const sol = acct ? (acct.sol !== undefined ? { sol: acct.sol, solUsd: acct.solUsd } : {}) : m.bankroll && 'sol' in m.bankroll ? { sol: m.bankroll.sol, solUsd: solUsd } : {};
|
|
177
|
+
members.push({ config: m, strategy, basedOn, bankroll: { usd: strategy.bankroll.startUsd, ...sol } });
|
|
178
|
+
}
|
|
179
|
+
if (problems.length) {
|
|
180
|
+
io.stderr.write(`dexterm autotrade run --all: nothing started:\n${problems.map((p) => ` ${p}`).join('\n')}\n`);
|
|
181
|
+
closeData();
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
const fleetId = newRunId('fleet');
|
|
185
|
+
const lock = acquireRunLock(home, fleetId);
|
|
186
|
+
if (!lock.ok) {
|
|
187
|
+
io.stderr.write(`dexterm autotrade run --all: another runner is active (pid ${lock.holder.pid}, run ${lock.holder.runId}); stop it first\n`);
|
|
188
|
+
closeData();
|
|
189
|
+
return 1;
|
|
190
|
+
}
|
|
191
|
+
if (lock.reclaimed)
|
|
192
|
+
say(`reclaimed a stale runner lock (pid ${lock.reclaimed.pid} is gone)`);
|
|
193
|
+
if (!clearStaleControl(home, fleetId)) {
|
|
194
|
+
io.stderr.write(`dexterm autotrade run --all: ${controlPath(home)} cannot be read; look at it and remove it, then start again\n`);
|
|
195
|
+
releaseRunLock(home, fleetId);
|
|
196
|
+
closeData();
|
|
197
|
+
return 1;
|
|
198
|
+
}
|
|
199
|
+
clearMemberControls(home, fleetId);
|
|
200
|
+
const { ledger: fleetLedger, files: fleetFiles } = fileLedger(home, fleetId);
|
|
201
|
+
const startedAt = Date.now();
|
|
202
|
+
const guard = new FleetGuard(fleetCfg.global, runnerCfg, { loadavg: over.loadavg });
|
|
203
|
+
const rawPath = opts.raw ? join(home, 'autotrade', 'runs', `${fleetId}.stream.jsonl`) : null;
|
|
204
|
+
const sessions = over.sessions ?? (opts.stub
|
|
205
|
+
? () => stubSession()
|
|
206
|
+
: (m, gate, log, clock) => claudeSessionFactory({
|
|
207
|
+
bin: typeof opts['claude-bin'] === 'string' ? opts['claude-bin'] : (io.env.DEXTERM_CLAUDE_BIN || 'claude'),
|
|
208
|
+
account: () => prepareAccount(runnerCfg.account, io.env),
|
|
209
|
+
model: typeof opts.model === 'string' ? opts.model : undefined,
|
|
210
|
+
systemPrompt: (s) => runnerSystemPrompt(s, { live: !replay }),
|
|
211
|
+
gate, log,
|
|
212
|
+
onRaw: rawPath ? (id, line) => appendFileSync(rawPath, `${JSON.stringify({ strategy: m.config.id, sessionId: id, line })}\n`, { mode: 0o600 }) : undefined,
|
|
213
|
+
now: clock,
|
|
214
|
+
}));
|
|
215
|
+
let live = null;
|
|
216
|
+
let source;
|
|
217
|
+
let dossier;
|
|
218
|
+
let vet;
|
|
219
|
+
if (data) {
|
|
220
|
+
live = new LiveSource({ store: data.store, dexscreener: data.dexscreener }, { onLog: (l) => fleetLedger.add(Date.now(), 'source_error', { error: l.slice(0, 300) }) });
|
|
221
|
+
const ls = live;
|
|
222
|
+
source = { name: 'live keyless data layer (Store + DexScreener tokens/v1 fast lane), shared by every strategy', ticks: ls.ticks(), startT: startedAt, setFocus: (marks) => { ls.setFocus(marks); writeFocus(home, marks); } };
|
|
223
|
+
// One dossier record per coin per 30 s, whichever strategy asks; each strategy renders its own text.
|
|
224
|
+
const { McpTools } = await import('../../../mcp/src/index.js');
|
|
225
|
+
const mcp = new McpTools(data, () => ({ screen: 'autotrade-runner', actions: { mints: [], feeds: [], sortKeys: [] } }));
|
|
226
|
+
const shared = sharedDossier(async (mint) => {
|
|
227
|
+
try {
|
|
228
|
+
return await mcp.call('get_dossier', { mint, interval: '5m' });
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
return { availability: { present: [], missing: [`dossier: ${e.message}`], knownUnavailable: [] } };
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
dossier = () => async (k, h, matched, b) => renderDossierRecord(await shared.get(k.mint), { mint: k.mint, symbol: k.symbol, matched }, k, h, b);
|
|
235
|
+
// A member's LARP gate (entry.safety.larp, the yolo preset's) on live data, as `autotrade run` has it.
|
|
236
|
+
vet = fleetLarpVet((mint) => mcp.call('larp_check', { mint }));
|
|
237
|
+
}
|
|
238
|
+
// Heartbeat: the aggregate rendezvous, the fleet status, every summary.
|
|
239
|
+
const states = new Map();
|
|
240
|
+
const runOf = new Map();
|
|
241
|
+
const tails = [];
|
|
242
|
+
let lastStatus = null;
|
|
243
|
+
let lastWrite = 0;
|
|
244
|
+
let lastBeatOk = startedAt;
|
|
245
|
+
let beatFailing = false;
|
|
246
|
+
const writeAll = (force = false) => {
|
|
247
|
+
const st = lastStatus;
|
|
248
|
+
if (!st)
|
|
249
|
+
return;
|
|
250
|
+
const now = Date.now();
|
|
251
|
+
if (!force && now - lastWrite < 1000)
|
|
252
|
+
return;
|
|
253
|
+
lastWrite = now;
|
|
254
|
+
try {
|
|
255
|
+
const agg = aggregateRendezvous(st, states, tails);
|
|
256
|
+
agg.fleet.statusPath = fleetStatusPath(home);
|
|
257
|
+
writeRendezvous(home, { ...agg, heartbeatAt: now });
|
|
258
|
+
writeJsonAtomic(fleetStatusPath(home), { ...st, heartbeatAt: now });
|
|
259
|
+
writeJsonAtomic(fleetFiles.summary, { schema: RUN_SUMMARY_SCHEMA, runId: fleetId, strategy: { id: 'parallel', version: `${st.members.length} strategies`, hash: '' }, startedAt, updatedAt: now, state: st.state, book: agg.book, spend: agg.spend, events: fleetLedger.events.length, ledgerHead: fleetLedger.head(), source: st.source });
|
|
260
|
+
for (const m of st.members) {
|
|
261
|
+
const s = states.get(m.id);
|
|
262
|
+
const runId = runOf.get(m.id);
|
|
263
|
+
if (!s || !runId)
|
|
264
|
+
continue;
|
|
265
|
+
writeJsonAtomic(runFiles(home, runId).summary, { schema: RUN_SUMMARY_SCHEMA, runId, strategy: s.strategy, startedAt: m.startedAt, updatedAt: now, state: s.state, book: s.book, spend: s.spend, events: s.events, ledgerHead: s.ledgerHead, source: st.source, fleetId });
|
|
266
|
+
}
|
|
267
|
+
lastBeatOk = now;
|
|
268
|
+
if (beatFailing) {
|
|
269
|
+
beatFailing = false;
|
|
270
|
+
fleetLedger.add(now, 'heartbeat_ok', {});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
if (!beatFailing) {
|
|
275
|
+
beatFailing = true;
|
|
276
|
+
fleetLedger.add(now, 'heartbeat_failed', { error: e.message.slice(0, 200) });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const beat = setInterval(() => writeAll(true), HEARTBEAT_MS);
|
|
281
|
+
const maxMin = num(opts['max-minutes'], 0);
|
|
282
|
+
const stopAt = maxMin ? startedAt + maxMin * 60_000 : Infinity;
|
|
283
|
+
let stopping = false;
|
|
284
|
+
const stop = (why) => { if (stopping)
|
|
285
|
+
return; stopping = true; fleetLedger.add(Date.now(), 'stop_requested', { why }); say(`stopping every strategy (${why}): open sessions close, each paper book is kept in its ledger`); live?.stop(); };
|
|
286
|
+
const onSig = () => stop('signal');
|
|
287
|
+
process.once('SIGINT', onSig);
|
|
288
|
+
process.once('SIGTERM', onSig);
|
|
289
|
+
const safety = new RunnerSafety({ home, runId: fleetId, killSwitch: kill, heartbeatAgeMs: () => (beatFailing ? Date.now() - lastBeatOk : 0) });
|
|
290
|
+
say(`dexterm autotrade run --all · PAPER · ${members.length} strategies at once (${enabled.length} enabled) · ${replay ? `replay ${replay}` : 'live keyless data, one feed for all'} · sessions ${opts.stub ? 'stub' : `claude (${runnerCfg.account.kind}${typeof opts.model === 'string' ? `, ${opts.model}` : ''})`}`);
|
|
291
|
+
if (accounts.length)
|
|
292
|
+
say(`paper accounts: ${accounts.map((a) => `${a.id} $${a.startUsd}${a.sol !== undefined ? ` (${a.sol} SOL)` : ''} ← ${members.filter((m) => m.config.account === a.id).map((m) => m.config.id).join(', ') || 'no strategy'}`).join(' · ')}`);
|
|
293
|
+
// The single run's HIGH RISK line (task-1532), for every yolo-like member of the fleet (paper only here).
|
|
294
|
+
const risky = members.filter((m) => m.strategy.highRisk).map((m) => m.config.id);
|
|
295
|
+
if (risky.length)
|
|
296
|
+
say(`⚠ HIGH RISK strateg${risky.length === 1 ? 'y' : 'ies'} (${risky.join(', ')}): one position at a time, early entries, most of them lose. Experimental; can lose all funds; not financial advice.`);
|
|
297
|
+
say(`fleet ${fleetId} · global caps: ${guard.caps.maxConcurrentSessions} sessions at once, $${guard.caps.modelUsdPerDay}/day model spend, ${guard.caps.candidateSessionsPerDay} candidate sessions/day · ledger ${fleetFiles.ledger}`);
|
|
298
|
+
let code = 0;
|
|
299
|
+
try {
|
|
300
|
+
const res = await runFleet({
|
|
301
|
+
fleetId, home, members, tape, source, runner: runnerCfg, guard, fleetLedger, fleetLedgerPath: fleetFiles.ledger, ...(accounts.length ? { accounts } : {}),
|
|
302
|
+
pacing: replay ? { mode: 'paced', msPerTick: num(opts['ms-per-tick'], 2000) } : { mode: 'live' },
|
|
303
|
+
sessions, dossier, vet, newRunId: (id) => newRunId(id), verbose: true, onLine: say,
|
|
304
|
+
globalSafety: () => {
|
|
305
|
+
if (Date.now() >= stopAt)
|
|
306
|
+
stop('max-minutes');
|
|
307
|
+
const v = safety.step();
|
|
308
|
+
return stopping && !v.halt ? { ...v, halt: 'runner-stopped', detail: 'a signal or --max-minutes', endWhenFlat: true } : v;
|
|
309
|
+
},
|
|
310
|
+
hooks: {
|
|
311
|
+
onMemberEvent: (m, runId, e) => {
|
|
312
|
+
runOf.set(m, runId);
|
|
313
|
+
if (['candidate', 'fill', 'trade_closed', 'halt', 'refused', 'session_start', 'session_end', 'entries_paused', 'entries_resumed', 'stuck'].includes(e.kind)) {
|
|
314
|
+
tails.push({ t: e.t, kind: e.kind, member: m, text: tailLine(e) });
|
|
315
|
+
if (tails.length > 48)
|
|
316
|
+
tails.splice(0, tails.length - 24);
|
|
317
|
+
}
|
|
318
|
+
over.hooks?.onMemberEvent?.(m, runId, e);
|
|
319
|
+
},
|
|
320
|
+
onMemberState: (m, runId, s) => { runOf.set(m, runId); states.set(m, s); over.hooks?.onMemberState?.(m, runId, s); },
|
|
321
|
+
onFleetState: (st) => { lastStatus = st; writeAll(st.state === 'stopped'); over.hooks?.onFleetState?.(st); },
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
const rows = compareRows(res.status);
|
|
325
|
+
say('');
|
|
326
|
+
for (const l of compareTable(rows))
|
|
327
|
+
say(l);
|
|
328
|
+
const acctLines = accountTable(res.status);
|
|
329
|
+
if (acctLines.length) {
|
|
330
|
+
say('');
|
|
331
|
+
for (const l of acctLines)
|
|
332
|
+
say(l);
|
|
333
|
+
}
|
|
334
|
+
const tot = fleetTotals(res.status);
|
|
335
|
+
say(`done · ${members.length} strategies · together ${tot.pnlUsd >= 0 ? '+' : '-'}$${Math.abs(tot.pnlUsd).toFixed(2)} on $${tot.startUsd.toFixed(2)} (paper) · $${tot.spendUsd.toFixed(4)} model spend · fleet ledger head ${fleetLedger.head()}`);
|
|
336
|
+
}
|
|
337
|
+
catch (e) {
|
|
338
|
+
io.stderr.write(`dexterm autotrade run --all: ${e.message}\n`);
|
|
339
|
+
code = 1;
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
clearInterval(beat);
|
|
343
|
+
process.removeListener('SIGINT', onSig);
|
|
344
|
+
process.removeListener('SIGTERM', onSig);
|
|
345
|
+
if (lastStatus && lastStatus.state !== 'stopped')
|
|
346
|
+
lastStatus = { ...lastStatus, state: 'stopped' };
|
|
347
|
+
writeAll(true);
|
|
348
|
+
writeFocus(home, []);
|
|
349
|
+
live?.stop();
|
|
350
|
+
closeData();
|
|
351
|
+
clearControl(home, fleetId);
|
|
352
|
+
clearMemberControls(home, null);
|
|
353
|
+
releaseRunLock(home, fleetId);
|
|
354
|
+
}
|
|
355
|
+
return code;
|
|
356
|
+
}
|
|
357
|
+
/** One short line for the aggregate ledger tail (no message text). */
|
|
358
|
+
function tailLine(e) {
|
|
359
|
+
const v = (k) => (e[k] === undefined || e[k] === null ? '' : String(e[k]).slice(0, 40));
|
|
360
|
+
switch (e.kind) {
|
|
361
|
+
case 'candidate': return `candidate ${v('symbol')}`;
|
|
362
|
+
case 'fill': return `${v('side')} $${Number(e.usd ?? 0).toFixed(2)} ${v('reason')}`;
|
|
363
|
+
case 'trade_closed': {
|
|
364
|
+
const t = e.trade;
|
|
365
|
+
return `closed ${t?.symbol ?? ''} ${(t?.pnlUsd ?? 0) >= 0 ? '+' : '-'}$${Math.abs(t?.pnlUsd ?? 0).toFixed(2)} by ${t?.exitReason ?? ''}`;
|
|
366
|
+
}
|
|
367
|
+
case 'refused': return `refused ${v('rule')}`;
|
|
368
|
+
default: return [v('reason'), v('sessionId'), v('rule')].filter(Boolean).join(' ') || String(e.kind);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/** Candidate + held coins of every strategy for the recorder's tick lane (task-1504 focus file); best effort. */
|
|
372
|
+
function writeFocus(home, marks) {
|
|
373
|
+
void import('../../../recorder/src/index.js').then((m) => {
|
|
374
|
+
try {
|
|
375
|
+
m.writeFocusFile(m.focusFilePath(home), marks.map((x) => ({ mint: x.mint, role: x.role, until: Date.now() + 30 * 60_000 })), Date.now());
|
|
376
|
+
}
|
|
377
|
+
catch { /* the recorder is optional */ }
|
|
378
|
+
}).catch(() => { });
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* The fleet's LARP gate per member (entry.safety.larp, e.g. the yolo preset), on live data only, as `autotrade run` has
|
|
382
|
+
* it: without it a gated strategy would open candidates unchecked under `run --all`. One larp_check per coin for the
|
|
383
|
+
* whole fleet; each member applies its own maxScore / unknown, and a check that throws is not kept (the next asks again).
|
|
384
|
+
*/
|
|
385
|
+
export function fleetLarpVet(check) {
|
|
386
|
+
const byMint = new Map();
|
|
387
|
+
const shared = (mint) => {
|
|
388
|
+
let p = byMint.get(mint);
|
|
389
|
+
if (!p) {
|
|
390
|
+
p = check(mint);
|
|
391
|
+
byMint.set(mint, p);
|
|
392
|
+
p.catch(() => byMint.delete(mint));
|
|
393
|
+
}
|
|
394
|
+
return p;
|
|
395
|
+
};
|
|
396
|
+
return (m) => (m.strategy.entry.safety.larp ? larpVet(m.strategy.entry.safety.larp, shared) : undefined);
|
|
397
|
+
}
|