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,198 @@
|
|
|
1
|
+
// `dexterm autotrade list|validate|new|import|remove|journal` (task-1503 AT-1): the strategy store from the CLI.
|
|
2
|
+
// Every write goes through @dexterm/autotrade StrategyStore (validated first, atomic, journaled with actor
|
|
3
|
+
// "user"); nothing here signs, arms or trades.
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { fmtIssue, StrategyStore, validateStrategy } from '../../../autotrade/src/index.js';
|
|
6
|
+
export function parseArgs(argv, booleans = []) {
|
|
7
|
+
const pos = [];
|
|
8
|
+
const opts = {};
|
|
9
|
+
for (let i = 0; i < argv.length; i++) {
|
|
10
|
+
const a = argv[i];
|
|
11
|
+
if (!a.startsWith('--')) {
|
|
12
|
+
pos.push(a);
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const eq = a.indexOf('=');
|
|
16
|
+
if (eq > 0) {
|
|
17
|
+
opts[a.slice(2, eq)] = a.slice(eq + 1);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const k = a.slice(2);
|
|
21
|
+
const v = argv[i + 1];
|
|
22
|
+
if (!booleans.includes(k) && v !== undefined && !v.startsWith('--')) {
|
|
23
|
+
opts[k] = v;
|
|
24
|
+
i++;
|
|
25
|
+
}
|
|
26
|
+
else
|
|
27
|
+
opts[k] = true;
|
|
28
|
+
}
|
|
29
|
+
return { pos, opts };
|
|
30
|
+
}
|
|
31
|
+
export const storeFor = (io) => new StrategyStore({ env: io.env });
|
|
32
|
+
const looksLikeFile = (a) => a.endsWith('.json') || a.includes('/') || a.includes('\\');
|
|
33
|
+
/** Resolve `<id|file>`: an existing path (or anything that looks like one) is a file; otherwise the store, then the presets. */
|
|
34
|
+
export function resolveStrategyArg(arg, io) {
|
|
35
|
+
if (!arg)
|
|
36
|
+
return null;
|
|
37
|
+
if (looksLikeFile(arg) || existsSync(arg)) {
|
|
38
|
+
if (!existsSync(arg))
|
|
39
|
+
return { ok: false, errors: [{ path: '', msg: `no such file ${arg}` }], warnings: [], source: 'file' };
|
|
40
|
+
let doc;
|
|
41
|
+
try {
|
|
42
|
+
doc = JSON.parse(readFileSync(arg, 'utf8'));
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
return { ok: false, errors: [{ path: '', msg: `not JSON: ${e.message}` }], warnings: [], source: 'file' };
|
|
46
|
+
}
|
|
47
|
+
return { ...validateStrategy(doc), source: 'file' };
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const g = storeFor(io).get(arg);
|
|
51
|
+
return g ? { ...g, source: g.source } : { ok: false, errors: [{ path: '', msg: `no strategy or preset named ${arg} (dexterm autotrade list)` }], warnings: [], source: 'store' };
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
return { ok: false, errors: [{ path: '', msg: e.message }], warnings: [], source: 'store' };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const printIssues = (io, kind, xs, to = 'stdout') => { for (const x of xs)
|
|
58
|
+
io[to].write(`${kind} ${fmtIssue(x)}\n`); };
|
|
59
|
+
export function listCmd(argv, io) {
|
|
60
|
+
const { opts } = parseArgs(argv, ['json']);
|
|
61
|
+
const store = storeFor(io);
|
|
62
|
+
const rows = store.list();
|
|
63
|
+
if (opts.json) {
|
|
64
|
+
io.stdout.write(JSON.stringify({ dir: store.dir, strategies: rows }, null, 2) + '\n');
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
io.stdout.write(`STRATEGIES · PAPER · ${store.dir}\n`);
|
|
68
|
+
const w = Math.max(4, ...rows.map((r) => r.id.length));
|
|
69
|
+
for (const r of rows) {
|
|
70
|
+
const bt = r.lastBacktest ? `last backtest ${r.lastBacktest.trades} trades ${r.lastBacktest.netUsd >= 0 ? '+' : '-'}$${Math.abs(r.lastBacktest.netUsd).toFixed(2)} (${r.lastBacktest.netPct}%), dd ${r.lastBacktest.maxDrawdownPct}%` : 'no backtest yet';
|
|
71
|
+
const head = ` ${r.id.padEnd(w)} ${(r.source + (r.shadowsPreset ? '*' : '')).padEnd(7)}`;
|
|
72
|
+
if (!r.ok) {
|
|
73
|
+
io.stdout.write(`${head} INVALID: ${(r.errors ?? []).slice(0, 2).map(fmtIssue).join('; ')}\n`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
io.stdout.write(`${head} v${r.version.padEnd(7)} ${r.hash} ${r.mode.padEnd(5)} profiles ${r.profiles.join(',')} · ${bt}${r.highRisk ? ' · ⚠ HIGH RISK' : ''}\n`);
|
|
77
|
+
}
|
|
78
|
+
if (rows.some((r) => r.shadowsPreset))
|
|
79
|
+
io.stdout.write(' * your file shadows the shipped preset of the same id\n');
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
export function validateCmd(argv, io) {
|
|
83
|
+
const { pos, opts } = parseArgs(argv, ['json']);
|
|
84
|
+
const r = resolveStrategyArg(pos[0] ?? (typeof opts.strategy === 'string' ? opts.strategy : undefined), io);
|
|
85
|
+
if (!r) {
|
|
86
|
+
io.stderr.write('usage: dexterm autotrade validate <id|file> [--json]\n');
|
|
87
|
+
return 2;
|
|
88
|
+
}
|
|
89
|
+
if (opts.json) {
|
|
90
|
+
io.stdout.write(JSON.stringify(r.ok ? { ok: true, id: r.strategy.id, version: r.strategy.version, hash: r.strategy.hash, mode: r.strategy.mode, warnings: r.warnings } : { ok: false, errors: r.errors, warnings: r.warnings }, null, 2) + '\n');
|
|
91
|
+
return r.ok ? 0 : 2;
|
|
92
|
+
}
|
|
93
|
+
printIssues(io, 'warning', r.warnings);
|
|
94
|
+
if (!r.ok) {
|
|
95
|
+
printIssues(io, 'error', r.errors);
|
|
96
|
+
return 2;
|
|
97
|
+
}
|
|
98
|
+
const s = r.strategy;
|
|
99
|
+
io.stdout.write(`ok ${s.id} v${s.version} hash ${s.hash} mode ${s.mode}${s.mode === 'live' ? ' (does not run: dexterm runs strategies on paper only)' : ''} profiles ${Object.keys(s.exits.profiles).join(',')} (${r.source})\n`);
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
export function newCmd(argv, io) {
|
|
103
|
+
const { pos, opts } = parseArgs(argv);
|
|
104
|
+
const id = pos[0];
|
|
105
|
+
const from = typeof opts.from === 'string' ? opts.from : undefined;
|
|
106
|
+
if (!id || !from) {
|
|
107
|
+
io.stderr.write('usage: dexterm autotrade new <new-id> --from <preset|id> [--name TEXT]\n');
|
|
108
|
+
return 2;
|
|
109
|
+
}
|
|
110
|
+
let r;
|
|
111
|
+
try {
|
|
112
|
+
r = storeFor(io).createFrom(from, id, { actor: 'user', name: typeof opts.name === 'string' ? opts.name : undefined });
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
io.stderr.write(`dexterm autotrade: ${e.message}\n`);
|
|
116
|
+
return 2;
|
|
117
|
+
}
|
|
118
|
+
if (!r.ok) {
|
|
119
|
+
printIssues(io, 'error', r.errors, 'stderr');
|
|
120
|
+
return 2;
|
|
121
|
+
}
|
|
122
|
+
printIssues(io, 'warning', r.warnings);
|
|
123
|
+
io.stdout.write(`created ${id} v${r.strategy.version} (${r.strategy.hash}) from ${from} → ${storeFor(io).pathOf(id)}\n edit the file, then: dexterm autotrade validate ${id} · dexterm autotrade backtest ${id}\n`);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
export function importCmd(argv, io) {
|
|
127
|
+
const { pos, opts } = parseArgs(argv, ['replace']);
|
|
128
|
+
const file = pos[0];
|
|
129
|
+
if (!file || !existsSync(file)) {
|
|
130
|
+
io.stderr.write('usage: dexterm autotrade import <file> [--replace]\n');
|
|
131
|
+
return 2;
|
|
132
|
+
}
|
|
133
|
+
let doc;
|
|
134
|
+
try {
|
|
135
|
+
doc = JSON.parse(readFileSync(file, 'utf8'));
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
io.stderr.write(`not JSON: ${e.message}\n`);
|
|
139
|
+
return 2;
|
|
140
|
+
}
|
|
141
|
+
// Spec §2c: an imported strategy is always paper, whatever the file says.
|
|
142
|
+
const forced = doc && typeof doc === 'object' && doc.mode !== 'paper';
|
|
143
|
+
if (forced)
|
|
144
|
+
doc = { ...doc, mode: 'paper' };
|
|
145
|
+
const store = storeFor(io);
|
|
146
|
+
const id = typeof doc.id === 'string' ? doc.id : '';
|
|
147
|
+
let r;
|
|
148
|
+
try {
|
|
149
|
+
r = opts.replace && id && store.has(id) ? store.update(id, doc, { actor: 'import', why: `import ${file}` }) : store.create(doc, { actor: 'import', why: `import ${file}` });
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
io.stderr.write(`dexterm autotrade: ${e.message}\n`);
|
|
153
|
+
return 2;
|
|
154
|
+
}
|
|
155
|
+
if (!r.ok) {
|
|
156
|
+
printIssues(io, 'error', r.errors, 'stderr');
|
|
157
|
+
return 2;
|
|
158
|
+
}
|
|
159
|
+
printIssues(io, 'warning', r.warnings);
|
|
160
|
+
io.stdout.write(`imported ${r.strategy.id} v${r.strategy.version} (${r.strategy.hash})${forced ? ' · mode forced to paper' : ''}\n`);
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
export function removeCmd(argv, io) {
|
|
164
|
+
const { pos } = parseArgs(argv);
|
|
165
|
+
if (!pos[0]) {
|
|
166
|
+
io.stderr.write('usage: dexterm autotrade remove <id>\n');
|
|
167
|
+
return 2;
|
|
168
|
+
}
|
|
169
|
+
let r;
|
|
170
|
+
try {
|
|
171
|
+
r = storeFor(io).remove(pos[0], { actor: 'user' });
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
io.stderr.write(`dexterm autotrade: ${e.message}\n`);
|
|
175
|
+
return 2;
|
|
176
|
+
}
|
|
177
|
+
if (!r.ok) {
|
|
178
|
+
printIssues(io, 'error', r.errors, 'stderr');
|
|
179
|
+
return 2;
|
|
180
|
+
}
|
|
181
|
+
io.stdout.write(`removed ${pos[0]} → ${r.movedTo}\n`);
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
export function journalCmd(argv, io) {
|
|
185
|
+
const { pos, opts } = parseArgs(argv, ['json']);
|
|
186
|
+
const j = storeFor(io).journal(pos[0]);
|
|
187
|
+
if (opts.json) {
|
|
188
|
+
io.stdout.write(JSON.stringify(j, null, 2) + '\n');
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
if (!j.length) {
|
|
192
|
+
io.stdout.write('no strategy changes journaled yet\n');
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
for (const x of j)
|
|
196
|
+
io.stdout.write(`${new Date(x.t).toISOString()} ${x.action.padEnd(6)} ${x.id.padEnd(20)} by ${x.actor.padEnd(8)} ${x.beforeHash ?? '—'} → ${x.afterHash ?? '—'}${x.version ? ` v${x.version}` : ''} ${x.ops.length} op(s)${x.why ? ` · ${x.why}` : ''}\n`);
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// `dexterm autotrade status [--all]`, `start <strategy>`, `pause | resume [--strategy ID]`, and the per-strategy
|
|
2
|
+
// `stop | flatten --strategy ID` (task-1529, parallel strategies). PAPER ONLY.
|
|
3
|
+
//
|
|
4
|
+
// Without --strategy, pause and resume act on the whole runner (control.json, like the Q screen's `p`), whether it
|
|
5
|
+
// runs one strategy or several. With --strategy they act on one strategy of `dexterm autotrade run --all` through its
|
|
6
|
+
// own control file (autotrade/fleet/<id>.control.json): another strategy never sees it. `start` starts a strategy of
|
|
7
|
+
// autotrade.json that is not running (paper, with its own config; nothing else can be changed from here).
|
|
8
|
+
// Nothing here touches a wallet, a key or the signer.
|
|
9
|
+
import { dextermHome } from '../../../custom/src/index.js';
|
|
10
|
+
import { accountTable, activeRunner, compareRows, compareTable, fleetTotals, readFleetStatus, requestControl, requestMemberControl, } from '../../../autotrade/src/index.js';
|
|
11
|
+
import { parseArgs } from "./strategies.js";
|
|
12
|
+
export const STATUS_USAGE = 'dexterm autotrade status [--all] [--json]';
|
|
13
|
+
export const START_USAGE = 'dexterm autotrade start <strategy> [--wait SEC] [--json]';
|
|
14
|
+
export const PAUSE_USAGE = 'dexterm autotrade pause [--strategy ID] [--json]';
|
|
15
|
+
export const RESUME_USAGE = 'dexterm autotrade resume [--strategy ID] [--json]';
|
|
16
|
+
const tick = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
17
|
+
const waitSec = (v, dflt = 10) => { const n = typeof v === 'string' ? Number(v) : NaN; return Number.isFinite(n) && n >= 0 ? Math.min(n, 120) : dflt; };
|
|
18
|
+
function memberNow(home, id) {
|
|
19
|
+
const st = readFleetStatus(home);
|
|
20
|
+
return st.ok ? st.status.members.find((m) => m.id === id) ?? null : null;
|
|
21
|
+
}
|
|
22
|
+
/** True once the strategy's state shows the request (or the fleet is gone). */
|
|
23
|
+
function acknowledged(m, action, runId) {
|
|
24
|
+
if (!m)
|
|
25
|
+
return true;
|
|
26
|
+
switch (action) {
|
|
27
|
+
case 'start': return m.runId !== null && m.runId !== runId && m.state !== 'idle';
|
|
28
|
+
case 'stop': return m.runId !== runId || m.state === 'stopped' || m.state === 'halted';
|
|
29
|
+
case 'flatten': return m.runId !== runId || m.state === 'stopped' || m.haltReason === 'flatten';
|
|
30
|
+
case 'pause': return m.state === 'paused' || m.state === 'halted' || m.state === 'stopped';
|
|
31
|
+
case 'resume-entries': return m.state !== 'paused';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Write one strategy's request and wait up to `wait` seconds for its runner to show it. */
|
|
35
|
+
export async function sendMemberControl(io, id, action, wait) {
|
|
36
|
+
const home = dextermHome(io.env);
|
|
37
|
+
const before = memberNow(home, id);
|
|
38
|
+
const w = requestMemberControl(home, id, action, 'cli');
|
|
39
|
+
if (!w.ok)
|
|
40
|
+
return { error: w.why };
|
|
41
|
+
const prevRun = action === 'start' ? before?.runId ?? null : w.request.runId;
|
|
42
|
+
const until = Date.now() + wait * 1000;
|
|
43
|
+
let cur = memberNow(home, id);
|
|
44
|
+
while (!acknowledged(cur, action, prevRun) && Date.now() < until) {
|
|
45
|
+
await tick(200);
|
|
46
|
+
cur = memberNow(home, id);
|
|
47
|
+
}
|
|
48
|
+
return { strategy: id, action, requested: true, already: w.already, acknowledged: acknowledged(cur, action, prevRun), runId: cur?.runId ?? null, state: cur?.state ?? null };
|
|
49
|
+
}
|
|
50
|
+
const WORD = { start: 'start', stop: 'stop', flatten: 'flatten', pause: 'pause', 'resume-entries': 'resume' };
|
|
51
|
+
export function printMemberOutcome(io, res, json) {
|
|
52
|
+
if (json) {
|
|
53
|
+
io.stdout.write(`${JSON.stringify(res)}\n`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
io.stdout.write(`${WORD[res.action]} ${res.already ? 'was already requested' : 'requested'} for strategy ${res.strategy} (the others are not touched)\n`);
|
|
57
|
+
if (!res.acknowledged)
|
|
58
|
+
io.stdout.write('the runner has not confirmed yet: it reads the request on its next tick. `dexterm autotrade status --all` shows each strategy.\n');
|
|
59
|
+
else
|
|
60
|
+
io.stdout.write(`✓ ${res.strategy}: ${res.state ?? 'gone'}${res.action === 'start' && res.runId ? ` (run ${res.runId})` : ''}\n`);
|
|
61
|
+
}
|
|
62
|
+
/** `dexterm autotrade stop | flatten --strategy ID` (called from stop.ts / flatten.ts). */
|
|
63
|
+
export async function memberStopCmd(io, id, action, opts) {
|
|
64
|
+
const res = await sendMemberControl(io, id, action, waitSec(opts.wait));
|
|
65
|
+
if ('error' in res) {
|
|
66
|
+
io.stderr.write(`dexterm autotrade ${action}: ${res.error}\n`);
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
printMemberOutcome(io, res, !!opts.json);
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
export async function startCmd(argv, io) {
|
|
73
|
+
const { pos, opts } = parseArgs(argv, ['json']);
|
|
74
|
+
const id = typeof opts.strategy === 'string' ? opts.strategy : pos[0];
|
|
75
|
+
if (!id || pos.length > 1) {
|
|
76
|
+
io.stderr.write(`usage: ${START_USAGE}\n (starts a strategy of autotrade.json in the running \`dexterm autotrade run --all --paper\`)\n`);
|
|
77
|
+
return 2;
|
|
78
|
+
}
|
|
79
|
+
const res = await sendMemberControl(io, id, 'start', waitSec(opts.wait));
|
|
80
|
+
if ('error' in res) {
|
|
81
|
+
io.stderr.write(`dexterm autotrade start: ${res.error}\n`);
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
printMemberOutcome(io, res, !!opts.json);
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
async function pauseResume(argv, io, action) {
|
|
88
|
+
const { pos, opts } = parseArgs(argv, ['json']);
|
|
89
|
+
const name = action === 'pause' ? 'pause' : 'resume';
|
|
90
|
+
if (pos.length) {
|
|
91
|
+
io.stderr.write(`usage: ${action === 'pause' ? PAUSE_USAGE : RESUME_USAGE}\n`);
|
|
92
|
+
return 2;
|
|
93
|
+
}
|
|
94
|
+
if (typeof opts.strategy === 'string') {
|
|
95
|
+
const res = await sendMemberControl(io, opts.strategy, action, waitSec(opts.wait, 5));
|
|
96
|
+
if ('error' in res) {
|
|
97
|
+
io.stderr.write(`dexterm autotrade ${name}: ${res.error}\n`);
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
printMemberOutcome(io, res, !!opts.json);
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
const home = dextermHome(io.env);
|
|
104
|
+
const r = activeRunner(home);
|
|
105
|
+
if (!r) {
|
|
106
|
+
io.stdout.write(opts.json ? `${JSON.stringify({ requested: false, why: 'no autotrade runner is running' })}\n` : `no autotrade runner is running; nothing to ${name}\n`);
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
const w = requestControl(home, r.runId, action, 'cli');
|
|
110
|
+
if (!w.ok) {
|
|
111
|
+
io.stderr.write(`dexterm autotrade ${name}: ${w.why}\n`);
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
if (opts.json) {
|
|
115
|
+
io.stdout.write(`${JSON.stringify({ requested: true, runId: r.runId, action })}\n`);
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
io.stdout.write(action === 'pause'
|
|
119
|
+
? `pause requested for run ${r.runId}: no new entries${r.runId.includes('-fleet-') ? ' in any strategy' : ''}; open positions keep their exits (\`dexterm autotrade resume\` lifts it)\n`
|
|
120
|
+
: `resume requested for run ${r.runId}: new entries allowed again\n`);
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
export const pauseCmd = (argv, io) => pauseResume(argv, io, 'pause');
|
|
124
|
+
export const resumeCmd = (argv, io) => pauseResume(argv, io, 'resume-entries');
|
|
125
|
+
export function statusCmd(argv, io) {
|
|
126
|
+
const { pos, opts } = parseArgs(argv, ['all', 'json']);
|
|
127
|
+
if (pos.length) {
|
|
128
|
+
io.stderr.write(`usage: ${STATUS_USAGE}\n`);
|
|
129
|
+
return 2;
|
|
130
|
+
}
|
|
131
|
+
const home = dextermHome(io.env);
|
|
132
|
+
const r = activeRunner(home);
|
|
133
|
+
const fs = readFleetStatus(home);
|
|
134
|
+
const fleetCurrent = fs.ok && r?.runId === fs.status.fleetId;
|
|
135
|
+
if (opts.json) {
|
|
136
|
+
io.stdout.write(`${JSON.stringify({
|
|
137
|
+
paper: true, runner: r,
|
|
138
|
+
...(fs.ok && (opts.all || fleetCurrent) ? { fleet: { fleetId: fs.status.fleetId, state: r?.runId === fs.status.fleetId ? fs.status.state : 'stopped', stale: fs.stale, global: fs.status.global, totals: fleetTotals(fs.status), strategies: compareRows(fs.status), ...(fs.status.accounts ? { accounts: fs.status.accounts } : {}) } } : {}),
|
|
139
|
+
}, null, 2)}\n`);
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
if (!r)
|
|
143
|
+
io.stdout.write('no autotrade runner is running\n');
|
|
144
|
+
else
|
|
145
|
+
io.stdout.write(`paper runner ${r.runId} (pid ${r.pid}) · ${r.state ?? 'starting'}${r.haltReason ? ` (${r.haltReason})` : ''} · ${r.positions} open · ${r.sessions} sessions${r.heartbeatAt ? ` · heartbeat ${Math.round((Date.now() - r.heartbeatAt) / 1000)} s ago` : ''}\n`);
|
|
146
|
+
if (!opts.all && !fleetCurrent) {
|
|
147
|
+
if (r)
|
|
148
|
+
io.stdout.write('(one strategy; `dexterm autotrade run --all --paper` runs every strategy of autotrade.json at once)\n');
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
if (!fs.ok) {
|
|
152
|
+
io.stdout.write(`parallel strategies: ${fs.why}\n`);
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
const st = fs.status;
|
|
156
|
+
const running = r?.runId === st.fleetId;
|
|
157
|
+
io.stdout.write(`\nPARALLEL STRATEGIES · PAPER · fleet ${st.fleetId} · ${running ? st.state : 'ended'}${fs.stale && running ? ' (heartbeat stale)' : ''} · ${st.source}\n`);
|
|
158
|
+
const g = st.global;
|
|
159
|
+
io.stdout.write(`global: ${g.liveSessions}/${g.caps.maxConcurrentSessions} sessions open · $${g.today.modelUsd.toFixed(2)}/$${g.caps.modelUsdPerDay} model spend today · ${g.today.candidateSessions}/${g.caps.candidateSessionsPerDay} candidate sessions · load ${g.load.load1m ?? '—'}${g.load.paused ? ' (sessions paused: machine busy)' : ''}${g.blocked ? ` · blocked: ${g.blocked}` : ''}\n\n`);
|
|
160
|
+
for (const l of compareTable(compareRows(st)))
|
|
161
|
+
io.stdout.write(`${l}\n`);
|
|
162
|
+
const acct = accountTable(st);
|
|
163
|
+
if (acct.length)
|
|
164
|
+
io.stdout.write(`\npaper accounts (shared simulated wallets):\n${acct.join('\n')}\n`);
|
|
165
|
+
const tot = fleetTotals(st);
|
|
166
|
+
io.stdout.write(`\ntogether: ${tot.pnlUsd >= 0 ? '+' : '-'}$${Math.abs(tot.pnlUsd).toFixed(2)}${tot.pnlPct === null ? '' : ` (${tot.pnlPct >= 0 ? '+' : ''}${tot.pnlPct}%)`} on $${tot.startUsd.toFixed(2)} · ${tot.closed} closed · ${tot.open} open · $${tot.spendUsd.toFixed(4)} model spend. Paper, simulated fills; not financial advice.\n`);
|
|
167
|
+
const missed = st.members.flatMap((m) => m.stats.missedSample.map((x) => `${m.id}: ${x.symbol} ${x.x}x`));
|
|
168
|
+
if (missed.length)
|
|
169
|
+
io.stdout.write(`missed runners (seen, not entered, later >= 2x): ${missed.slice(0, 6).join(' · ')}\n`);
|
|
170
|
+
for (const m of st.members)
|
|
171
|
+
if (m.state === 'error')
|
|
172
|
+
io.stdout.write(`${m.id}: failed: ${m.error ?? 'unknown'}\n`);
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// The Telegram side of `dexterm autotrade run --paper` (task-1527, AUTOTRADE-SPEC §8). PAPER today.
|
|
2
|
+
//
|
|
3
|
+
// When `dexterm telegram setup` stored a token and a chat is paired, the runner starts the user's own bot in the
|
|
4
|
+
// same process: notify (the run ledger as it is written), control (/status /pause /kill …, through the same control
|
|
5
|
+
// file the CLI writes) and approve-to-trade (proposals with one-tap buttons; the order then goes through the paper
|
|
6
|
+
// port, which wraps the runner's paper executor and spends a single-use proof per approved order). A /use switch
|
|
7
|
+
// takes effect when the run has closed out its positions: the host starts the next run with the new strategy.
|
|
8
|
+
// The PnL card of a closed trade is the app's card renderer over the position's ledger fills (USD book, priced in
|
|
9
|
+
// SOL with the coin's own DexScreener SOL quote at the close; no image when there is none).
|
|
10
|
+
// task-1571: the bot is the user's OWN bot and the port is always the paper port. dexterm holds no key and places no
|
|
11
|
+
// live order; an approval only ever releases a PAPER order.
|
|
12
|
+
import { PaperPort, TelegramConnector, TelegramService, loadConnectors, secretStore, } from '../../../autotrade/src/index.js';
|
|
13
|
+
/** Why a Telegram approval is refused for a strategy that is not paper (dexterm places no live order). */
|
|
14
|
+
export const LIVE_APPROVALS_REFUSED = 'live-approval-port-not-installed';
|
|
15
|
+
export class TelegramHost {
|
|
16
|
+
service;
|
|
17
|
+
port;
|
|
18
|
+
runId = null;
|
|
19
|
+
strategy = null;
|
|
20
|
+
state = null;
|
|
21
|
+
next = null;
|
|
22
|
+
execRef;
|
|
23
|
+
/** SOL/USD near a coin for the card, set per run (a /use switch starts a new data layer). */
|
|
24
|
+
solUsdFor;
|
|
25
|
+
constructor(o, connector, knownToken = null) {
|
|
26
|
+
this.execRef = { exec: o.exec };
|
|
27
|
+
this.solUsdFor = o.solUsdFor ?? null;
|
|
28
|
+
const exec = { name: o.exec.name, buy: (k, usd, ctx) => this.execRef.exec.buy(k, usd, ctx), sell: (k, t, r) => this.execRef.exec.sell(k, t, r) };
|
|
29
|
+
// A Telegram approval is a paper proof: every approval for a strategy that is not mode "paper" is refused here
|
|
30
|
+
// (notify and /kill keep working).
|
|
31
|
+
this.port = new PaperPort(exec, { now: o.now, entryBlock: () => (this.strategy && this.strategy.mode !== 'paper' ? LIVE_APPROVALS_REFUSED : null) });
|
|
32
|
+
this.service = new TelegramService({
|
|
33
|
+
home: o.home, connector, port: this.port, now: o.now, timers: o.timers, knownToken: () => knownToken,
|
|
34
|
+
runId: () => this.runId, state: () => this.state, strategy: () => this.strategy,
|
|
35
|
+
onSwitch: (s) => { this.next = s; o.say(`telegram: switch to ${s.id} v${s.version} confirmed; it starts once nothing is held`); },
|
|
36
|
+
alert: (line) => o.say(`! ${line}`),
|
|
37
|
+
card: (t) => (this.solUsdFor ? autotradeCardPng(t, this.solUsdFor) : Promise.resolve(null)), // the card is a paper card
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** Point the host at the run that is starting (a /use switch starts a new one in the same process). */
|
|
41
|
+
attach(runId, s, exec, solUsdFor) {
|
|
42
|
+
this.runId = runId;
|
|
43
|
+
this.strategy = s;
|
|
44
|
+
this.state = null;
|
|
45
|
+
this.next = null;
|
|
46
|
+
this.execRef.exec = exec;
|
|
47
|
+
if (solUsdFor)
|
|
48
|
+
this.solUsdFor = solUsdFor;
|
|
49
|
+
}
|
|
50
|
+
onLedger = (e) => this.service.onLedger(e);
|
|
51
|
+
onState = (st) => { this.state = st; this.service.onState(st); };
|
|
52
|
+
/** The strategy a confirmed /use asked for, once (null when none). */
|
|
53
|
+
takeSwitch() { const n = this.next; this.next = null; return n; }
|
|
54
|
+
async stop() { await this.service.stop(); }
|
|
55
|
+
}
|
|
56
|
+
/** Start the bot for a run when it is set up and paired; `quiet` = it is simply not set up (say nothing). */
|
|
57
|
+
export async function startTelegramHost(o) {
|
|
58
|
+
const r = loadConnectors(o.home);
|
|
59
|
+
if (!r.ok)
|
|
60
|
+
return { ok: false, why: r.error, quiet: false };
|
|
61
|
+
const t = r.config.telegram;
|
|
62
|
+
if (!t.enabled || !t.allow.length)
|
|
63
|
+
return { ok: false, why: 'telegram is not set up (dexterm telegram setup)', quiet: !t.enabled && !r.exists };
|
|
64
|
+
let token = null;
|
|
65
|
+
let connector = o.connector;
|
|
66
|
+
if (!connector) {
|
|
67
|
+
const store = o.store ?? (t.secretStore ? secretStore(t.secretStore, o.home) : null);
|
|
68
|
+
token = store?.get() ?? null;
|
|
69
|
+
if (!token)
|
|
70
|
+
return { ok: false, why: 'the bot token is missing from the secret store (dexterm telegram setup)', quiet: false };
|
|
71
|
+
const tk = token;
|
|
72
|
+
connector = new TelegramConnector({ token: () => tk, onLog: (l) => o.say(`telegram: ${l}`) });
|
|
73
|
+
}
|
|
74
|
+
const host = new TelegramHost(o, connector, token);
|
|
75
|
+
await host.service.start();
|
|
76
|
+
return { ok: true, host };
|
|
77
|
+
}
|
|
78
|
+
/** The PnL card PNG of an autotrade paper trade (pnl-card.ts over the position's fills), or null. */
|
|
79
|
+
export async function autotradeCardPng(t, solUsdFor) {
|
|
80
|
+
const fills = t.fills ?? [];
|
|
81
|
+
if (!fills.some((f) => f.side === 'buy'))
|
|
82
|
+
return null;
|
|
83
|
+
const solUsd = await solUsdFor(t.mint).catch(() => null);
|
|
84
|
+
if (!solUsd || !(solUsd > 0))
|
|
85
|
+
return null;
|
|
86
|
+
const card = await import("../pnl-card.js");
|
|
87
|
+
const DEC = 6;
|
|
88
|
+
const input = {
|
|
89
|
+
source: 'paper', mint: t.mint, symbol: t.symbol, name: null, decimals: DEC,
|
|
90
|
+
fills: fills.map((f) => ({ t: f.t, side: f.side, solDelta: (f.side === 'buy' ? -1 : 1) * (f.usd / solUsd), tokenDelta: BigInt(Math.round(f.tokens * 10 ** DEC)) * (f.side === 'buy' ? 1n : -1n), feeSol: null, sig: null })),
|
|
91
|
+
ledger: `dexterm autotrade paper run (USD book, SOL at $${solUsd.toFixed(2)})`, wallet: null, mark: null,
|
|
92
|
+
excluded: [], fees: 'modelled fee, slippage and price impact included',
|
|
93
|
+
};
|
|
94
|
+
try {
|
|
95
|
+
const stats = card.computeCard(input, t.closedAt);
|
|
96
|
+
return new Uint8Array(card.cardPng(stats, { aspect: 'wide' }));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// `dexterm autotrade <sub>` (task-1503 AT-1, AUTOTRADE-SPEC). PAPER ONLY: no keys, no wallet, no signer.
|
|
2
|
+
//
|
|
3
|
+
// A dispatcher table: each subcommand is one row that lazy-loads its own module under ./autotrade-cmd/, so
|
|
4
|
+
// later cards add a row (run, service, stop, flatten, audit) without editing anyone else's lines.
|
|
5
|
+
// `dexterm backtest --subject strategy …` is routed here too (the strategy subject runs on this engine).
|
|
6
|
+
export const SUBCOMMANDS = {
|
|
7
|
+
list: {
|
|
8
|
+
usage: 'dexterm autotrade list [--json]',
|
|
9
|
+
summary: 'your strategies (~/.dexterm/strategies) and the shipped presets: version, hash, mode, profiles, last backtest',
|
|
10
|
+
load: () => import("./autotrade-cmd/strategies.js").then((m) => m.listCmd),
|
|
11
|
+
},
|
|
12
|
+
validate: {
|
|
13
|
+
usage: 'dexterm autotrade validate <id|file> [--json]',
|
|
14
|
+
summary: 'check a strategy file or a stored strategy; every problem is printed with its path',
|
|
15
|
+
load: () => import("./autotrade-cmd/strategies.js").then((m) => m.validateCmd),
|
|
16
|
+
},
|
|
17
|
+
new: {
|
|
18
|
+
usage: 'dexterm autotrade new <new-id> --from <preset|id> [--name TEXT]',
|
|
19
|
+
summary: 'copy a preset (or one of your strategies) into ~/.dexterm/strategies/<new-id>.json to edit',
|
|
20
|
+
load: () => import("./autotrade-cmd/strategies.js").then((m) => m.newCmd),
|
|
21
|
+
},
|
|
22
|
+
import: {
|
|
23
|
+
usage: 'dexterm autotrade import <file> [--replace]',
|
|
24
|
+
summary: 'validate a strategy file and store it (mode is forced to paper; --replace needs a higher version)',
|
|
25
|
+
load: () => import("./autotrade-cmd/strategies.js").then((m) => m.importCmd),
|
|
26
|
+
},
|
|
27
|
+
remove: {
|
|
28
|
+
usage: 'dexterm autotrade remove <id>',
|
|
29
|
+
summary: 'move one of your strategies to ~/.dexterm/strategies/.removed/ (journaled; presets cannot be removed)',
|
|
30
|
+
load: () => import("./autotrade-cmd/strategies.js").then((m) => m.removeCmd),
|
|
31
|
+
},
|
|
32
|
+
journal: {
|
|
33
|
+
usage: 'dexterm autotrade journal [<id>] [--json]',
|
|
34
|
+
summary: 'the append-only change journal: who changed what, with the before and after hashes',
|
|
35
|
+
load: () => import("./autotrade-cmd/strategies.js").then((m) => m.journalCmd),
|
|
36
|
+
},
|
|
37
|
+
backtest: {
|
|
38
|
+
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]',
|
|
39
|
+
summary: 'replay a strategy over recorded history with modelled fills (paper); the limits of the data are always printed',
|
|
40
|
+
load: () => import("./autotrade-cmd/backtest.js").then((m) => m.backtestCmd),
|
|
41
|
+
},
|
|
42
|
+
sweep: {
|
|
43
|
+
usage: 'dexterm autotrade sweep <id|file> [--tape FILE | --db PATH] [--grid JSON] [--mode grid|random] [--max N] [--seed N] [--json]',
|
|
44
|
+
summary: 'tune on the older coins, check once on the newest 30% it never saw; no gain wording without enough trades and a held-out profit',
|
|
45
|
+
load: () => import("./autotrade-cmd/backtest.js").then((m) => m.sweepCmd),
|
|
46
|
+
},
|
|
47
|
+
'yolo-replay': {
|
|
48
|
+
usage: 'dexterm autotrade yolo-replay <id|file> [--tape FILE | --db PATH [--days N | --since T] [--until T]] [--seed N] [--json]',
|
|
49
|
+
summary: 'HIGH RISK check before arming a full-port strategy: replay it at 100% of the book over recorded history (paper): rugs, the loss spread, how often it zeroed the wallet',
|
|
50
|
+
load: () => import("./autotrade-cmd/backtest.js").then((m) => m.yoloReplayCmd),
|
|
51
|
+
},
|
|
52
|
+
requests: {
|
|
53
|
+
usage: 'dexterm autotrade requests [list [--all] [--json] | run <br_id> | dismiss <br_id>]',
|
|
54
|
+
summary: 'backtests an agent asked for over MCP (backtest_request): nothing runs until you run one here (paper, recorded data)',
|
|
55
|
+
load: () => import("./autotrade-cmd/requests.js").then((m) => m.requestsCmd),
|
|
56
|
+
},
|
|
57
|
+
run: {
|
|
58
|
+
usage: 'dexterm autotrade run <id|file> --paper [--replay TAPE [--ms-per-tick 2000]] [--model M] [--max-minutes N] [--stub] [--raw] [--quiet] [--no-telegram]',
|
|
59
|
+
summary: 'the paper runner: the screener hands each candidate to your own Claude (runner.json picks the account), every size clamped; ledger in autotrade/runs/; your paired Telegram bot starts with it (dexterm telegram setup)',
|
|
60
|
+
load: () => import("./autotrade-cmd/run.js").then((m) => m.runCmd),
|
|
61
|
+
},
|
|
62
|
+
'run --all': {
|
|
63
|
+
usage: 'dexterm autotrade run --all --paper [--config FILE] [--replay TAPE] [--sol-usd N] [--stub] [--max-minutes N]',
|
|
64
|
+
summary: 'every strategy of autotrade/autotrade.json at once on one data feed: each its own bankroll, ledger, caps and budget, under global caps',
|
|
65
|
+
load: () => import("./autotrade-cmd/fleet.js").then((m) => m.fleetRunCmd),
|
|
66
|
+
},
|
|
67
|
+
status: {
|
|
68
|
+
usage: 'dexterm autotrade status [--all] [--json]',
|
|
69
|
+
summary: 'the paper runner now; --all: every strategy of a parallel run side by side (P/L, win rate, rug exposure, missed runners, spend per entry)',
|
|
70
|
+
load: () => import("./autotrade-cmd/strategy-control.js").then((m) => m.statusCmd),
|
|
71
|
+
},
|
|
72
|
+
start: {
|
|
73
|
+
usage: 'dexterm autotrade start <strategy> [--wait SEC] [--json]',
|
|
74
|
+
summary: 'start one strategy of autotrade.json inside the running `run --all` (paper, its own book and caps)',
|
|
75
|
+
load: () => import("./autotrade-cmd/strategy-control.js").then((m) => m.startCmd),
|
|
76
|
+
},
|
|
77
|
+
pause: {
|
|
78
|
+
usage: 'dexterm autotrade pause [--strategy ID] [--json]',
|
|
79
|
+
summary: 'no new entries (every strategy, or just one with --strategy); open positions keep their exits',
|
|
80
|
+
load: () => import("./autotrade-cmd/strategy-control.js").then((m) => m.pauseCmd),
|
|
81
|
+
},
|
|
82
|
+
resume: {
|
|
83
|
+
usage: 'dexterm autotrade resume [--strategy ID] [--json]',
|
|
84
|
+
summary: 'lift a pause (every strategy, or just one with --strategy); a stop is final and is never lifted',
|
|
85
|
+
load: () => import("./autotrade-cmd/strategy-control.js").then((m) => m.resumeCmd),
|
|
86
|
+
},
|
|
87
|
+
stop: {
|
|
88
|
+
usage: 'dexterm autotrade stop [--strategy ID] [--wait SEC] [--json]',
|
|
89
|
+
summary: 'soft-stop the paper runner: no new entries, sessions closed, orders dropped; mechanical exits keep running until nothing is held',
|
|
90
|
+
load: () => import("./autotrade-cmd/stop.js").then((m) => m.stopCmd),
|
|
91
|
+
},
|
|
92
|
+
flatten: {
|
|
93
|
+
usage: 'dexterm autotrade flatten [--strategy ID] [--yes] [--wait SEC] [--json]',
|
|
94
|
+
summary: 'stop, then sell every paper position at its next tick (confirm with y on a terminal, or --yes)',
|
|
95
|
+
load: () => import("./autotrade-cmd/flatten.js").then((m) => m.flattenCmd),
|
|
96
|
+
},
|
|
97
|
+
audit: {
|
|
98
|
+
usage: 'dexterm autotrade audit [<run-id>|--last] [--verify] [--json]',
|
|
99
|
+
summary: 'list the run ledgers; --verify recomputes each hash chain and names the first edited, missing or reordered event',
|
|
100
|
+
load: () => import("./autotrade-cmd/audit.js").then((m) => m.auditCmd),
|
|
101
|
+
},
|
|
102
|
+
extract: {
|
|
103
|
+
usage: 'dexterm autotrade extract --out FILE [--db PATH] [--max-mints N] [--min-peak-liq USD] [--span-hours H]',
|
|
104
|
+
summary: 'save a replay tape from your recorder history (read-only) to a file you can backtest offline',
|
|
105
|
+
load: () => import("./autotrade-cmd/backtest.js").then((m) => m.extractCmd),
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
export function autotradeHelp() {
|
|
109
|
+
return [
|
|
110
|
+
'dexterm autotrade: strategies, backtests and the paper runner. PAPER ONLY, not financial advice.',
|
|
111
|
+
'',
|
|
112
|
+
...Object.values(SUBCOMMANDS).map((s) => ` ${s.usage}\n ${s.summary}`),
|
|
113
|
+
'',
|
|
114
|
+
' Strategies live in $DEXTERM_HOME/strategies (default ~/.dexterm/strategies), one JSON file each, with an',
|
|
115
|
+
' append-only .journal.jsonl. Presets: scalp, swing, runner, moonbag, sample-dip-ladder, yolo (HIGH RISK) (read-only; copy one with `new`).',
|
|
116
|
+
' `dexterm backtest --subject strategy --strategy <id|file>` runs the same backtest.',
|
|
117
|
+
].join('\n') + '\n';
|
|
118
|
+
}
|
|
119
|
+
/** The value of `--subject` (or `--subject=x`) in an argv, if any. */
|
|
120
|
+
function subjectOf(argv) {
|
|
121
|
+
for (let i = 0; i < argv.length; i++) {
|
|
122
|
+
if (argv[i] === '--subject')
|
|
123
|
+
return argv[i + 1];
|
|
124
|
+
if (argv[i].startsWith('--subject='))
|
|
125
|
+
return argv[i].slice('--subject='.length);
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
/** True for `dexterm autotrade …` and `dexterm backtest --subject strategy …`. */
|
|
130
|
+
export function handles(argv) {
|
|
131
|
+
return argv[0] === 'autotrade' || (argv[0] === 'backtest' && subjectOf(argv) === 'strategy');
|
|
132
|
+
}
|
|
133
|
+
export async function autotradeCommand(argv, io) {
|
|
134
|
+
let rest;
|
|
135
|
+
if (argv[0] === 'backtest') {
|
|
136
|
+
// `dexterm backtest --subject strategy [--strategy X] …` → `dexterm autotrade backtest [X] …`
|
|
137
|
+
rest = ['backtest'];
|
|
138
|
+
for (let i = 1; i < argv.length; i++) {
|
|
139
|
+
if (argv[i] === '--subject') {
|
|
140
|
+
i++;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (argv[i].startsWith('--subject='))
|
|
144
|
+
continue;
|
|
145
|
+
rest.push(argv[i]);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else
|
|
149
|
+
rest = argv.slice(1);
|
|
150
|
+
const sub = rest[0];
|
|
151
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
152
|
+
io.stdout.write(autotradeHelp());
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
// `run --all` has its own row (parallel strategies, task-1529) so its --help shows its own flags.
|
|
156
|
+
const key = sub === 'run' && rest.includes('--all') ? 'run --all' : sub;
|
|
157
|
+
const row = Object.hasOwn(SUBCOMMANDS, key) ? SUBCOMMANDS[key] : undefined;
|
|
158
|
+
if (!row) {
|
|
159
|
+
io.stderr.write(`dexterm autotrade: unknown subcommand ${JSON.stringify(sub)}\n\n${autotradeHelp()}`);
|
|
160
|
+
return 2;
|
|
161
|
+
}
|
|
162
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
163
|
+
io.stdout.write(`${row.usage}\n ${row.summary}\n`);
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
const fn = await row.load();
|
|
167
|
+
return fn(rest.slice(1), io);
|
|
168
|
+
}
|