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,79 @@
|
|
|
1
|
+
// `dexterm autotrade requests list|run|dismiss` (task-1530): the user's side of the MCP `backtest_request` tool. An
|
|
2
|
+
// agent can only FILE a request ($DEXTERM_HOME/autotrade/requests/<id>.json); nothing runs until the user runs it here.
|
|
3
|
+
// `run` is the ordinary `dexterm autotrade backtest <id> --days N` on the recorded history (paper, no network, no
|
|
4
|
+
// model), and its one-line result is written back so the agent reads it with `backtest_requests`.
|
|
5
|
+
import { BacktestRequests, isRequestId } from '../../../mcp/src/index.js';
|
|
6
|
+
import { dextermHomeDir } from '../../../data/src/trade/ledger/index.js';
|
|
7
|
+
import { backtestCmd } from "./backtest.js";
|
|
8
|
+
const USAGE = 'dexterm autotrade requests [list [--all] [--json] | run <br_id> | dismiss <br_id>]';
|
|
9
|
+
const when = (t) => new Date(t).toISOString().slice(0, 16).replace('T', ' ');
|
|
10
|
+
function line(r) {
|
|
11
|
+
const head = `${r.requestId} ${r.status.padEnd(9)} ${when(r.createdAt)} ${r.id}${r.version ? `@${r.version}` : ''} · ${r.days}d${r.everyCandidate ? ' · every candidate' : ''} · by ${r.actor}`;
|
|
12
|
+
return `${head}\n why: ${r.why}${r.result ? `\n result: ${r.result}` : ''}`;
|
|
13
|
+
}
|
|
14
|
+
export async function requestsCmd(argv, io) {
|
|
15
|
+
const files = new BacktestRequests(dextermHomeDir(io.env));
|
|
16
|
+
const sub = argv[0] ?? 'list';
|
|
17
|
+
if (sub === 'list') {
|
|
18
|
+
const all = argv.includes('--all');
|
|
19
|
+
const rows = files.list(all ? {} : { status: 'pending' });
|
|
20
|
+
if (argv.includes('--json')) {
|
|
21
|
+
io.stdout.write(`${JSON.stringify({ requests: rows })}\n`);
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
if (!rows.length) {
|
|
25
|
+
io.stdout.write(all ? 'no backtest requests\n' : 'no pending backtest requests (--all shows the resolved ones)\n');
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
for (const r of rows)
|
|
29
|
+
io.stdout.write(`${line(r)}\n`);
|
|
30
|
+
io.stdout.write('\nAn agent filed these; nothing ran. `dexterm autotrade requests run <id>` runs one (paper, recorded data); `dismiss <id>` drops it.\n');
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
if (sub !== 'run' && sub !== 'dismiss') {
|
|
34
|
+
io.stderr.write(`dexterm autotrade requests: unknown action ${JSON.stringify(sub)}\n ${USAGE}\n`);
|
|
35
|
+
return 2;
|
|
36
|
+
}
|
|
37
|
+
const id = argv[1];
|
|
38
|
+
if (!isRequestId(id)) {
|
|
39
|
+
io.stderr.write(`dexterm autotrade requests ${sub}: pass a request id (br_…, from \`dexterm autotrade requests\`)\n`);
|
|
40
|
+
return 2;
|
|
41
|
+
}
|
|
42
|
+
const r = files.get(id);
|
|
43
|
+
if (!r) {
|
|
44
|
+
io.stderr.write(`dexterm autotrade requests: no request ${id}\n`);
|
|
45
|
+
return 2;
|
|
46
|
+
}
|
|
47
|
+
if (r.status !== 'pending') {
|
|
48
|
+
io.stderr.write(`dexterm autotrade requests: ${id} is already ${r.status}\n`);
|
|
49
|
+
return 2;
|
|
50
|
+
}
|
|
51
|
+
if (sub === 'dismiss') {
|
|
52
|
+
files.resolve(id, 'dismissed', 'dismissed by the user');
|
|
53
|
+
io.stdout.write(`${id} dismissed\n`);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
// run: the ordinary backtest, its JSON captured for the one-line result, the human report shown after
|
|
57
|
+
let out = '';
|
|
58
|
+
let err = '';
|
|
59
|
+
const args = [r.id, '--days', String(r.days), ...(r.everyCandidate ? ['--every-candidate'] : []), '--json'];
|
|
60
|
+
const rc = await backtestCmd(args, { ...io, stdout: { write: (s) => { out += s; return true; } }, stderr: { write: (s) => { err += s; return true; } } });
|
|
61
|
+
if (rc !== 0) {
|
|
62
|
+
const why = err.trim().split('\n')[0] || `the backtest exited ${rc}`;
|
|
63
|
+
files.resolve(id, 'failed', why);
|
|
64
|
+
io.stderr.write(`${id} failed: ${why}\n`);
|
|
65
|
+
return rc;
|
|
66
|
+
}
|
|
67
|
+
let result;
|
|
68
|
+
try {
|
|
69
|
+
const rep = JSON.parse(out);
|
|
70
|
+
const wr = rep.winRate ? `${rep.winRate.value}% (95% ${rep.winRate.lo}–${rep.winRate.hi}%)` : '—';
|
|
71
|
+
result = `paper backtest ${r.days}d: ${rep.trades ?? 0} trades · win ${wr} · net $${(rep.netUsd ?? 0).toFixed(2)} (${rep.netPct ?? 0}%) · max drawdown ${rep.maxDrawdownPct ?? 0}%`;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
result = 'the backtest ran but its report was not readable JSON';
|
|
75
|
+
}
|
|
76
|
+
files.resolve(id, 'done', result);
|
|
77
|
+
io.stdout.write(`${id} done: ${result}\n (the agent reads this with backtest_requests; the full report: \`dexterm autotrade backtest ${r.id} --days ${r.days}\`)\n`);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
// `dexterm autotrade run <id|file> --paper` (task-1505 AT-3, AUTOTRADE-SPEC §4). PAPER ONLY.
|
|
2
|
+
//
|
|
3
|
+
// Headless, like `dexterm watch` and `dexterm record run`, one per machine (run.lock). Live: the keyless data layer's
|
|
4
|
+
// Store feeds MarketTicks (the screener pool every 10 s, candidate and held coins every 5 s). `--replay FILE` paces a
|
|
5
|
+
// recorded tape instead (no network for market data). Each candidate gets one session on the user's own `claude`
|
|
6
|
+
// (runner.json picks the account), clamped by the strategy; orders fill on the simulated paper book (the paper desk
|
|
7
|
+
// executor arrives with the monitor card). Every step goes to $DEXTERM_HOME/autotrade/runs/<runId>.jsonl, and the
|
|
8
|
+
// rendezvous $DEXTERM_HOME/autotrade/runner-status.json is rewritten every 10 s for the TUI's runner view.
|
|
9
|
+
//
|
|
10
|
+
// Nothing here arms a mandate, signs, holds a key or touches a wallet. task-1571: there is no `--live`: dexterm places
|
|
11
|
+
// no live order. Live trading happens in the Telegram trading bot of the user's choice.
|
|
12
|
+
//
|
|
13
|
+
// Telegram (task-1527): when `dexterm telegram setup` stored a token and a chat is paired, the user's own bot starts
|
|
14
|
+
// with the run (telegram-host.ts): notify, control through the same control file, and approve-to-trade on paper (a
|
|
15
|
+
// strategy with `entry.decide: "agent+approve"`, or approvals on). --no-telegram keeps it off; an agent+approve
|
|
16
|
+
// strategy then refuses to start, because no entry could ever be approved.
|
|
17
|
+
//
|
|
18
|
+
// Safety (task-1509, @dexterm/autotrade safety.ts): every step re-reads the panic file and the control file
|
|
19
|
+
// (`dexterm autotrade stop|flatten`, the Q screen's `!` / `f` / `p`); a run refuses to start while the panic file is
|
|
20
|
+
// engaged (resume is `dexterm wallet resume`, a keypress on a real terminal); entries pause while the heartbeat cannot
|
|
21
|
+
// be written. After a stop or flatten the runner keeps running the mechanical exits and ends once nothing is held.
|
|
22
|
+
import { appendFileSync } from 'node:fs';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { dextermHome, loadConfig } from '../../../custom/src/index.js';
|
|
25
|
+
import { pickBasis } from '../../../data/src/basis.js';
|
|
26
|
+
import { KillSwitch, createDataLayer, defaultDbPath, resolveRpcUrl } from '../../../data/src/index.js';
|
|
27
|
+
import { HEARTBEAT_MS, LiveSource, badgeOf, RUNNER_RENDEZVOUS_SCHEMA, RunnerSafety, SessionGate, acquireRunLock, clearControl, clearStaleControl, controlPath, claudeSessionFactory, effectiveCaps, fileLedger, loadRunnerConfig, loadTape, newRunId, prepareAccount, releaseRunLock, renderDossierRecord, runAutotrade, runnerRendezvousPath, runnerSystemPrompt, simExecutor, stubSession, writeJsonAtomic, writeRendezvous, RUN_SUMMARY_SCHEMA, builtinTapePath, } from '../../../autotrade/src/index.js';
|
|
28
|
+
import { assertAutotradeAllowed, ollamaSessionFactory } from '../../../autotrade/src/ollama-sessions.js';
|
|
29
|
+
import { parseArgs, resolveStrategyArg } from "./strategies.js";
|
|
30
|
+
import { startTelegramHost } from "./telegram-host.js";
|
|
31
|
+
const BOOL = ['paper', 'live', 'stub', 'verbose', 'quiet', 'raw', 'no-telegram'];
|
|
32
|
+
export const RUN_USAGE = 'dexterm autotrade run <id|file> --paper [--replay TAPE [--ms-per-tick 2000]] [--engine claude|ollama] [--model M] [--max-minutes N] [--stub] [--raw] [--quiet] [--no-telegram]';
|
|
33
|
+
/** task-1571: what `--live` says. dexterm holds no key and places no live order. */
|
|
34
|
+
export const NO_LIVE = 'dexterm autotrade run: there is no --live: dexterm runs strategies on paper only and never holds a key or signs; live trading happens in the Telegram trading bot of your choice (your own bot gets the alerts: dexterm telegram setup)';
|
|
35
|
+
function num(v, dflt) {
|
|
36
|
+
const n = typeof v === 'string' ? Number(v) : NaN;
|
|
37
|
+
return Number.isFinite(n) && n > 0 ? n : dflt;
|
|
38
|
+
}
|
|
39
|
+
export async function runCmd(argv, io, over = {}) {
|
|
40
|
+
// task-1529: `--all` runs every strategy of autotrade.json at once (fleet.ts).
|
|
41
|
+
if (argv.includes('--all'))
|
|
42
|
+
return (await import("./fleet.js")).fleetRunCmd(argv, io, { killSwitch: over.killSwitch });
|
|
43
|
+
const { pos, opts } = parseArgs(argv, BOOL);
|
|
44
|
+
if (opts.live || opts['devnet-rehearsal'] !== undefined || opts['signer-size'] !== undefined) {
|
|
45
|
+
io.stderr.write(`${NO_LIVE}\n`);
|
|
46
|
+
return 2;
|
|
47
|
+
}
|
|
48
|
+
if (!opts.paper) {
|
|
49
|
+
io.stderr.write(`dexterm autotrade run: pass --paper (autotrade runs on paper only)\nusage: ${RUN_USAGE}\n`);
|
|
50
|
+
return 2;
|
|
51
|
+
}
|
|
52
|
+
const arg = typeof opts.strategy === 'string' ? opts.strategy : pos[0];
|
|
53
|
+
const r = resolveStrategyArg(arg, io);
|
|
54
|
+
if (!r) {
|
|
55
|
+
io.stderr.write(`usage: ${RUN_USAGE}\n`);
|
|
56
|
+
return 2;
|
|
57
|
+
}
|
|
58
|
+
for (const w of r.warnings)
|
|
59
|
+
io.stderr.write(`warning ${w.path} ${w.msg}\n`);
|
|
60
|
+
if (!r.ok) {
|
|
61
|
+
for (const e of r.errors)
|
|
62
|
+
io.stderr.write(`error ${e.path} ${e.msg}\n`);
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
const strategy = r.strategy;
|
|
66
|
+
if (strategy.mode !== 'paper') {
|
|
67
|
+
io.stderr.write(`dexterm autotrade run: ${strategy.id} is mode "${strategy.mode}": the runner runs paper only (dexterm holds no key and places no live order)\n`);
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
const home = dextermHome(io.env);
|
|
71
|
+
// task-1537: the run's lines and the agents' messages speak the user's basis (--price/--mcap/env, config, market cap)
|
|
72
|
+
const basis = pickBasis(loadConfig(home).config.display?.priceMode, io.env);
|
|
73
|
+
const kill = over.killSwitch ?? new KillSwitch();
|
|
74
|
+
const panic = kill.engaged();
|
|
75
|
+
if (panic) {
|
|
76
|
+
io.stderr.write(`dexterm autotrade run: the panic switch is engaged (${panic.reason}); nothing starts until you run \`dexterm wallet resume\` in a terminal\n`);
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
const cfg = loadRunnerConfig(home);
|
|
80
|
+
if (!cfg.ok) {
|
|
81
|
+
io.stderr.write(`dexterm autotrade run: ${cfg.path} is invalid:\n${cfg.errors.map((e) => ` ${e}`).join('\n')}\n`);
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
const config = cfg.config;
|
|
85
|
+
const replay = typeof opts.replay === 'string' ? opts.replay : null;
|
|
86
|
+
// task-1543: the engine for the sessions: --engine, else ai.json (surfaces.autotrade / provider); auto = claude.
|
|
87
|
+
let local = null;
|
|
88
|
+
if (!opts.stub) {
|
|
89
|
+
const r = await localEngine(opts, io);
|
|
90
|
+
if ('error' in r) {
|
|
91
|
+
io.stderr.write(`dexterm autotrade run: ${r.error}\n`);
|
|
92
|
+
return r.code;
|
|
93
|
+
}
|
|
94
|
+
local = r.engine === 'ollama' ? r : null;
|
|
95
|
+
}
|
|
96
|
+
const quiet = !!opts.quiet;
|
|
97
|
+
const say = (s) => { if (!quiet)
|
|
98
|
+
io.stdout.write(`${s}\n`); };
|
|
99
|
+
const runId = newRunId(strategy.id);
|
|
100
|
+
const lock = acquireRunLock(home, runId);
|
|
101
|
+
if (!lock.ok) {
|
|
102
|
+
io.stderr.write(`dexterm autotrade run: another runner is active (pid ${lock.holder.pid}, run ${lock.holder.runId}); stop it first\n`);
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
if (lock.reclaimed)
|
|
106
|
+
say(`reclaimed a stale runner lock (pid ${lock.reclaimed.pid} is gone)`);
|
|
107
|
+
if (!clearStaleControl(home, runId)) {
|
|
108
|
+
io.stderr.write(`dexterm autotrade run: ${controlPath(home)} cannot be read; look at it and remove it, then start again\n`);
|
|
109
|
+
releaseRunLock(home, runId);
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
const badge = 'PAPER';
|
|
113
|
+
// Telegram: the user's own bot, when it is set up and paired (or the host a /use switch carried over).
|
|
114
|
+
let host = over.telegramHost ?? null;
|
|
115
|
+
const ownHost = !over.telegramHost;
|
|
116
|
+
let liveData = null;
|
|
117
|
+
// The card's SOL/USD: the coin's own SOL-quoted pair on DexScreener at the close (live data only; a replay sends text).
|
|
118
|
+
const solUsdFor = async (mint) => {
|
|
119
|
+
const v = liveData ? (await liveData.tokens([mint])).get(mint) : undefined;
|
|
120
|
+
const p = v?.pair;
|
|
121
|
+
return p && p.quoteMint === 'So11111111111111111111111111111111111111112' && p.priceUsd && p.priceNative ? p.priceUsd / p.priceNative : null;
|
|
122
|
+
};
|
|
123
|
+
if (!host && !opts['no-telegram']) {
|
|
124
|
+
const t = await startTelegramHost({ home, exec: simExecutor(strategy.execution), say, ...over.telegram, solUsdFor });
|
|
125
|
+
if (t.ok)
|
|
126
|
+
host = t.host;
|
|
127
|
+
else if (!t.quiet)
|
|
128
|
+
say(`telegram: off (${t.why})`);
|
|
129
|
+
}
|
|
130
|
+
if (strategy.entry.decide === 'agent+approve' && !host) {
|
|
131
|
+
io.stderr.write(`dexterm autotrade run: ${strategy.id} needs a one-tap approval for every entry (entry.decide "agent+approve"), and no paired Telegram chat is connected; set it up with \`dexterm telegram setup\`\n`);
|
|
132
|
+
releaseRunLock(home, runId);
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
const baseExec = simExecutor(strategy.execution);
|
|
136
|
+
if (host)
|
|
137
|
+
host.attach(runId, strategy, baseExec, solUsdFor);
|
|
138
|
+
const { ledger, files } = fileLedger(home, runId, host?.onLedger);
|
|
139
|
+
const startedAt = Date.now();
|
|
140
|
+
const gate = new SessionGate(effectiveCaps(strategy, config), config.allowanceGuard);
|
|
141
|
+
let runNow = startedAt;
|
|
142
|
+
const log = (kind, data) => { ledger.add(runNow, kind, data); };
|
|
143
|
+
// The run ledger anchors the chat audit (day file, head, count) at start and end: `dexterm telegram audit --verify`
|
|
144
|
+
// cross-checks these, so a chat audit cut or re-chained after an anchor no longer verifies.
|
|
145
|
+
const anchorChat = (when) => { const h = host?.service.audit.head(); if (h?.file && h.day && h.events > 0)
|
|
146
|
+
log('chat_audit', { when, day: h.day, file: h.file, head: h.head, events: h.events }); };
|
|
147
|
+
anchorChat('start');
|
|
148
|
+
const rawPath = opts.raw ? join(home, 'autotrade', 'runs', `${runId}.stream.jsonl`) : null;
|
|
149
|
+
let sessions;
|
|
150
|
+
if (opts.stub)
|
|
151
|
+
sessions = stubSession();
|
|
152
|
+
else if (local && local.engine === 'ollama') {
|
|
153
|
+
sessions = ollamaSessionFactory({
|
|
154
|
+
client: local.client, model: local.model, numCtx: local.numCtx, restriction: local.level, live: false,
|
|
155
|
+
systemPrompt: (s) => runnerSystemPrompt(s, { live: !replay, basis }),
|
|
156
|
+
gate, log,
|
|
157
|
+
onRaw: rawPath ? (id, line) => appendFileSync(rawPath, `${JSON.stringify({ sessionId: id, line })}\n`, { mode: 0o600 }) : undefined,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
sessions = claudeSessionFactory({
|
|
162
|
+
bin: typeof opts['claude-bin'] === 'string' ? opts['claude-bin'] : (io.env.DEXTERM_CLAUDE_BIN || 'claude'),
|
|
163
|
+
account: () => prepareAccount(config.account, io.env),
|
|
164
|
+
model: typeof opts.model === 'string' ? opts.model : undefined,
|
|
165
|
+
systemPrompt: (s) => runnerSystemPrompt(s, { live: !replay, basis }),
|
|
166
|
+
gate, log,
|
|
167
|
+
onRaw: rawPath ? (id, line) => appendFileSync(rawPath, `${JSON.stringify({ sessionId: id, line })}\n`, { mode: 0o600 }) : undefined,
|
|
168
|
+
now: () => runNow,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
// The market: a paced tape, or the live keyless data layer.
|
|
172
|
+
let source;
|
|
173
|
+
let tape;
|
|
174
|
+
let live = null;
|
|
175
|
+
let dossier;
|
|
176
|
+
let vet;
|
|
177
|
+
let closeData = () => { };
|
|
178
|
+
if (replay) {
|
|
179
|
+
try {
|
|
180
|
+
tape = loadTape(builtinTapePath(replay));
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
io.stderr.write(`dexterm autotrade run: ${e.message}\n`);
|
|
184
|
+
releaseRunLock(home, runId);
|
|
185
|
+
return 1;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const rpc = resolveRpcUrl({ env: io.env });
|
|
190
|
+
const data = createDataLayer({ dbPath: io.env.DEXTERM_DB_PATH || defaultDbPath(io.env), rpcUrl: rpc.url, rpcFallbackUrl: rpc.fallbackUrl });
|
|
191
|
+
closeData = () => data.close();
|
|
192
|
+
liveData = data.dexscreener;
|
|
193
|
+
live = new LiveSource({ store: data.store, dexscreener: data.dexscreener }, { onLog: (l) => log('source_error', { error: l.slice(0, 300) }) });
|
|
194
|
+
source = { name: 'live keyless data layer (Store + DexScreener tokens/v1 fast lane)', ticks: live.ticks(), startT: startedAt };
|
|
195
|
+
// v1 dossier: the MCP get_dossier assembly (market, holders, security, candles, tape, creator) for the mint.
|
|
196
|
+
const { McpTools } = await import('../../../mcp/src/index.js');
|
|
197
|
+
const mcp = new McpTools(data, () => ({ screen: 'autotrade-runner', actions: { mints: [], feeds: [], sortKeys: [] } }));
|
|
198
|
+
dossier = async (k, h, matched, b) => {
|
|
199
|
+
let rec;
|
|
200
|
+
try {
|
|
201
|
+
rec = await mcp.call('get_dossier', { mint: k.mint, interval: '5m' });
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
rec = { availability: { present: [], missing: [`dossier: ${e.message}`], knownUnavailable: [] } };
|
|
205
|
+
}
|
|
206
|
+
return renderDossierRecord(rec, { mint: k.mint, symbol: k.symbol, matched }, k, h, b, basis);
|
|
207
|
+
};
|
|
208
|
+
// task-1532: the strategy's LARP gate (entry.safety.larp) on live data, once per coin, before it becomes a candidate.
|
|
209
|
+
const larp = strategy.entry.safety.larp;
|
|
210
|
+
if (larp)
|
|
211
|
+
vet = larpVet(larp, (mint) => mcp.call('larp_check', { mint }));
|
|
212
|
+
}
|
|
213
|
+
// Heartbeat + rendezvous: on every state change (throttled to 1 s) and every 10 s regardless.
|
|
214
|
+
let last = null;
|
|
215
|
+
let lastWrite = 0;
|
|
216
|
+
// Dead-man: the time of the last heartbeat that was written OK; entries pause when it goes stale (a full disk,
|
|
217
|
+
// a removed directory), because the TUI and the monitor would see this runner as dead.
|
|
218
|
+
let lastBeatOk = startedAt;
|
|
219
|
+
let beatFailing = false;
|
|
220
|
+
const writeState = (force = false) => {
|
|
221
|
+
if (!last)
|
|
222
|
+
return;
|
|
223
|
+
const now = Date.now();
|
|
224
|
+
if (!force && now - lastWrite < 1000)
|
|
225
|
+
return;
|
|
226
|
+
lastWrite = now;
|
|
227
|
+
const { t: _t, ...body } = last;
|
|
228
|
+
try {
|
|
229
|
+
writeRendezvous(home, { schema: RUNNER_RENDEZVOUS_SCHEMA, pid: process.pid, mode: 'paper', runId, startedAt, heartbeatAt: now, ledgerPath: files.ledger, ...body });
|
|
230
|
+
writeJsonAtomic(files.summary, { schema: RUN_SUMMARY_SCHEMA, runId, strategy: body.strategy, startedAt, updatedAt: now, state: body.state, book: body.book, spend: body.spend, events: body.events, ledgerHead: body.ledgerHead, source: replay ? `replay ${replay}` : 'live' });
|
|
231
|
+
lastBeatOk = now;
|
|
232
|
+
if (beatFailing) {
|
|
233
|
+
beatFailing = false;
|
|
234
|
+
log('heartbeat_ok', {});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
if (!beatFailing) {
|
|
239
|
+
beatFailing = true;
|
|
240
|
+
log('heartbeat_failed', { error: e.message.slice(0, 200) });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const beat = setInterval(() => writeState(true), HEARTBEAT_MS);
|
|
245
|
+
const maxMin = num(opts['max-minutes'], 0);
|
|
246
|
+
const stopAt = maxMin ? startedAt + maxMin * 60_000 : Infinity;
|
|
247
|
+
let stopping = false;
|
|
248
|
+
const stop = (why) => { if (stopping)
|
|
249
|
+
return; stopping = true; log('stop_requested', { why }); say(`stopping (${why}): open sessions close, the paper book is kept in the ledger`); live?.stop(); };
|
|
250
|
+
const onSig = () => stop('signal');
|
|
251
|
+
process.once('SIGINT', onSig);
|
|
252
|
+
process.once('SIGTERM', onSig);
|
|
253
|
+
// The heartbeat goes stale only while writes fail (a slow first fetch before any state is not a failure).
|
|
254
|
+
const safety = new RunnerSafety({ home, runId, killSwitch: kill, heartbeatAgeMs: () => (beatFailing ? Date.now() - lastBeatOk : 0) });
|
|
255
|
+
if (strategy.highRisk)
|
|
256
|
+
say(`⚠ HIGH RISK strategy (${strategy.id}): one position at a time, early entries, most of them lose. Paper only; not financial advice.`);
|
|
257
|
+
say(`dexterm autotrade run · ${badge} · strategy ${strategy.id} v${strategy.version} (${strategy.hash}) · ${replay ? `replay ${replay}` : 'live keyless data'} · sessions ${opts.stub ? 'stub' : local ? `ollama (local ${local.model}, profile ${local.level}, $0)` : `claude (${config.account.kind}${typeof opts.model === 'string' ? `, ${opts.model}` : ''})`}`);
|
|
258
|
+
say(`run ${runId} · ledger ${files.ledger} · rendezvous ${runnerRendezvousPath(home)}`);
|
|
259
|
+
if (host)
|
|
260
|
+
say(`telegram: on (your own bot, ${badgeOf(host.port.network)}) · approvals ${strategy.entry.decide === 'agent+approve' ? 'required by the strategy' : 'when turned on'} · /status /pause /kill from your paired chat`);
|
|
261
|
+
let switchTo = null;
|
|
262
|
+
let code = 0;
|
|
263
|
+
try {
|
|
264
|
+
const res = await runAutotrade({
|
|
265
|
+
strategy, basis, tape, source, sessions, gate, dossier, vet, verbose: true, onLine: say, ledger, runId,
|
|
266
|
+
...(host ? { executor: host.port, approvals: host.service.broker } : { executor: baseExec }),
|
|
267
|
+
pacing: replay ? { mode: 'paced', msPerTick: num(opts['ms-per-tick'], 2000) } : { mode: 'live' },
|
|
268
|
+
// The panic file (`dexterm wallet panic`), a stop / flatten request and a local stop (signal, --max-minutes) all
|
|
269
|
+
// halt: no new entries, sessions close. A signal or --max-minutes also ends the tick stream at once.
|
|
270
|
+
safety: () => {
|
|
271
|
+
if (Date.now() >= stopAt)
|
|
272
|
+
stop('max-minutes');
|
|
273
|
+
const v = safety.step();
|
|
274
|
+
return stopping && !v.halt ? { ...v, halt: 'runner-stopped', detail: 'a signal or --max-minutes' } : v;
|
|
275
|
+
},
|
|
276
|
+
onState: (st) => { runNow = st.t; last = st; writeState(st.state === 'stopped'); host?.onState(st); },
|
|
277
|
+
onFocus: (marks) => { live?.setFocus(marks); writeFocus(home, marks); },
|
|
278
|
+
});
|
|
279
|
+
const next = host?.takeSwitch() ?? null;
|
|
280
|
+
if (next && !stopping)
|
|
281
|
+
switchTo = next.id;
|
|
282
|
+
say(`done · ${res.candidates} candidates · ${res.sessions} sessions · $${res.sessionCostUsd.toFixed(4)} model spend · ${res.trades.length} closed, ${res.openAtEnd.length} open · ledger head ${ledger.head()}`);
|
|
283
|
+
}
|
|
284
|
+
catch (e) {
|
|
285
|
+
io.stderr.write(`dexterm autotrade run: ${e.message}\n`);
|
|
286
|
+
code = 1;
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
clearInterval(beat);
|
|
290
|
+
process.removeListener('SIGINT', onSig);
|
|
291
|
+
process.removeListener('SIGTERM', onSig);
|
|
292
|
+
anchorChat('end');
|
|
293
|
+
writeState(true);
|
|
294
|
+
writeFocus(home, []);
|
|
295
|
+
live?.stop();
|
|
296
|
+
closeData();
|
|
297
|
+
clearControl(home, runId);
|
|
298
|
+
releaseRunLock(home, runId);
|
|
299
|
+
if (host && !(switchTo && code === 0) && ownHost)
|
|
300
|
+
await host.stop();
|
|
301
|
+
}
|
|
302
|
+
if (host && switchTo && code === 0) {
|
|
303
|
+
// A confirmed /use: the same bot, the next run on the new strategy (it was checked equal-or-tighter).
|
|
304
|
+
say(`telegram: starting the next run on ${switchTo}`);
|
|
305
|
+
const rest = [];
|
|
306
|
+
let dropped = false;
|
|
307
|
+
for (let i = 0; i < argv.length; i++) {
|
|
308
|
+
if (argv[i] === '--strategy') {
|
|
309
|
+
i++;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (!dropped && argv[i] === arg) {
|
|
313
|
+
dropped = true;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
rest.push(argv[i]);
|
|
317
|
+
}
|
|
318
|
+
const c2 = await runCmd([switchTo, ...rest], io, { ...over, telegramHost: host });
|
|
319
|
+
if (ownHost)
|
|
320
|
+
await host.stop();
|
|
321
|
+
return c2;
|
|
322
|
+
}
|
|
323
|
+
return code;
|
|
324
|
+
}
|
|
325
|
+
/** Candidate + held coins for the recorder's tick lane (task-1504 focus file); best effort, never fails the run. */
|
|
326
|
+
function writeFocus(home, marks) {
|
|
327
|
+
void import('../../../recorder/src/index.js').then((m) => {
|
|
328
|
+
try {
|
|
329
|
+
m.writeFocusFile(m.focusFilePath(home), marks.map((x) => ({ mint: x.mint, role: x.role, until: Date.now() + 30 * 60_000 })), Date.now());
|
|
330
|
+
}
|
|
331
|
+
catch { /* the recorder is optional */ }
|
|
332
|
+
}).catch(() => { });
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* task-1543: resolve the session engine. claude = the original path (null here). ollama = the user's local model,
|
|
336
|
+
* allowed only when the local profile allows autotrade (paper) — checked before the run lock.
|
|
337
|
+
*/
|
|
338
|
+
async function localEngine(opts, io) {
|
|
339
|
+
const eng = await import('../../../mcp/src/engine/index.js');
|
|
340
|
+
const loaded = eng.loadAiConfig(io.env);
|
|
341
|
+
const flag = typeof opts.engine === 'string' ? eng.parseEngineChoice(opts.engine) : null;
|
|
342
|
+
if (typeof opts.engine === 'string' && !flag)
|
|
343
|
+
return { error: `unknown engine ${String(opts.engine).slice(0, 16)} (claude|ollama)`, code: 2 };
|
|
344
|
+
const choice = flag ?? eng.engineChoiceFor('autotrade', loaded.config, io.env);
|
|
345
|
+
if (choice === 'auto' || choice === 'claude')
|
|
346
|
+
return { engine: 'claude' };
|
|
347
|
+
if (choice === 'codex')
|
|
348
|
+
return { error: 'autotrade sessions run on claude or a local ollama model (codex has no session mode); pass --engine claude or --engine ollama', code: 2 };
|
|
349
|
+
const set = eng.ollamaSettings(loaded.config, io.env);
|
|
350
|
+
const client = new eng.OllamaClient({ host: set.host });
|
|
351
|
+
let pulled;
|
|
352
|
+
try {
|
|
353
|
+
pulled = await client.tags();
|
|
354
|
+
}
|
|
355
|
+
catch (e) {
|
|
356
|
+
return { error: `Ollama is not reachable at ${set.host.url} (${e.message.slice(0, 120)}); start it, or run on claude (--engine claude)`, code: 1 };
|
|
357
|
+
}
|
|
358
|
+
const asked = typeof opts.model === 'string' && eng.MODEL_NAME.test(opts.model) && !/^claude/i.test(opts.model) ? opts.model : null;
|
|
359
|
+
const model = asked ?? set.model ?? eng.pickModel(pulled);
|
|
360
|
+
if (!model)
|
|
361
|
+
return { error: `no local model pulled: ollama pull ${eng.SUGGESTED_MODELS[0].name} && dexterm ai model ${eng.SUGGESTED_MODELS[0].name}`, code: 1 };
|
|
362
|
+
if (!pulled.includes(model) && !pulled.includes(`${model}:latest`))
|
|
363
|
+
return { error: `${model} is not pulled: ollama pull ${model}`, code: 1 };
|
|
364
|
+
const { level, note } = eng.effectiveRestriction(loaded.config, model, io.env);
|
|
365
|
+
if (note)
|
|
366
|
+
io.stderr.write(`note: ${note}\n`);
|
|
367
|
+
try {
|
|
368
|
+
assertAutotradeAllowed(level, false);
|
|
369
|
+
}
|
|
370
|
+
catch (e) {
|
|
371
|
+
return { error: e.message, code: 1 };
|
|
372
|
+
}
|
|
373
|
+
const w = eng.remoteHostWarning(set.host);
|
|
374
|
+
if (w)
|
|
375
|
+
io.stderr.write(`warning: ${w}\n`);
|
|
376
|
+
return { engine: 'ollama', client, model, numCtx: Math.min(set.numCtx, 16384), level };
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* task-1532: the LARP gate. `larp_check` (the MCP tool: links, website, domain age, repo, X account) scores the coin
|
|
380
|
+
* 0-100; above maxScore is refused. A check that fails, times out (20 s) or comes back without a score is "unknown":
|
|
381
|
+
* refused when the strategy says reject (the default for yolo). Evidence, not a verdict: a low score is not a safety check.
|
|
382
|
+
*/
|
|
383
|
+
export function larpVet(g, check, timeoutMs = 20_000) {
|
|
384
|
+
return async (k) => {
|
|
385
|
+
let rec = null;
|
|
386
|
+
let why = '';
|
|
387
|
+
// task-1574: the timeout keeps the event loop alive until the race settles (an unref'd timer let a check that never
|
|
388
|
+
// answers leave the vet pending forever when nothing else held the loop; Node 22's test runner cancels on that).
|
|
389
|
+
let timer;
|
|
390
|
+
try {
|
|
391
|
+
rec = await Promise.race([check(k.mint), new Promise((_, rej) => { timer = setTimeout(() => rej(new Error(`no answer in ${timeoutMs / 1000} s`)), timeoutMs); })]);
|
|
392
|
+
if (rec && typeof rec.error === 'string') {
|
|
393
|
+
why = rec.error.slice(0, 120);
|
|
394
|
+
rec = null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch (e) {
|
|
398
|
+
why = e.message.slice(0, 120);
|
|
399
|
+
}
|
|
400
|
+
finally {
|
|
401
|
+
clearTimeout(timer);
|
|
402
|
+
}
|
|
403
|
+
const score = rec && typeof rec.score === 'number' && Number.isFinite(rec.score) ? rec.score : null;
|
|
404
|
+
if (score === null) {
|
|
405
|
+
const detail = `the LARP check could not run${why ? ` (${why})` : ''}`;
|
|
406
|
+
return g.unknown === 'allow' ? { ok: true, note: `${detail}; allowed by the strategy` } : { ok: false, rule: 'larp-unknown', detail };
|
|
407
|
+
}
|
|
408
|
+
const band = typeof rec.band === 'string' ? rec.band : '';
|
|
409
|
+
if (score > g.maxScore)
|
|
410
|
+
return { ok: false, rule: 'larp-score', detail: `LARP score ${score}${band ? ` (${band})` : ''} is above ${g.maxScore}` };
|
|
411
|
+
return { ok: true, note: `LARP score ${score}${band ? ` (${band})` : ''} ≤ ${g.maxScore}` };
|
|
412
|
+
};
|
|
413
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// `dexterm autotrade stop` (task-1509 AT-7, AUTOTRADE-SPEC §6.3). PAPER ONLY.
|
|
2
|
+
//
|
|
3
|
+
// A soft stop of the running paper runner: no new entries, every session closed, pending orders dropped, and the
|
|
4
|
+
// mechanical exits (stops, take-profits, trails, rug exits) keep running until nothing is held; then the runner
|
|
5
|
+
// ends. It writes a request for that run into $DEXTERM_HOME/autotrade/control.json, which the runner re-reads on every
|
|
6
|
+
// step, and waits a few seconds for the runner to confirm through its rendezvous. A stop is final for the run.
|
|
7
|
+
// `dexterm wallet panic` is the harder switch: every paper desk and signer re-reads it, and only
|
|
8
|
+
// `dexterm wallet resume` on a real terminal lifts it. Nothing here touches a wallet, a key or the signer.
|
|
9
|
+
import { dextermHome } from '../../../custom/src/index.js';
|
|
10
|
+
import { activeRunner, requestControl } from '../../../autotrade/src/index.js';
|
|
11
|
+
import { parseArgs } from "./strategies.js";
|
|
12
|
+
export const STOP_USAGE = 'dexterm autotrade stop [--strategy ID] [--wait SEC] [--json]';
|
|
13
|
+
const tick = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
14
|
+
/** True once the runner shows the request in its rendezvous (or is gone). */
|
|
15
|
+
function acknowledged(r, runId, action) {
|
|
16
|
+
if (!r || r.runId !== runId)
|
|
17
|
+
return true;
|
|
18
|
+
if (r.state === 'stopped')
|
|
19
|
+
return true;
|
|
20
|
+
return action === 'flatten' ? r.haltReason === 'flatten' : r.haltReason !== null && r.state === 'halted';
|
|
21
|
+
}
|
|
22
|
+
/** Send stop or flatten to the active runner and wait up to waitSec for it to confirm. */
|
|
23
|
+
export async function sendControl(io, action, waitSec, runner) {
|
|
24
|
+
const home = dextermHome(io.env);
|
|
25
|
+
const w = requestControl(home, runner.runId, action, 'cli');
|
|
26
|
+
if (!w.ok)
|
|
27
|
+
return { error: w.why };
|
|
28
|
+
const until = Date.now() + Math.max(0, waitSec) * 1000;
|
|
29
|
+
let cur = activeRunner(home);
|
|
30
|
+
while (!acknowledged(cur, runner.runId, action) && Date.now() < until) {
|
|
31
|
+
await tick(200);
|
|
32
|
+
cur = activeRunner(home);
|
|
33
|
+
}
|
|
34
|
+
const same = cur && cur.runId === runner.runId ? cur : null;
|
|
35
|
+
return { requested: true, runId: runner.runId, acknowledged: acknowledged(cur, runner.runId, action), already: w.already, positions: same ? same.positions : 0, sessions: same ? same.sessions : 0, ended: !same || same.state === 'stopped' };
|
|
36
|
+
}
|
|
37
|
+
export function waitArg(v) {
|
|
38
|
+
const n = typeof v === 'string' ? Number(v) : NaN;
|
|
39
|
+
return Number.isFinite(n) && n >= 0 ? Math.min(n, 120) : 10;
|
|
40
|
+
}
|
|
41
|
+
export async function stopCmd(argv, io) {
|
|
42
|
+
const { pos, opts } = parseArgs(argv, ['json']);
|
|
43
|
+
if (pos.length) {
|
|
44
|
+
io.stderr.write(`usage: ${STOP_USAGE}\n`);
|
|
45
|
+
return 2;
|
|
46
|
+
}
|
|
47
|
+
// task-1529: one strategy of a parallel run; the others keep running.
|
|
48
|
+
if (typeof opts.strategy === 'string')
|
|
49
|
+
return (await import("./strategy-control.js")).memberStopCmd(io, opts.strategy, 'stop', opts);
|
|
50
|
+
const home = dextermHome(io.env);
|
|
51
|
+
const r = activeRunner(home);
|
|
52
|
+
if (!r) {
|
|
53
|
+
if (opts.json)
|
|
54
|
+
io.stdout.write(`${JSON.stringify({ requested: false, runId: null, why: 'no autotrade runner is running' })}\n`);
|
|
55
|
+
else
|
|
56
|
+
io.stdout.write('no autotrade runner is running; nothing to stop. (`dexterm wallet panic` halts every paper desk and signer.)\n');
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
const res = await sendControl(io, 'stop', waitArg(opts.wait), r);
|
|
60
|
+
if ('error' in res) {
|
|
61
|
+
io.stderr.write(`dexterm autotrade stop: ${res.error}\n`);
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
if (opts.json) {
|
|
65
|
+
io.stdout.write(`${JSON.stringify(res)}\n`);
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
io.stdout.write(`stop ${res.already ? 'was already requested' : 'requested'} for run ${r.runId} (pid ${r.pid})\n`);
|
|
69
|
+
if (!res.acknowledged)
|
|
70
|
+
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');
|
|
71
|
+
else if (res.ended)
|
|
72
|
+
io.stdout.write('✓ the runner stopped (nothing was held)\n');
|
|
73
|
+
else
|
|
74
|
+
io.stdout.write(`✓ halted: no new entries, sessions closed, orders dropped${res.positions ? `; ${res.positions} paper position${res.positions === 1 ? '' : 's'} keep${res.positions === 1 ? 's' : ''} its mechanical exits until closed (\`dexterm autotrade flatten\` sells now)` : ''}\n`);
|
|
75
|
+
return 0;
|
|
76
|
+
}
|