dexterm-cli 0.1.6 → 0.2.0
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/spectator.js +3 -1
- package/dist/packages/agents/src/story.js +11 -2
- package/dist/packages/agents/src/tiers.js +36 -2
- package/dist/packages/agents/src/tools.js +35 -2
- package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
- package/dist/packages/autotrade/assets/limits.schema.json +569 -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/examples/executor/README.md +70 -0
- package/dist/packages/autotrade/examples/executor/jsonl-tail-executor.mjs +57 -0
- package/dist/packages/autotrade/examples/executor/kit.mjs +144 -0
- package/dist/packages/autotrade/examples/executor/signal-cmd-executor.mjs +25 -0
- package/dist/packages/autotrade/examples/executor/telegram-bot-recipe.md +47 -0
- package/dist/packages/autotrade/examples/executor/webhook-executor.mjs +47 -0
- package/dist/packages/autotrade/src/account.js +74 -0
- package/dist/packages/autotrade/src/backtest.js +358 -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 +95 -0
- package/dist/packages/autotrade/src/chat/connector.js +24 -0
- package/dist/packages/autotrade/src/chat/control.js +104 -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 +302 -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 +106 -0
- package/dist/packages/autotrade/src/chat/service.js +251 -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 +401 -0
- package/dist/packages/autotrade/src/execution-port.js +97 -0
- package/dist/packages/autotrade/src/executor-health.js +217 -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 +642 -0
- package/dist/packages/autotrade/src/forward.js +355 -0
- package/dist/packages/autotrade/src/index.js +72 -0
- package/dist/packages/autotrade/src/journey.js +477 -0
- package/dist/packages/autotrade/src/ledger.js +108 -0
- package/dist/packages/autotrade/src/limits.js +682 -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/outbox.js +394 -0
- package/dist/packages/autotrade/src/paper-accounts.js +72 -0
- package/dist/packages/autotrade/src/paper-limits.js +87 -0
- package/dist/packages/autotrade/src/real-mode.js +688 -0
- package/dist/packages/autotrade/src/receipts.js +330 -0
- package/dist/packages/autotrade/src/replay.js +198 -0
- package/dist/packages/autotrade/src/report.js +168 -0
- package/dist/packages/autotrade/src/runner-config.js +99 -0
- package/dist/packages/autotrade/src/runner.js +923 -0
- package/dist/packages/autotrade/src/safety.js +243 -0
- package/dist/packages/autotrade/src/service-guard.js +125 -0
- package/dist/packages/autotrade/src/service-install.js +213 -0
- package/dist/packages/autotrade/src/service-recover.js +232 -0
- package/dist/packages/autotrade/src/service.js +289 -0
- package/dist/packages/autotrade/src/session.js +269 -0
- package/dist/packages/autotrade/src/sessions.js +503 -0
- package/dist/packages/autotrade/src/signal-channels.js +312 -0
- package/dist/packages/autotrade/src/signal.js +196 -0
- package/dist/packages/autotrade/src/store.js +250 -0
- package/dist/packages/autotrade/src/strategy.js +258 -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 +83 -15
- package/dist/packages/cli/src/analyst/codex.js +15 -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 +56 -8
- package/dist/packages/cli/src/autotrade-cmd/audit.js +74 -0
- package/dist/packages/cli/src/autotrade-cmd/backtest.js +216 -0
- package/dist/packages/cli/src/autotrade-cmd/executor.js +91 -0
- package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
- package/dist/packages/cli/src/autotrade-cmd/fleet.js +401 -0
- package/dist/packages/cli/src/autotrade-cmd/forward.js +283 -0
- package/dist/packages/cli/src/autotrade-cmd/journey.js +208 -0
- package/dist/packages/cli/src/autotrade-cmd/limits.js +393 -0
- package/dist/packages/cli/src/autotrade-cmd/receipt.js +63 -0
- package/dist/packages/cli/src/autotrade-cmd/report.js +79 -0
- package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
- package/dist/packages/cli/src/autotrade-cmd/run.js +657 -0
- package/dist/packages/cli/src/autotrade-cmd/service.js +873 -0
- package/dist/packages/cli/src/autotrade-cmd/signal.js +824 -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 +214 -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 +290 -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 +162 -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-journey.js +134 -0
- package/dist/packages/cli/src/screens/autotrade-keys.js +182 -0
- package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
- package/dist/packages/cli/src/screens/autotrade-screen.js +776 -0
- package/dist/packages/cli/src/screens/autotrade-ui.js +650 -0
- package/dist/packages/cli/src/screens/autotrade-worker.js +91 -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 +454 -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-executor-kit.md +44 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-journey.md +46 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-limits.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-real-signal.md +45 -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 +229 -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 +51 -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 +38 -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 +37 -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-always-on-service.md +57 -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 +50 -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-build-a-strategy-by-chat-1-ask-for-a-strategy.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-2-backtest-it-held-out-split.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-3-forward-test-it-on-live-data.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-4-promote-it-to-always-on-paper.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-5-real-only-if-you-choose.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-ladder.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-report.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-what-the-analyst-knows.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat.md +24 -0
- 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-going-real-executor-health.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-paper-vs-real.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-digest.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-executor-kit.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-whole-path.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-what-dexterm-does-and-never-does.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-your-limits.md +77 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real.md +24 -0
- 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-real-mode-your-strategy-s-trades-to-your.md +31 -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 +31 -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-real-mode-receipts-what-your-executor-did.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-safety.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-set-it-up.md +47 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-channels.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-signal.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-is.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-will-not-do.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-where-you-see-it.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode.md +27 -0
- 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 +40 -0
- package/dist/packages/mcp/src/app-help.js +1 -1
- package/dist/packages/mcp/src/autotrade-tools.js +1107 -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 +494 -0
- package/dist/packages/mcp/src/paper-agent.js +2 -0
- package/dist/packages/mcp/src/server.js +138 -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 +28 -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,494 @@
|
|
|
1
|
+
// @dexterm/mcp — the TRADE LEDGER tools and their neighbours (task-1530). Read-only, plus one PROPOSE tool.
|
|
2
|
+
//
|
|
3
|
+
// ledger_trades observe trades from the unified ledger (paper, desk, autotrade, live), filtered, newest first
|
|
4
|
+
// ledger_positions observe the open trades (cost, held time, profile); no price fetch, no balance
|
|
5
|
+
// ledger_equity observe the realized equity curve and its drawdown
|
|
6
|
+
// ledger_summary observe win rate (with its 95 % interval), P/L, drawdown, rug rate, per-profile / exit / strategy
|
|
7
|
+
// ledger_compare observe one row per strategy version: the forward-test table
|
|
8
|
+
// candidate_dossier observe what an autotrade run recorded about a candidate: the rules it matched, the tick,
|
|
9
|
+
// the session's decisions, the clamp, the fills and the close
|
|
10
|
+
// outcomes_lookup observe the recorder's outcome labels (rug / dead / fade / flat / runner) for a coin, or for
|
|
11
|
+
// every candidate of a run (what the skips turned into)
|
|
12
|
+
// telegram_status observe the Telegram connector's settings: enabled, paired chats (a count), approvals, notify
|
|
13
|
+
// trading_status observe where live trading happens (task-1571: not in dexterm; it holds no key and places no
|
|
14
|
+
// live order), whether the panic switch is engaged, and real mode's state (task-1645)
|
|
15
|
+
// trade_fire_feed observe task-1645: the MCP channel of real mode: the LIVE TRADE FIRE signals of the feed
|
|
16
|
+
// (autotrade/real/mcp-feed.jsonl) after a nonce, and real mode's status. Reading is all it
|
|
17
|
+
// does: no tool arms real mode, changes a cap, lifts the kill switch or records a receipt
|
|
18
|
+
// trading_limits observe task-1654: real mode's limits in force (for a strategy / account / mode), where each
|
|
19
|
+
// number comes from, what each limit does, today's counters and the executor's health
|
|
20
|
+
// backtest_requests observe the backtests this agent asked for, and their results once the user ran them
|
|
21
|
+
// backtest_request propose ask the user to run a longer backtest (a request file; the user runs or dismisses it)
|
|
22
|
+
// limits_propose propose task-1654: a limits change as a DIFF CARD (old -> new, which numbers it raises, the
|
|
23
|
+
// exact command the user would type). It writes nothing at all: only the user applies a
|
|
24
|
+
// change, on their own terminal (`dexterm autotrade limits set`; a raise is typed)
|
|
25
|
+
//
|
|
26
|
+
// Nothing here can trade, sign, change a cap, enable live or touch a key: the ledger is read through
|
|
27
|
+
// `@dexterm/autotrade`'s loader (pure file reads, loaded lazily: @dexterm/autotrade imports this package). Text that
|
|
28
|
+
// came from a token or an agent is firewalled before it is returned.
|
|
29
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
30
|
+
import { join } from 'node:path';
|
|
31
|
+
import { cleanLedgerText, compareStrategies, equityCurve, filterTrades, summarize } from '../../data/src/trade/ledger/index.js';
|
|
32
|
+
import { firewall } from "./firewall.js";
|
|
33
|
+
import { dextermHome } from "./user-content.js";
|
|
34
|
+
import { BacktestRequests, MAX_PENDING_REQUESTS, REQUEST_MAX_DAYS, REQUEST_RATE_PER_HOUR } from "./backtest-requests.js";
|
|
35
|
+
export const LEDGER_READ_TOOLS = [
|
|
36
|
+
'ledger_trades', 'ledger_positions', 'ledger_equity', 'ledger_summary', 'ledger_compare',
|
|
37
|
+
'candidate_dossier', 'outcomes_lookup', 'telegram_status', 'trading_status', 'trade_fire_feed', 'trading_limits', 'backtest_requests',
|
|
38
|
+
];
|
|
39
|
+
/** Write-shaped (a request file), so not on the read-only surface a skill may name. */
|
|
40
|
+
export const LEDGER_PROPOSE_TOOLS = ['backtest_request', 'limits_propose'];
|
|
41
|
+
export const LEDGER_TOOLS = [...LEDGER_READ_TOOLS, ...LEDGER_PROPOSE_TOOLS];
|
|
42
|
+
const MINT = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
43
|
+
const RUN_ID = /^[0-9TZ]+-[a-z][a-z0-9-]{1,31}-[0-9a-f]{4}$/;
|
|
44
|
+
const DAY_MS = 86_400_000;
|
|
45
|
+
const fw = (s, kind) => (s ? firewall(s, 'ledger', kind) : null);
|
|
46
|
+
const iso = (t) => (typeof t === 'number' && Number.isFinite(t) ? new Date(t < 1e12 ? t * 1000 : t).toISOString() : null);
|
|
47
|
+
/** The compact, firewalled view of one trade an agent gets (fills only when asked). */
|
|
48
|
+
export function tradeView(t, o = {}) {
|
|
49
|
+
return {
|
|
50
|
+
tradeId: t.tradeId, source: t.source, walletMode: t.walletMode, status: t.status, mint: t.mint, symbol: fw(t.symbol, 'symbol'),
|
|
51
|
+
strategy: t.strategy, runId: t.runId, account: t.account ?? null,
|
|
52
|
+
profile: t.profile, reprofiles: t.reprofiles.map((r) => ({ at: iso(r.t), from: r.from, to: r.to, reason: fw(r.reason, 'reason') })),
|
|
53
|
+
entryReason: { text: fw(t.entryReason.text, 'reason'), source: t.entryReason.source },
|
|
54
|
+
exitReason: { code: t.exitReason.code, text: fw(t.exitReason.text, 'reason'), source: t.exitReason.source },
|
|
55
|
+
openedAt: iso(t.openedAt), closedAt: iso(t.closedAt), holdSec: t.holdSec,
|
|
56
|
+
sizeSol: t.sizeSol, sizeUsd: t.sizeUsd, pctOfBook: t.pctOfBook,
|
|
57
|
+
entry: { priceSol: t.entryPriceSol, priceUsd: t.entryPriceUsd, mcapUsd: t.entryMcapUsd },
|
|
58
|
+
exit: { priceSol: t.exitPriceSol, priceUsd: t.exitPriceUsd, mcapUsd: t.exitMcapUsd },
|
|
59
|
+
supply: t.supply, pnlSol: t.pnlSol, pnlUsd: t.pnlUsd, pnlPct: t.pnlPct,
|
|
60
|
+
feesSol: t.feesSol, impactPct: { entry: t.impactPctEntry, exit: t.impactPctExit }, slippageBps: t.slippageBps,
|
|
61
|
+
mfePct: t.mfePct, maePct: t.maePct, excursionBasis: t.excursionBasis, rug: t.rug, rugWhy: t.rugWhy, stuck: t.stuck,
|
|
62
|
+
txSigs: t.txSigs, model: t.model,
|
|
63
|
+
...(o.fills ? { fills: t.fills.map((f) => ({ ...f, at: iso(f.t) })) } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Tool args → a ledger filter. Unknown values are refused, never guessed. */
|
|
67
|
+
export function filterFromArgs(a, now) {
|
|
68
|
+
const f = {};
|
|
69
|
+
const pick = (k, allowed) => {
|
|
70
|
+
if (a[k] === undefined)
|
|
71
|
+
return undefined;
|
|
72
|
+
return allowed.includes(a[k]) ? a[k] : false;
|
|
73
|
+
};
|
|
74
|
+
const source = pick('source', ['paper', 'desk', 'autotrade', 'live']);
|
|
75
|
+
const mode = pick('mode', ['paper', 'devnet', 'live', 'real']);
|
|
76
|
+
const status = pick('status', ['open', 'closed']);
|
|
77
|
+
if (source === false || mode === false || status === false)
|
|
78
|
+
return { ok: false, error: 'source is paper|desk|autotrade|live, mode is paper|devnet|live|real, status is open|closed' };
|
|
79
|
+
if (source)
|
|
80
|
+
f.source = source;
|
|
81
|
+
if (mode)
|
|
82
|
+
f.mode = mode;
|
|
83
|
+
if (status)
|
|
84
|
+
f.status = status;
|
|
85
|
+
for (const [k, key, re] of [['strategy', 'strategy', /^[a-z][a-z0-9-]{0,31}(@[0-9a-z.]{1,16})?$/], ['profile', 'profile', /^[a-z][a-z0-9-]{0,31}$/], ['exit_reason', 'exitReason', /^[a-z][a-z0-9-]{0,31}$/]]) {
|
|
86
|
+
if (a[k] === undefined)
|
|
87
|
+
continue;
|
|
88
|
+
if (typeof a[k] !== 'string' || !re.test(a[k]))
|
|
89
|
+
return { ok: false, error: `${k} is not a valid id` };
|
|
90
|
+
f[key] = a[k];
|
|
91
|
+
}
|
|
92
|
+
if (a.mint !== undefined) {
|
|
93
|
+
if (typeof a.mint !== 'string' || !MINT.test(a.mint))
|
|
94
|
+
return { ok: false, error: 'mint is not a Solana address' };
|
|
95
|
+
f.mint = a.mint;
|
|
96
|
+
}
|
|
97
|
+
if (a.run_id !== undefined) {
|
|
98
|
+
if (typeof a.run_id !== 'string' || !RUN_ID.test(a.run_id))
|
|
99
|
+
return { ok: false, error: 'run_id is not a run id' };
|
|
100
|
+
f.runId = a.run_id;
|
|
101
|
+
}
|
|
102
|
+
if (a.account !== undefined) {
|
|
103
|
+
if (typeof a.account !== 'string' || !/^[a-z][a-z0-9-]{1,31}$/.test(a.account))
|
|
104
|
+
return { ok: false, error: 'account is not a paper account id' };
|
|
105
|
+
f.account = a.account;
|
|
106
|
+
}
|
|
107
|
+
if (a.days !== undefined) {
|
|
108
|
+
const d = Number(a.days);
|
|
109
|
+
if (!Number.isFinite(d) || d <= 0 || d > 3650)
|
|
110
|
+
return { ok: false, error: 'days is a number from 0 to 3650' };
|
|
111
|
+
f.since = now - d * DAY_MS;
|
|
112
|
+
}
|
|
113
|
+
return { ok: true, filter: f };
|
|
114
|
+
}
|
|
115
|
+
export class LedgerTools {
|
|
116
|
+
env;
|
|
117
|
+
actor;
|
|
118
|
+
now;
|
|
119
|
+
requestTimes = [];
|
|
120
|
+
o;
|
|
121
|
+
constructor(o = {}) {
|
|
122
|
+
this.o = o;
|
|
123
|
+
this.env = o.env ?? process.env;
|
|
124
|
+
this.actor = o.actor && /^[a-z][a-z0-9:._-]{0,63}$/.test(o.actor) ? o.actor : 'mcp-agent';
|
|
125
|
+
this.now = o.now ?? Date.now;
|
|
126
|
+
}
|
|
127
|
+
home() { return dextermHome(this.env); }
|
|
128
|
+
async view() {
|
|
129
|
+
if (this.o.view)
|
|
130
|
+
return this.o.view();
|
|
131
|
+
const at = await import('../../autotrade/src/index.js');
|
|
132
|
+
return at.loadLedgerView({ env: this.env, now: this.now() });
|
|
133
|
+
}
|
|
134
|
+
async call(name, args) {
|
|
135
|
+
try {
|
|
136
|
+
switch (name) {
|
|
137
|
+
case 'ledger_trades':
|
|
138
|
+
case 'ledger_positions':
|
|
139
|
+
case 'ledger_equity':
|
|
140
|
+
case 'ledger_summary':
|
|
141
|
+
case 'ledger_compare': return await this.ledger(name, args);
|
|
142
|
+
case 'candidate_dossier': return await this.dossier(args);
|
|
143
|
+
case 'outcomes_lookup': return await this.outcomes(args);
|
|
144
|
+
case 'telegram_status': return this.telegram();
|
|
145
|
+
case 'trading_status': return this.trading();
|
|
146
|
+
case 'trade_fire_feed': return this.signals(args);
|
|
147
|
+
case 'trading_limits': return await this.limits(args);
|
|
148
|
+
case 'limits_propose': return await this.limitsPropose(args);
|
|
149
|
+
case 'backtest_requests': return this.requests();
|
|
150
|
+
case 'backtest_request': return await this.request(args);
|
|
151
|
+
default: return { error: `unknown ledger tool ${name}` };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
return { error: firewall(e instanceof Error ? e.message : String(e), 'ledger', 'error') ?? 'error' };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// ───────────────────────────── the ledger reads ─────────────────────────────
|
|
159
|
+
async ledger(name, args) {
|
|
160
|
+
const fr = filterFromArgs(args, this.now());
|
|
161
|
+
if (!fr.ok)
|
|
162
|
+
return { error: fr.error };
|
|
163
|
+
const v = await this.view();
|
|
164
|
+
const filter = name === 'ledger_positions' ? { ...fr.filter, status: 'open' } : fr.filter;
|
|
165
|
+
const rows = filterTrades(v.trades, filter);
|
|
166
|
+
const integrity = { ledger: v.file.check.ok ? `verified, ${v.file.events.length} events` : `FAILS its check at line ${v.file.check.at}: ${v.file.check.error}`, pendingFromSources: v.pending.length, problems: v.sources.filter((s) => s.problem).map((s) => s.problem) };
|
|
167
|
+
const modes = [...new Set(rows.map((t) => t.walletMode))];
|
|
168
|
+
const base = { readOnly: true, modes, integrity, note: modes.length > 1 ? 'PAPER, REAL, DEVNET and LIVE trades are in this set: filter by mode before comparing results.' : modes.includes('paper') ? 'PAPER (simulated) trades: never present them as results.' : modes.includes('real') ? 'REAL-mode trades: dexterm\'s model of the trades it signalled to the user\'s executor; the executor\'s receipts are the real fills.' : undefined };
|
|
169
|
+
if (name === 'ledger_trades' || name === 'ledger_positions') {
|
|
170
|
+
const limit = Math.min(200, Math.max(1, Number.isInteger(args.limit) ? Number(args.limit) : 50));
|
|
171
|
+
const newest = [...rows].sort((a, b) => b.openedAt - a.openedAt).slice(0, limit);
|
|
172
|
+
return { ...base, count: rows.length, shown: newest.length, trades: newest.map((t) => tradeView(t, { fills: args.fills === true })) };
|
|
173
|
+
}
|
|
174
|
+
if (name === 'ledger_equity') {
|
|
175
|
+
const c = equityCurve(rows);
|
|
176
|
+
const max = 200;
|
|
177
|
+
const step = Math.max(1, Math.ceil(c.points.length / max));
|
|
178
|
+
const pts = c.points.filter((_, i) => i % step === 0 || i === c.points.length - 1).map((p) => ({ at: iso(p.t), tradeId: p.tradeId, pnl: p.pnl, cum: p.cum, drawdown: p.drawdown }));
|
|
179
|
+
return { ...base, unit: c.unit, startingBook: c.start, final: c.final, maxDrawdown: c.maxDrawdown, maxDrawdownPct: c.maxDrawdownPct, points: pts, sampled: step > 1 ? `every ${step}th of ${c.points.length} closes` : undefined };
|
|
180
|
+
}
|
|
181
|
+
if (name === 'ledger_summary')
|
|
182
|
+
return { ...base, summary: summarize(rows) };
|
|
183
|
+
return { ...base, ...compareStrategies(rows), note: 'One row per strategy version; a small trade count means little (see winRate.lo / hi).' };
|
|
184
|
+
}
|
|
185
|
+
// ───────────────────────────── a run's record of one candidate ─────────────────────────────
|
|
186
|
+
async dossier(args) {
|
|
187
|
+
if (typeof args.mint !== 'string' || !MINT.test(args.mint))
|
|
188
|
+
return { error: 'mint is required (a Solana address)' };
|
|
189
|
+
if (args.run_id !== undefined && (typeof args.run_id !== 'string' || !RUN_ID.test(args.run_id)))
|
|
190
|
+
return { error: 'run_id is not a run id' };
|
|
191
|
+
const dir = join(this.home(), 'autotrade', 'runs');
|
|
192
|
+
if (!existsSync(dir))
|
|
193
|
+
return { found: false, note: 'no autotrade runs on this machine' };
|
|
194
|
+
const runs = readdirSync(dir).filter((f) => /\.jsonl$/.test(f)).map((f) => f.slice(0, -6)).filter((r) => RUN_ID.test(r)).sort().reverse();
|
|
195
|
+
const pick = args.run_id ? runs.filter((r) => r === args.run_id) : runs.slice(0, 30);
|
|
196
|
+
for (const runId of pick) {
|
|
197
|
+
const p = join(dir, `${runId}.jsonl`);
|
|
198
|
+
if (statSync(p).size > 64 * 1024 * 1024)
|
|
199
|
+
continue;
|
|
200
|
+
const evs = [];
|
|
201
|
+
for (const l of readFileSync(p, 'utf8').split('\n')) {
|
|
202
|
+
if (!l.trim())
|
|
203
|
+
continue;
|
|
204
|
+
try {
|
|
205
|
+
evs.push(JSON.parse(l));
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const cands = evs.filter((e) => e.kind === 'candidate' && e.mint === args.mint);
|
|
212
|
+
if (!cands.length)
|
|
213
|
+
continue;
|
|
214
|
+
const start = evs.find((e) => e.kind === 'run_start') ?? {};
|
|
215
|
+
const sids = new Set(evs.filter((e) => e.kind === 'session_start' && e.mint === args.mint).map((e) => String(e.sessionId)));
|
|
216
|
+
const pos = new Set(evs.filter((e) => e.kind === 'fill' && e.mint === args.mint).map((e) => String(e.positionId)));
|
|
217
|
+
const decisions = evs.filter((e) => e.kind === 'reply' && sids.has(String(e.sessionId))).map((e) => {
|
|
218
|
+
const d = e.decision;
|
|
219
|
+
return { at: iso(e.t), sessionId: e.sessionId, costUsd: e.costUsd, ...(d?.ok ? { action: d.decision?.action, profile: d.decision?.profile ?? null, sizePct: d.decision?.sizePct ?? null, reason: fw(d.decision?.reason, 'reason') } : { invalid: fw(d?.error ?? 'invalid', 'error') }) };
|
|
220
|
+
});
|
|
221
|
+
const tick = (cands[cands.length - 1].tick ?? {});
|
|
222
|
+
return {
|
|
223
|
+
found: true, readOnly: true, runId, strategy: { id: start.strategy ?? null, version: start.version ?? null, hash: start.strategyHash ?? null }, mode: start.mode ?? 'paper',
|
|
224
|
+
mint: args.mint, symbol: fw(cands[cands.length - 1].symbol, 'symbol'),
|
|
225
|
+
candidate: cands.map((c) => ({ at: iso(c.t), matched: c.matched })),
|
|
226
|
+
tick: Object.fromEntries(Object.entries(tick).filter(([, v]) => typeof v === 'number' || typeof v === 'boolean' || v === null)),
|
|
227
|
+
sessions: [...sids], decisions,
|
|
228
|
+
clamp: evs.filter((e) => e.kind === 'clamp' && e.mint === args.mint).map((e) => ({ at: iso(e.t), result: e.result })),
|
|
229
|
+
fills: evs.filter((e) => e.kind === 'fill' && e.mint === args.mint).map((e) => ({ at: iso(e.t), side: e.side, usd: e.usd, price: e.avgPrice ?? e.price, reason: e.reason, profile: e.profile ?? null, detail: fw(e.detail, 'detail') })),
|
|
230
|
+
closed: evs.filter((e) => e.kind === 'trade_closed' && pos.has(String(e.trade?.id))).map((e) => { const tr = e.trade; return { at: iso(e.t), exitReason: tr.exitReason, pnlUsd: tr.pnlUsd, pnlPct: tr.pnlPct, stuck: tr.stuck === true }; }),
|
|
231
|
+
note: 'What the run recorded (the session\'s own dossier text is kept only in a --verbose run). For the coin as it is now, call get_dossier.',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return { found: false, note: args.run_id ? `run ${String(args.run_id)} has no candidate ${args.mint}` : `no candidate ${args.mint} in the last ${pick.length} runs` };
|
|
235
|
+
}
|
|
236
|
+
// ───────────────────────────── the recorder's outcome labels ─────────────────────────────
|
|
237
|
+
async outcomes(args) {
|
|
238
|
+
const db = this.o.historyDb?.() ?? null;
|
|
239
|
+
if (!db)
|
|
240
|
+
return { error: 'no recorder history on this machine (outcomes come from the recorder DB; run the app with the recorder on)' };
|
|
241
|
+
const limit = Math.min(100, Math.max(1, Number.isInteger(args.limit) ? Number(args.limit) : 20));
|
|
242
|
+
const q = db.prepare('SELECT mint, t0, horizon_sec, t1, label, label_tier, mcap_ratio, liq_ratio, price_ratio, max_mcap_ratio FROM outcomes WHERE mint = ? ORDER BY t0 DESC, horizon_sec ASC LIMIT ?');
|
|
243
|
+
const row = (r) => ({ t0: iso(Number(r.t0)), horizonSec: Number(r.horizon_sec), observedAt: r.t1 === null ? null : iso(Number(r.t1)), label: r.label, tier: r.label_tier, mcapRatio: r.mcap_ratio, liqRatio: r.liq_ratio, priceRatio: r.price_ratio, maxMcapRatio: r.max_mcap_ratio });
|
|
244
|
+
const legend = 'Labels are code-only, from recorded numbers: rug, dead, fade, flat, runner, unobserved (keyless-proxy unless chain-verified). Not a prediction.';
|
|
245
|
+
if (args.mint !== undefined) {
|
|
246
|
+
if (typeof args.mint !== 'string' || !MINT.test(args.mint))
|
|
247
|
+
return { error: 'mint is not a Solana address' };
|
|
248
|
+
return { readOnly: true, mint: args.mint, outcomes: q.all(args.mint, limit).map(row), legend };
|
|
249
|
+
}
|
|
250
|
+
if (typeof args.run_id !== 'string' || !RUN_ID.test(args.run_id))
|
|
251
|
+
return { error: 'pass mint or run_id' };
|
|
252
|
+
const p = join(this.home(), 'autotrade', 'runs', `${args.run_id}.jsonl`);
|
|
253
|
+
if (!existsSync(p))
|
|
254
|
+
return { error: `no run ${args.run_id}` };
|
|
255
|
+
const seen = new Map();
|
|
256
|
+
for (const l of readFileSync(p, 'utf8').split('\n')) {
|
|
257
|
+
if (!l.trim())
|
|
258
|
+
continue;
|
|
259
|
+
let e;
|
|
260
|
+
try {
|
|
261
|
+
e = JSON.parse(l);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
const mint = typeof e.mint === 'string' ? e.mint : null;
|
|
267
|
+
if (!mint || !MINT.test(mint))
|
|
268
|
+
continue;
|
|
269
|
+
if (e.kind === 'candidate' && !seen.has(mint))
|
|
270
|
+
seen.set(mint, { symbol: e.symbol, at: Number(e.t), entered: false });
|
|
271
|
+
if (e.kind === 'fill' && e.side === 'buy' && seen.has(mint))
|
|
272
|
+
seen.get(mint).entered = true;
|
|
273
|
+
}
|
|
274
|
+
const coins = [...seen.entries()].slice(0, limit).map(([mint, c]) => ({ mint, symbol: fw(String(c.symbol ?? ''), 'symbol'), candidateAt: iso(c.at), entered: c.entered, outcomes: q.all(mint, 6).map(row) }));
|
|
275
|
+
return { readOnly: true, runId: args.run_id, candidates: seen.size, shown: coins.length, coins, legend };
|
|
276
|
+
}
|
|
277
|
+
// ───────────────────────────── telegram (settings only) ─────────────────────────────
|
|
278
|
+
telegram() {
|
|
279
|
+
const p = join(this.home(), 'connectors.json');
|
|
280
|
+
if (!existsSync(p))
|
|
281
|
+
return { readOnly: true, configured: false, note: 'Telegram is not set up (`dexterm telegram setup` on this machine).' };
|
|
282
|
+
let raw;
|
|
283
|
+
try {
|
|
284
|
+
raw = JSON.parse(readFileSync(p, 'utf8'));
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return { readOnly: true, configured: false, error: 'connectors.json is not readable JSON (the bot stays off)' };
|
|
288
|
+
}
|
|
289
|
+
const t = (raw.telegram && typeof raw.telegram === 'object' ? raw.telegram : {});
|
|
290
|
+
const allow = Array.isArray(t.allow) ? t.allow : [];
|
|
291
|
+
const n = (t.notify && typeof t.notify === 'object' ? t.notify : {});
|
|
292
|
+
const tiers = Array.isArray(t.tiers) ? t.tiers.filter((x) => x === 'notify' || x === 'control' || x === 'approve') : [];
|
|
293
|
+
return {
|
|
294
|
+
readOnly: true, configured: true, enabled: t.enabled === true, botConfigured: !!t.bot,
|
|
295
|
+
tokenStore: t.secretStore === 'keychain' || t.secretStore === 'libsecret' || t.secretStore === 'file' ? t.secretStore : null,
|
|
296
|
+
pairedChats: allow.length, tiers, approvals: t.approvals === 'on' ? 'on' : 'off',
|
|
297
|
+
approvalExpirySec: typeof t.approvalExpirySec === 'number' ? t.approvalExpirySec : null,
|
|
298
|
+
notify: Object.fromEntries(['candidates', 'entries', 'closes', 'halts', 'spend', 'daily'].map((k) => [k, typeof n[k] === 'boolean' || typeof n[k] === 'string' ? n[k] : null])),
|
|
299
|
+
quietHours: typeof t.quietHours === 'string' ? t.quietHours : null, dailySummaryAt: typeof t.dailySummaryAt === 'string' ? t.dailySummaryAt : null,
|
|
300
|
+
note: 'Settings only (no token, no chat or sender ids). The bot runs inside `dexterm autotrade run` when a chat is paired; only local commands change these settings.',
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// ───────────────────────────── live trading (task-1571: not in dexterm) ───────────────
|
|
304
|
+
trading() {
|
|
305
|
+
const panic = existsSync(join(this.home(), 'trading.panic'));
|
|
306
|
+
const real = this.realState();
|
|
307
|
+
return {
|
|
308
|
+
readOnly: true,
|
|
309
|
+
liveTrading: 'not in dexterm: dexterm holds no key, signs nothing and places no live order. Live trading happens in the Telegram trading bot (or script) the user chose, which holds its own wallet; dexterm only sends alerts, run control and, in real mode, LIVE TRADE FIRE signals to the user\'s own executor (telegram_status, trade_fire_feed).',
|
|
310
|
+
paperTrading: 'local: the paper ticket, paper accounts and paper autotrade runs (ledger_* tools)',
|
|
311
|
+
realMode: `${real.armed.length} strategy(ies) armed for real mode by the user on their own terminal${real.kill ? `; the real-mode kill switch is ON (${real.kill})` : ''}. Only the user arms, disarms, sets caps or lifts the kill switch, on their own terminal; no tool can.`,
|
|
312
|
+
panicSwitch: panic ? 'engaged (autotrade runs and the paper agent desk are halted; only `dexterm wallet resume` in a terminal clears it)' : 'off',
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
// ───────────────────────────── real mode (task-1645) ─────────────────────────────
|
|
316
|
+
readJson(path) {
|
|
317
|
+
try {
|
|
318
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/** Real mode's state from its files (pure reads; ids and numbers only). */
|
|
325
|
+
realState() {
|
|
326
|
+
const dir = join(this.home(), 'autotrade', 'real');
|
|
327
|
+
const arming = this.readJson(join(dir, 'arming.json'));
|
|
328
|
+
const armed = Object.values((arming?.armed ?? {}))
|
|
329
|
+
.filter((a) => typeof a.strategyId === 'string' && /^[a-z][a-z0-9-]{1,31}$/.test(a.strategyId))
|
|
330
|
+
.map((a) => ({ id: String(a.strategyId), version: String(a.version ?? '').slice(0, 16), accountId: String(a.accountId ?? '').replace(/[^a-z0-9_-]/g, '').slice(0, 32) }));
|
|
331
|
+
const killFile = join(dir, 'kill.json');
|
|
332
|
+
const k = existsSync(killFile) ? this.readJson(killFile) : null;
|
|
333
|
+
// task-1654: the caps are four of the limits (limits.json global, else card A's caps.json).
|
|
334
|
+
const lim = this.readJson(join(dir, 'limits.json'))?.global;
|
|
335
|
+
const caps = (lim ? Object.fromEntries(['perTradeSol', 'dailySol', 'buysPerDay', 'maxOpenPositions'].filter((k) => typeof lim[k] === 'number').map((k) => [k, lim[k]])) : undefined) ?? this.readJson(join(dir, 'caps.json'))?.caps;
|
|
336
|
+
const capsNum = caps ? Object.fromEntries(Object.entries(caps).filter(([, v]) => typeof v === 'number')) : null;
|
|
337
|
+
const n = Number(this.readJson(join(dir, 'nonce.json'))?.last);
|
|
338
|
+
return { armed, kill: existsSync(killFile) ? cleanLedgerText(k?.reason ?? 'engaged', 80) || 'engaged' : null, caps: capsNum, lastNonce: Number.isSafeInteger(n) ? n : null };
|
|
339
|
+
}
|
|
340
|
+
signals(args) {
|
|
341
|
+
const after = Number(args.after_nonce ?? 0);
|
|
342
|
+
const limit = Number(args.limit ?? 50);
|
|
343
|
+
if (!Number.isSafeInteger(after) || after < 0)
|
|
344
|
+
return { error: 'after_nonce is a whole number >= 0' };
|
|
345
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
346
|
+
return { error: 'limit is a whole number from 1 to 100' };
|
|
347
|
+
const feed = join(this.home(), 'autotrade', 'real', 'mcp-feed.jsonl');
|
|
348
|
+
const out = [];
|
|
349
|
+
let feedOn = false;
|
|
350
|
+
if (existsSync(feed)) {
|
|
351
|
+
feedOn = true;
|
|
352
|
+
for (const l of readFileSync(feed, 'utf8').split('\n')) {
|
|
353
|
+
if (!l.trim())
|
|
354
|
+
continue;
|
|
355
|
+
let x;
|
|
356
|
+
try {
|
|
357
|
+
x = JSON.parse(l);
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (x.schema !== 'dexterm-trade-fire/1' || typeof x.nonce !== 'number' || x.nonce <= after)
|
|
363
|
+
continue;
|
|
364
|
+
// The signal as dexterm wrote it; the only token text in it is the symbol, already reduced to [A-Za-z0-9._$-].
|
|
365
|
+
out.push({ ...x, symbol: typeof x.symbol === 'string' ? x.symbol.replace(/[^A-Za-z0-9._$-]/g, '').slice(0, 16) : undefined });
|
|
366
|
+
if (out.length >= limit)
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const st = this.realState();
|
|
371
|
+
return {
|
|
372
|
+
readOnly: true, schema: 'dexterm-trade-fire/1', signals: out, nextAfterNonce: out.length ? out[out.length - 1].nonce : after,
|
|
373
|
+
realMode: { armed: st.armed, kill: st.kill, caps: st.caps, lastNonce: st.lastNonce, mcpFeed: feedOn ? 'on' : 'off (the user enables it: dexterm autotrade signal channels --mcp on)' },
|
|
374
|
+
rules: 'An executor acts only on its own accountId, ignores a signal after expiresAt, and dedupes by nonce (delivery is at-least-once). type "halted" ends a run; type "test" is never traded. The user records what was done with `dexterm autotrade receipt`; this tool changes nothing.',
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
// ───────────────────────────── limits (task-1654) ─────────────────────────────
|
|
378
|
+
limitsContext(args) {
|
|
379
|
+
const id = (v, name) => (v === undefined ? '-' : typeof v === 'string' && /^[a-z0-9][a-z0-9._-]{0,63}$/.test(v) ? v : { error: `${name} is an id ([a-z0-9._-], 64 max)` });
|
|
380
|
+
const strategyId = id(args.strategy, 'strategy');
|
|
381
|
+
if (typeof strategyId !== 'string')
|
|
382
|
+
return strategyId;
|
|
383
|
+
const accountId = id(args.account, 'account');
|
|
384
|
+
if (typeof accountId !== 'string')
|
|
385
|
+
return accountId;
|
|
386
|
+
if (args.mode !== undefined && args.mode !== 'real' && args.mode !== 'paper')
|
|
387
|
+
return { error: 'mode is real or paper' };
|
|
388
|
+
return { strategyId, accountId, mode: args.mode === 'paper' ? 'paper' : 'real' };
|
|
389
|
+
}
|
|
390
|
+
async limits(args) {
|
|
391
|
+
const c = this.limitsContext(args);
|
|
392
|
+
if ('error' in c)
|
|
393
|
+
return c;
|
|
394
|
+
const at = await import('../../autotrade/src/index.js');
|
|
395
|
+
const home = this.home();
|
|
396
|
+
const l = at.loadLimits(home);
|
|
397
|
+
const doc = l.ok ? l.doc : null;
|
|
398
|
+
const e = at.effectiveLimits(doc, c);
|
|
399
|
+
const risk = at.loadRiskState(home, this.now());
|
|
400
|
+
const ex = at.executorHealth(home, { now: this.now(), windowMin: e.values.executorSilenceMin ?? null });
|
|
401
|
+
return {
|
|
402
|
+
readOnly: true, context: c, applies: e.applies,
|
|
403
|
+
state: l.ok ? (l.source === 'migrated' ? 'migrated from caps.json' : l.unjournaled ? 'changed outside dexterm (a real run refuses it if it raises anything)' : 'saved by the user') : l.missing ? 'not set yet: placeholder defaults (real mode does not arm on them silently)' : 'unreadable: real buys are refused',
|
|
404
|
+
limits: at.LIMIT_FIELDS.map((f) => ({ key: f.key, group: f.group, unit: f.unit, kind: f.dir === 'max' ? 'cap (smaller is stricter)' : 'floor (larger is stricter)', value: e.values[f.key] ?? null, from: e.from[f.key] ?? null, placeholder: at.DEFAULT_LIMITS[f.key] ?? null, what: f.help })),
|
|
405
|
+
today: { day: risk.day, buys: risk.buys, buySol: risk.buySol, realizedSol: risk.dayPnlSol, lossStreak: risk.lossStreak, pausedUntil: iso(risk.pausedUntil), breaker: risk.breaker ? { at: iso(risk.breaker.at), drawdownSol: risk.breaker.drawdownSol } : null },
|
|
406
|
+
executor: at.healthLine(ex),
|
|
407
|
+
rules: 'Limits live outside the model. The effective number is the placeholder default replaced by the user\'s global value, then the STRICTER of every scope that sets it (mode, strategy, account). No tool changes them: limits_propose returns a diff card for the user to apply on their own terminal (raising needs their typed confirmation). The defaults are placeholders, not advice.',
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
async limitsPropose(args) {
|
|
411
|
+
const at = await import('../../autotrade/src/index.js');
|
|
412
|
+
const scope = typeof args.scope === 'string' ? args.scope : 'global';
|
|
413
|
+
const changes = args.changes;
|
|
414
|
+
if (!changes || typeof changes !== 'object' || Array.isArray(changes) || !Object.keys(changes).length || Object.keys(changes).length > 24)
|
|
415
|
+
return { error: 'changes is an object of 1 to 24 limits: { "perTradeSol": 0.1, "dailyLossSol": null }' };
|
|
416
|
+
// The agent's own reason is returned apart from the card, firewalled: the card is engine-made lines only.
|
|
417
|
+
const why = typeof args.why === 'string' ? fw(args.why.slice(0, 200), 'limits-why') : null;
|
|
418
|
+
const home = this.home();
|
|
419
|
+
const l = at.loadLimits(home);
|
|
420
|
+
const cur = l.ok ? l.doc : at.emptyLimits();
|
|
421
|
+
const next = structuredClone(cur);
|
|
422
|
+
delete next.migratedFrom;
|
|
423
|
+
const ref = at.scopeRef(next, scope, true);
|
|
424
|
+
if ('error' in ref)
|
|
425
|
+
return { error: ref.error };
|
|
426
|
+
for (const [k, v] of Object.entries(changes)) {
|
|
427
|
+
if (!at.limitField(k))
|
|
428
|
+
return { error: `unknown limit "${String(k).slice(0, 40)}"` };
|
|
429
|
+
if (v !== null && (typeof v !== 'number' || !Number.isFinite(v)))
|
|
430
|
+
return { error: `${k}: a number, or null to remove it` };
|
|
431
|
+
if (v === null && !ref.isGlobal)
|
|
432
|
+
delete ref.set[k];
|
|
433
|
+
else
|
|
434
|
+
ref.set[k] = v;
|
|
435
|
+
}
|
|
436
|
+
const issues = at.validateLimits(next);
|
|
437
|
+
if (issues.length)
|
|
438
|
+
return { error: issues.map((i) => `${i.path}: ${i.msg}`).join('; ').slice(0, 400) };
|
|
439
|
+
const diff = at.diffLimits(l.ok ? cur : null, next);
|
|
440
|
+
const up = at.raises(diff);
|
|
441
|
+
const assign = Object.entries(changes).map(([k, v]) => `${k}=${v === null ? 'off' : v}`).join(' ');
|
|
442
|
+
return {
|
|
443
|
+
applied: false, proposal: true,
|
|
444
|
+
card: [`PROPOSED LIMITS CHANGE · ${scope}`, ...diff.map((d) => ` ${at.changeLine(d)}`), up.length ? ` raises ${up.length} limit(s): you would type the confirmation on your own terminal` : ' lowers only: applies at once when you run it', ` to apply it yourself: dexterm autotrade limits set --scope ${scope} ${assign}`],
|
|
445
|
+
changes: diff, raises: up.length, why,
|
|
446
|
+
command: `dexterm autotrade limits set --scope ${scope} ${assign}`,
|
|
447
|
+
note: 'Nothing was changed: no tool can change a limit. Only the user applies this, on their own terminal.',
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
// ───────────────────────────── backtest requests ─────────────────────────────
|
|
451
|
+
requests() {
|
|
452
|
+
const rs = new BacktestRequests(this.home()).list().slice(0, 20);
|
|
453
|
+
return { readOnly: true, requests: rs.map((r) => ({ requestId: r.requestId, id: r.id, days: r.days, everyCandidate: r.everyCandidate, status: r.status, why: fw(r.why, 'why'), createdAt: iso(r.createdAt), resolvedAt: iso(r.resolvedAt ?? null), result: r.result ?? null })), note: 'The user runs a request with `dexterm autotrade requests run <requestId>`; results are backtests on recorded history, not predictions.' };
|
|
454
|
+
}
|
|
455
|
+
async request(args) {
|
|
456
|
+
const id = args.id;
|
|
457
|
+
if (typeof id !== 'string' || !/^[a-z][a-z0-9-]{1,31}$/.test(id))
|
|
458
|
+
return { error: 'id must be a strategy id from strategy_list' };
|
|
459
|
+
const days = Number(args.days ?? 30);
|
|
460
|
+
if (!Number.isInteger(days) || days < 1 || days > REQUEST_MAX_DAYS)
|
|
461
|
+
return { error: `days must be a whole number from 1 to ${REQUEST_MAX_DAYS}` };
|
|
462
|
+
// stored as plain one-line text (the user reads it in `dexterm autotrade requests`); fenced when an agent reads it back
|
|
463
|
+
const why = cleanLedgerText(args.why, 200);
|
|
464
|
+
if (!why)
|
|
465
|
+
return { error: 'why is required (one line: what the backtest should answer)' };
|
|
466
|
+
const t = this.now();
|
|
467
|
+
while (this.requestTimes.length && t - this.requestTimes[0] > 3_600_000)
|
|
468
|
+
this.requestTimes.shift();
|
|
469
|
+
if (this.requestTimes.length >= REQUEST_RATE_PER_HOUR)
|
|
470
|
+
return { error: `at most ${REQUEST_RATE_PER_HOUR} backtest requests per hour`, reason: 'rate-limited' };
|
|
471
|
+
const files = new BacktestRequests(this.home());
|
|
472
|
+
if (files.list({ status: 'pending' }).length >= MAX_PENDING_REQUESTS)
|
|
473
|
+
return { error: `${MAX_PENDING_REQUESTS} requests are already waiting for the user; wait for them to be run or dismissed`, reason: 'quota' };
|
|
474
|
+
const info = await (this.o.strategyInfo ?? defaultStrategyInfo(this.env, this.now))(id);
|
|
475
|
+
if (!info)
|
|
476
|
+
return { error: `no strategy ${id}` };
|
|
477
|
+
if (!info.ok)
|
|
478
|
+
return { error: `${id} does not validate: ${info.why ?? 'invalid'}` };
|
|
479
|
+
this.requestTimes.push(t);
|
|
480
|
+
const r = files.create({ id, version: info.version, strategyHash: info.hash, days, everyCandidate: args.every_candidate === true, why, actor: this.actor, createdAt: t });
|
|
481
|
+
return { proposal: true, requestId: r.requestId, id, days, everyCandidate: r.everyCandidate, status: 'pending', note: `Nothing ran. The user runs it with \`dexterm autotrade requests run ${r.requestId}\` (or dismisses it); read the result with backtest_requests.` };
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function defaultStrategyInfo(env, now) {
|
|
485
|
+
return async (id) => {
|
|
486
|
+
const at = await import('../../autotrade/src/index.js');
|
|
487
|
+
const got = new at.StrategyStore({ home: dextermHome(env), now }).get(id);
|
|
488
|
+
if (!got)
|
|
489
|
+
return null;
|
|
490
|
+
if (!got.ok)
|
|
491
|
+
return { ok: false, version: null, hash: null, why: got.errors.map(at.fmtIssue).join('; ').slice(0, 200) };
|
|
492
|
+
return { ok: true, version: String(got.strategy.version ?? ''), hash: at.strategyHash(got.strategy) };
|
|
493
|
+
};
|
|
494
|
+
}
|
|
@@ -85,6 +85,8 @@ export async function resolvePaperAgentDesk(data, env = process.env, opts = {})
|
|
|
85
85
|
control: () => readDeskControl(env),
|
|
86
86
|
journal: new SharedJournal(paperAgentJournalPath(env), systemClock),
|
|
87
87
|
agent: rv.agent ?? 'mcp-agent',
|
|
88
|
+
// task-1530: the launcher may name the model session (journaled on each fill so the trade ledger can join its cost)
|
|
89
|
+
sessionId: () => env.DEXTERM_SESSION_ID ?? null,
|
|
88
90
|
});
|
|
89
91
|
cache = { mtimeMs, db: rv.db, mandateId: rv.mandateId, wallet, desk };
|
|
90
92
|
return desk;
|