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,307 @@
|
|
|
1
|
+
// task-1530 — read-only IMPORTERS: every trade journal dexterm already keeps, as unified ledger drafts.
|
|
2
|
+
//
|
|
3
|
+
// Nothing here writes to a source. The existing journals stay exactly as they are (and keep being written the way
|
|
4
|
+
// they always were), so the unified ledger is fully backward compatible: an old journal with none of the new fields
|
|
5
|
+
// imports with those fields null, and the "migration" of an existing install is one `dexterm ledger sync`.
|
|
6
|
+
//
|
|
7
|
+
// desk $DEXTERM_HOME/paper-agent.journal: the agent paper desk (MCP paper tools, the autotrade desk executor).
|
|
8
|
+
// agent.paper.fill events, their structured tag (task-1530), the exit rule journaled just before a monitor
|
|
9
|
+
// sell (agent.paper.exit.<rule>), and late PROFILE / REPROFILE / EXIT declarations (agent.paper.tag).
|
|
10
|
+
// The journal's own hash chain is verified and its head recorded in the import event. An autotrade run on
|
|
11
|
+
// the desk (note.by "autotrade") is imported here too, with its strategy / profile source / model from
|
|
12
|
+
// the run ledger (`RunHint`).
|
|
13
|
+
// paper the paper wallet's paper_trades table (the TUI ticket). Rows the desk booked are joined to the desk
|
|
14
|
+
// fills (fees, impact, slippage, the book before the fill) and not imported twice.
|
|
15
|
+
// task-1571: there is no live source. dexterm holds no key and places no live order, so it keeps no live fills.
|
|
16
|
+
// Autotrade run ledgers are imported by @dexterm/autotrade (`importRunLedgers`), which owns that format.
|
|
17
|
+
//
|
|
18
|
+
// A round trip is every fill on one mint from a buy while flat until the position is empty again (the source's own
|
|
19
|
+
// token count). Its key is the ref of its first fill, so re-importing never re-keys a trade.
|
|
20
|
+
import { createHash } from 'node:crypto';
|
|
21
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
23
|
+
import { eventHash } from "../plans/journal.js";
|
|
24
|
+
import { buildFillTag, buildLateTag, tagCode } from "./tags.js";
|
|
25
|
+
import { cleanCode, cleanLedgerText, finite } from "./schema.js";
|
|
26
|
+
const LAMPORTS = 1e9;
|
|
27
|
+
const num = (x) => { const n = typeof x === 'string' && x.trim() !== '' ? Number(x) : x; return finite(n); };
|
|
28
|
+
const big = (x) => { try {
|
|
29
|
+
return typeof x === 'bigint' ? x : typeof x === 'string' && /^-?\d+$/.test(x) ? BigInt(x) : typeof x === 'number' && Number.isInteger(x) ? BigInt(x) : null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
} };
|
|
34
|
+
const absBig = (b) => (b < 0n ? -b : b);
|
|
35
|
+
const solOf = (l) => (l === null ? null : Number(l) / LAMPORTS);
|
|
36
|
+
const mintOk = (x) => typeof x === 'string' && /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(x);
|
|
37
|
+
const sigOk = (x) => (typeof x === 'string' && /^[1-9A-HJ-NP-Za-km-z]{64,90}$/.test(x) ? x : null);
|
|
38
|
+
const sha = (s) => createHash('sha256').update(s).digest('hex');
|
|
39
|
+
/** Track the token count per key; flags the fill that empties the position. */
|
|
40
|
+
class RoundTrips {
|
|
41
|
+
held = new Map();
|
|
42
|
+
/** The trade key for this fill, and whether the position is flat after it. A sell while flat has no trade (null). */
|
|
43
|
+
step(scope, side, tokenDeltaAbs, ref) {
|
|
44
|
+
const cur = this.held.get(scope);
|
|
45
|
+
if (side === 'buy') {
|
|
46
|
+
if (!cur || cur.tokens <= 0n) {
|
|
47
|
+
this.held.set(scope, { tokens: tokenDeltaAbs, key: ref });
|
|
48
|
+
return { tradeKey: ref, closes: false };
|
|
49
|
+
}
|
|
50
|
+
cur.tokens += tokenDeltaAbs;
|
|
51
|
+
return { tradeKey: cur.key, closes: false };
|
|
52
|
+
}
|
|
53
|
+
if (!cur || cur.tokens <= 0n)
|
|
54
|
+
return { tradeKey: null, closes: false };
|
|
55
|
+
cur.tokens -= tokenDeltaAbs;
|
|
56
|
+
const closes = cur.tokens <= 0n;
|
|
57
|
+
if (closes)
|
|
58
|
+
this.held.delete(scope);
|
|
59
|
+
return { tradeKey: cur.key, closes };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** pump.fun mints every coin with 6 decimals and a fixed 1,000,000,000 supply (the PnL card's rule, task-1518). */
|
|
63
|
+
export const PUMP_FUN_DECIMALS = 6;
|
|
64
|
+
export const PUMP_FUN_SUPPLY_TOKENS = 1_000_000_000;
|
|
65
|
+
const isPumpFun = (mint) => mint.endsWith('pump');
|
|
66
|
+
function fillDraft(p) {
|
|
67
|
+
const d = {
|
|
68
|
+
kind: 'fill', decimals: null, sol: null, usd: null, tokens: null, priceSol: null, priceUsd: null, supply: null, feeSol: null,
|
|
69
|
+
impactPct: null, slippageBps: null, realizedSol: null, realizedUsd: null, bookSol: null, bookUsd: null, closes: null, txSig: null,
|
|
70
|
+
runId: null, agent: null, strategy: null, sessionId: null, model: null, tag: null, ...p,
|
|
71
|
+
};
|
|
72
|
+
// a source that recorded no supply: a pump.fun coin's is fixed, so its market cap is price × 1B; any other stays null
|
|
73
|
+
if (!d.supply && isPumpFun(d.mint))
|
|
74
|
+
d.supply = { tokens: PUMP_FUN_SUPPLY_TOKENS, source: 'pump.fun fixed supply' };
|
|
75
|
+
return d;
|
|
76
|
+
}
|
|
77
|
+
function importDraft(source, label, head, verified, events, t, note) {
|
|
78
|
+
return { kind: 'import', t, ref: `import:${source}:${label}:${head ?? `n${events}`}`, source, label, sourceHead: head, sourceVerified: verified, events, ...(note ? { note } : {}) };
|
|
79
|
+
}
|
|
80
|
+
function openRo(path) {
|
|
81
|
+
if (!path || path === ':memory:' || !existsSync(path))
|
|
82
|
+
return null;
|
|
83
|
+
try {
|
|
84
|
+
return new DatabaseSync(path, { readOnly: true });
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const hasTable = (db, name) => Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(name));
|
|
91
|
+
function readWallet(path) {
|
|
92
|
+
const db = openRo(path);
|
|
93
|
+
if (!db)
|
|
94
|
+
return null;
|
|
95
|
+
try {
|
|
96
|
+
if (!hasTable(db, 'paper_trades'))
|
|
97
|
+
return null;
|
|
98
|
+
const w = hasTable(db, 'paper_wallet') ? db.prepare('SELECT start_lamports, created_at FROM paper_wallet WHERE id = 1').get() : undefined;
|
|
99
|
+
const rows = db.prepare('SELECT * FROM paper_trades ORDER BY id ASC').all().map((r) => ({
|
|
100
|
+
id: Number(r.id), t: Number(r.t), mint: String(r.mint), symbol: String(r.symbol), side: r.side === 'sell' ? 'sell' : 'buy', decimals: Number(r.decimals),
|
|
101
|
+
solDelta: big(r.sol_delta) ?? 0n, tokenDelta: big(r.token_delta) ?? 0n, fee: big(r.fee_lamports) ?? 0n, priceSol: Number(r.price_sol),
|
|
102
|
+
priceUsd: r.price_usd === null ? null : Number(r.price_usd), impact: r.price_impact_pct === null ? null : Number(r.price_impact_pct),
|
|
103
|
+
slippage: Number(r.slippage_bps), realized: big(r.realized_lamports) ?? 0n,
|
|
104
|
+
}));
|
|
105
|
+
return { rows, startLamports: w ? big(w.start_lamports) : null, createdAt: w ? Number(w.created_at) : null };
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
try {
|
|
112
|
+
db.close();
|
|
113
|
+
}
|
|
114
|
+
catch { /* ignore */ }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Per wallet row: the book (cash + open cost) just BEFORE it, replayed from the wallet's start balance. */
|
|
118
|
+
function walletFacts(w) {
|
|
119
|
+
const out = new Map();
|
|
120
|
+
let cash = w.startLamports;
|
|
121
|
+
const cost = new Map();
|
|
122
|
+
for (const r of w.rows) {
|
|
123
|
+
const openCost = [...cost.values()].reduce((s, c) => s + c.cost, 0n);
|
|
124
|
+
out.set(r.id, { decimals: r.decimals, feeSol: Number(r.fee) / LAMPORTS, impactPct: r.impact, slippageBps: Number.isFinite(r.slippage) ? r.slippage : null, bookSolBefore: cash === null ? null : Number(cash + openCost) / LAMPORTS, priceUsd: r.priceUsd });
|
|
125
|
+
if (cash !== null)
|
|
126
|
+
cash += r.solDelta;
|
|
127
|
+
const c = cost.get(r.mint) ?? { tokens: 0n, cost: 0n };
|
|
128
|
+
if (r.side === 'buy') {
|
|
129
|
+
c.tokens += absBig(r.tokenDelta);
|
|
130
|
+
c.cost += absBig(r.solDelta);
|
|
131
|
+
}
|
|
132
|
+
else if (c.tokens > 0n) {
|
|
133
|
+
const sold = absBig(r.tokenDelta) > c.tokens ? c.tokens : absBig(r.tokenDelta);
|
|
134
|
+
c.cost -= (c.cost * sold) / c.tokens;
|
|
135
|
+
c.tokens -= sold;
|
|
136
|
+
}
|
|
137
|
+
if (c.tokens <= 0n)
|
|
138
|
+
cost.delete(r.mint);
|
|
139
|
+
else
|
|
140
|
+
cost.set(r.mint, c);
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
function verifyDeskChain(events) {
|
|
145
|
+
let prev = 'genesis';
|
|
146
|
+
for (let i = 0; i < events.length; i++) {
|
|
147
|
+
const e = events[i];
|
|
148
|
+
if (e.seq !== i + 1 || e.prev !== prev)
|
|
149
|
+
return { ok: false, at: i + 1 };
|
|
150
|
+
const { hash, ...rest } = e;
|
|
151
|
+
if (eventHash(rest) !== hash)
|
|
152
|
+
return { ok: false, at: i + 1 };
|
|
153
|
+
prev = hash;
|
|
154
|
+
}
|
|
155
|
+
return { ok: true, at: null };
|
|
156
|
+
}
|
|
157
|
+
export function importDeskJournal(path, o = {}) {
|
|
158
|
+
const label = 'paper-agent.journal';
|
|
159
|
+
const res = { label, source: 'desk', drafts: [], events: 0, note: null, problem: null, fillIds: new Set() };
|
|
160
|
+
if (!existsSync(path))
|
|
161
|
+
return res;
|
|
162
|
+
const events = [];
|
|
163
|
+
for (const line of readFileSync(path, 'utf8').split('\n')) {
|
|
164
|
+
if (!line.trim())
|
|
165
|
+
continue;
|
|
166
|
+
try {
|
|
167
|
+
events.push(JSON.parse(line));
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
res.problem = `a line of ${label} is not JSON; imported the ${events.length} events before it`;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
res.events = events.length;
|
|
175
|
+
const chain = verifyDeskChain(events);
|
|
176
|
+
const head = events.length ? events[events.length - 1].hash : null;
|
|
177
|
+
const wallet = o.walletDb ? readWallet(o.walletDb) : null;
|
|
178
|
+
const facts = wallet ? walletFacts(wallet) : new Map();
|
|
179
|
+
const trips = new RoundTrips();
|
|
180
|
+
const lastExitRule = new Map();
|
|
181
|
+
const tradeOfMint = new Map();
|
|
182
|
+
for (const e of events) {
|
|
183
|
+
const d = (e.data ?? {});
|
|
184
|
+
const ref = `desk:${e.seq}:${String(e.hash).slice(0, 12)}`;
|
|
185
|
+
if (e.type.startsWith('agent.paper.exit.') && e.type !== 'agent.paper.exit.plan' && e.type !== 'agent.paper.exit.done' && mintOk(d.mint)) {
|
|
186
|
+
lastExitRule.set(d.mint, { rule: e.type.slice('agent.paper.exit.'.length), detail: cleanLedgerText(d.detail, 200), t: e.ts });
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (e.type === 'agent.paper.tag' && mintOk(d.mint)) {
|
|
190
|
+
const g = buildLateTag({ kind: d.tagKind, profile: d.profile, from: d.from, to: d.to, exitReason: d.exitReason, reason: d.reason, line: d.line });
|
|
191
|
+
if (!g.ok)
|
|
192
|
+
continue;
|
|
193
|
+
res.drafts.push({
|
|
194
|
+
kind: 'tag', t: e.ts, ref, tradeKey: tradeOfMint.get(d.mint) ?? null, source: 'desk', mint: d.mint, tagKind: g.tagKind,
|
|
195
|
+
profile: g.tagKind === 'profile' ? g.profile : null, from: g.tagKind === 'reprofile' ? g.from : null, to: g.tagKind === 'reprofile' ? g.to : null,
|
|
196
|
+
exitReason: g.tagKind === 'exit' ? g.exitReason : null, reason: g.reason, line: g.line, tagSource: g.tagKind === 'reprofile' ? 'structured' : 'late',
|
|
197
|
+
});
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (e.type !== 'agent.paper.fill' || !mintOk(d.mint))
|
|
201
|
+
continue;
|
|
202
|
+
const side = d.side === 'sell' ? 'sell' : 'buy';
|
|
203
|
+
const solDelta = big(d.solDelta);
|
|
204
|
+
const tokenDelta = big(d.tokenDelta);
|
|
205
|
+
if (solDelta === null || tokenDelta === null)
|
|
206
|
+
continue;
|
|
207
|
+
const fillId = num(d.fillId);
|
|
208
|
+
if (fillId !== null)
|
|
209
|
+
res.fillIds.add(fillId);
|
|
210
|
+
const note = (d.note && typeof d.note === 'object' ? d.note : {});
|
|
211
|
+
const runId = typeof note.runId === 'string' ? cleanCode(note.runId, 80) : null;
|
|
212
|
+
const byRun = note.by === 'autotrade';
|
|
213
|
+
const hint = byRun && runId ? o.runs?.get(runId) ?? null : null;
|
|
214
|
+
const trip = trips.step(d.mint, side, absBig(tokenDelta), ref);
|
|
215
|
+
if (trip.tradeKey)
|
|
216
|
+
tradeOfMint.set(d.mint, trip.tradeKey);
|
|
217
|
+
if (!trip.tradeKey)
|
|
218
|
+
continue;
|
|
219
|
+
const f = fillId !== null ? facts.get(fillId) : undefined;
|
|
220
|
+
// journals before task-1530 carry no decimals: a pump.fun coin's are fixed; any other mint stays unknown (no tokens)
|
|
221
|
+
const decimals = num(d.decimals) ?? f?.decimals ?? (isPumpFun(d.mint) ? PUMP_FUN_DECIMALS : null);
|
|
222
|
+
const tokens = decimals !== null ? Number(absBig(tokenDelta)) / 10 ** decimals : null;
|
|
223
|
+
const sol = Number(absBig(solDelta)) / LAMPORTS;
|
|
224
|
+
const priceSol = num(d.priceSol);
|
|
225
|
+
const priceUsd = num(d.priceUsd) ?? f?.priceUsd ?? null;
|
|
226
|
+
const solUsd = priceSol && priceUsd ? priceUsd / priceSol : null;
|
|
227
|
+
// an autotrade run's session ids are per run (s1, s2, …): scope them so two runs never share one
|
|
228
|
+
const rawSid = typeof d.sessionId === 'string' ? d.sessionId : typeof note.sessionId === 'string' ? note.sessionId : null;
|
|
229
|
+
const sessionId = cleanCode(byRun && runId && rawSid ? `${runId}:${rawSid}` : rawSid, 80);
|
|
230
|
+
// the tag: the structured one the desk now persists; else what an autotrade note carries; else the monitor exit
|
|
231
|
+
// rule journaled just before the sell
|
|
232
|
+
let tag = d.tag && typeof d.tag === 'object' ? sanitizeStoredTag(side, d.tag) : null;
|
|
233
|
+
if (side === 'buy' && !tag && typeof note.profile === 'string') {
|
|
234
|
+
const reason = cleanLedgerText(note.reason, 200);
|
|
235
|
+
const named = sessionId !== null && (hint?.declared.has(sessionId) ?? false);
|
|
236
|
+
tag = { profile: tagCode(note.profile), reason, exitReason: null, line: null, source: named ? 'structured' : 'default', ...(reason && !named ? { reasonSource: 'structured' } : {}) };
|
|
237
|
+
}
|
|
238
|
+
const exitNote = typeof note.exit === 'string' ? tagCode(note.exit) : null;
|
|
239
|
+
const seen = lastExitRule.get(d.mint);
|
|
240
|
+
const rule = side === 'sell' ? (exitNote ? { rule: exitNote, detail: null } : seen && e.ts - seen.t < 600_000 ? seen : null) : null;
|
|
241
|
+
if (side === 'sell' && rule && !(tag && tag.exitReason)) {
|
|
242
|
+
const code = tagCode(rule.rule);
|
|
243
|
+
const agentWords = cleanLedgerText(note.agentReason, 200);
|
|
244
|
+
tag = { profile: null, reason: tag?.reason ?? agentWords ?? rule.detail ?? null, exitReason: code, line: tag?.line ?? null, source: code === 'agent-exit' || code === 'agent-trim' ? 'structured' : 'rule' };
|
|
245
|
+
}
|
|
246
|
+
if (side === 'sell')
|
|
247
|
+
lastExitRule.delete(d.mint);
|
|
248
|
+
const noteStrategy = byRun && typeof note.strategy === 'string' ? { id: cleanCode(note.strategy, 32) ?? 'unknown', version: null, hash: cleanCode(note.strategyHash, 64) } : null;
|
|
249
|
+
res.drafts.push(fillDraft({
|
|
250
|
+
ref, tradeKey: trip.tradeKey, t: e.ts, source: 'desk', walletMode: 'paper', side, mint: d.mint, symbol: cleanLedgerText(d.symbol, 24) ?? '?', decimals,
|
|
251
|
+
sol, usd: solUsd ? sol * solUsd : null, tokens, priceSol, priceUsd,
|
|
252
|
+
feeSol: num(d.feeLamports) !== null ? num(d.feeLamports) / LAMPORTS : f?.feeSol ?? null,
|
|
253
|
+
impactPct: num(d.impactPct) ?? f?.impactPct ?? null, slippageBps: num(d.slippageBps) ?? f?.slippageBps ?? null,
|
|
254
|
+
realizedSol: side === 'sell' ? solOf(big(d.realizedLamports)) : null,
|
|
255
|
+
bookSol: num(d.bookLamports) !== null ? num(d.bookLamports) / LAMPORTS : f?.bookSolBefore ?? null,
|
|
256
|
+
bookUsd: null, closes: trip.closes, runId, agent: cleanLedgerText(d.agent, 40),
|
|
257
|
+
strategy: hint?.strategy ?? noteStrategy, sessionId,
|
|
258
|
+
model: cleanLedgerText(d.model, 40) ?? (sessionId ? hint?.models.get(sessionId) ?? null : null), tag,
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
if (events.length)
|
|
262
|
+
res.drafts.unshift(importDraft('desk', label, head, chain.ok, events.length, events[events.length - 1].ts, chain.ok ? undefined : `the journal's chain breaks at event ${chain.at}`));
|
|
263
|
+
if (!chain.ok)
|
|
264
|
+
res.problem = `${label}: its hash chain breaks at event ${chain.at} (imported as found; \`dexterm ledger verify\` shows the import)`;
|
|
265
|
+
return res;
|
|
266
|
+
}
|
|
267
|
+
/** A tag read back from a journal: re-cleaned (the journal is a file; never trust what it holds). */
|
|
268
|
+
function sanitizeStoredTag(side, g) {
|
|
269
|
+
const t = buildFillTag(side, { profile: g.profile, reason: g.reason, exitReason: g.exitReason, line: g.line });
|
|
270
|
+
if (!t)
|
|
271
|
+
return null;
|
|
272
|
+
const src = g.source;
|
|
273
|
+
const known = src === 'structured' || src === 'tag-line' || src === 'rule' || src === 'late' || src === 'default';
|
|
274
|
+
return { ...t, source: known ? src : t.source };
|
|
275
|
+
}
|
|
276
|
+
// ───────────────────────────── paper wallet (the TUI ticket) ─────────────────────────────
|
|
277
|
+
export function importPaperWallet(dbPath, deskFillIds = new Set()) {
|
|
278
|
+
const label = 'paper-wallet';
|
|
279
|
+
const res = { label, source: 'paper', drafts: [], events: 0, note: null, problem: null };
|
|
280
|
+
const w = readWallet(dbPath);
|
|
281
|
+
if (!w)
|
|
282
|
+
return res;
|
|
283
|
+
res.events = w.rows.length;
|
|
284
|
+
const facts = walletFacts(w);
|
|
285
|
+
const trips = new RoundTrips();
|
|
286
|
+
const scope = `w${w.createdAt ?? 0}`;
|
|
287
|
+
let n = 0;
|
|
288
|
+
for (const r of w.rows) {
|
|
289
|
+
const ref = `paper:${scope}:${r.id}`;
|
|
290
|
+
const trip = trips.step(r.mint, r.side, absBig(r.tokenDelta), ref);
|
|
291
|
+
if (deskFillIds.has(r.id) || !trip.tradeKey || !mintOk(r.mint))
|
|
292
|
+
continue; // the desk's fills come from its journal
|
|
293
|
+
const f = facts.get(r.id);
|
|
294
|
+
const sol = Number(absBig(r.solDelta)) / LAMPORTS;
|
|
295
|
+
const solUsd = r.priceUsd && r.priceSol ? r.priceUsd / r.priceSol : null;
|
|
296
|
+
res.drafts.push(fillDraft({
|
|
297
|
+
ref, tradeKey: trip.tradeKey, t: r.t, source: 'paper', walletMode: 'paper', side: r.side, mint: r.mint, symbol: cleanLedgerText(r.symbol, 24) ?? '?',
|
|
298
|
+
decimals: r.decimals, sol, usd: solUsd ? sol * solUsd : null, tokens: Number(absBig(r.tokenDelta)) / 10 ** r.decimals, priceSol: r.priceSol, priceUsd: r.priceUsd,
|
|
299
|
+
feeSol: f.feeSol, impactPct: r.impact, slippageBps: r.slippage, realizedSol: r.side === 'sell' ? Number(r.realized) / LAMPORTS : null,
|
|
300
|
+
bookSol: f.bookSolBefore, closes: trip.closes,
|
|
301
|
+
}));
|
|
302
|
+
n++;
|
|
303
|
+
}
|
|
304
|
+
if (w.rows.length)
|
|
305
|
+
res.drafts.unshift(importDraft('paper', label, `${scope}:${w.rows[w.rows.length - 1].id}`, null, w.rows.length, w.rows[w.rows.length - 1].t, n ? undefined : 'no fills outside the agent desk'));
|
|
306
|
+
return res;
|
|
307
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// task-1530 — ledger statistics: filters, the summary (win rate, P/L, drawdown, rug rate, breakdowns by profile,
|
|
2
|
+
// exit reason, strategy, source and wallet mode), the equity curve and the per-strategy comparison.
|
|
3
|
+
//
|
|
4
|
+
// Money is summed in ONE unit per report: SOL when every closed trade has a SOL figure, else USD (an autotrade run
|
|
5
|
+
// books in USD). The report names its unit. Paper, devnet and live are never blended silently: `modes` lists what
|
|
6
|
+
// is in the set, and the CLI says so when there is more than one.
|
|
7
|
+
/** `strategy` matches `id`, `id@version` or `id@hash-prefix`. */
|
|
8
|
+
export function matchStrategy(t, q) {
|
|
9
|
+
const s = t.strategy;
|
|
10
|
+
if (!s)
|
|
11
|
+
return q === 'none' || q === 'manual';
|
|
12
|
+
const [id, at] = q.split('@');
|
|
13
|
+
if (s.id !== id)
|
|
14
|
+
return false;
|
|
15
|
+
return !at || s.version === at || (s.hash ?? '').startsWith(at);
|
|
16
|
+
}
|
|
17
|
+
export function filterTrades(rows, f = {}) {
|
|
18
|
+
return rows.filter((t) => (!f.source || t.source === f.source) && (!f.mode || t.walletMode === f.mode) && (!f.status || t.status === f.status)
|
|
19
|
+
&& (!f.strategy || matchStrategy(t, f.strategy)) && (!f.profile || (t.profile.id ?? 'undeclared') === f.profile)
|
|
20
|
+
&& (!f.exitReason || (t.exitReason.code ?? 'untagged') === f.exitReason) && (!f.mint || t.mint === f.mint)
|
|
21
|
+
&& (!f.runId || t.runId === f.runId) && (!f.account || t.account === f.account) && (f.since === undefined || t.openedAt >= f.since) && (f.until === undefined || t.openedAt < f.until));
|
|
22
|
+
}
|
|
23
|
+
export function unitFor(rows) {
|
|
24
|
+
const closed = rows.filter((t) => t.status === 'closed');
|
|
25
|
+
return closed.length && closed.every((t) => t.pnlSol !== null) ? 'SOL' : closed.some((t) => t.pnlUsd !== null) ? 'USD' : 'SOL';
|
|
26
|
+
}
|
|
27
|
+
const pnlIn = (t, u) => (u === 'SOL' ? t.pnlSol : t.pnlUsd);
|
|
28
|
+
const r = (x, p = 6) => (x === null || !Number.isFinite(x) ? null : Number(x.toPrecision(p)));
|
|
29
|
+
/** Wilson 95 % interval for k wins in n trades. */
|
|
30
|
+
export function wilson(k, n) {
|
|
31
|
+
if (!n)
|
|
32
|
+
return null;
|
|
33
|
+
const z = 1.96, p = k / n, d = 1 + (z * z) / n;
|
|
34
|
+
const c = (p + (z * z) / (2 * n)) / d, h = (z * Math.sqrt((p * (1 - p)) / n + (z * z) / (4 * n * n))) / d;
|
|
35
|
+
return { value: r(p, 4), lo: r(Math.max(0, c - h), 4), hi: r(Math.min(1, c + h), 4) };
|
|
36
|
+
}
|
|
37
|
+
/** Cumulative realized P/L by close time. Drawdown is from the running peak; its % is of (starting book + peak). */
|
|
38
|
+
export function equityCurve(rows, unit = unitFor(rows)) {
|
|
39
|
+
const closed = rows.filter((t) => t.status === 'closed' && pnlIn(t, unit) !== null).sort((a, b) => (a.closedAt ?? 0) - (b.closedAt ?? 0) || a.tradeId.localeCompare(b.tradeId));
|
|
40
|
+
const firstBook = rows.map((t) => (unit === 'SOL' ? t.bookSol : t.bookUsd)).find((x) => x !== null && x > 0) ?? null;
|
|
41
|
+
let cum = 0, peak = 0, maxDd = 0, maxDdPct = firstBook ? 0 : null;
|
|
42
|
+
const points = [];
|
|
43
|
+
for (const t of closed) {
|
|
44
|
+
const p = pnlIn(t, unit);
|
|
45
|
+
cum += p;
|
|
46
|
+
peak = Math.max(peak, cum);
|
|
47
|
+
const dd = peak - cum;
|
|
48
|
+
maxDd = Math.max(maxDd, dd);
|
|
49
|
+
if (firstBook)
|
|
50
|
+
maxDdPct = Math.max(maxDdPct ?? 0, (dd / (firstBook + peak)) * 100);
|
|
51
|
+
points.push({ t: t.closedAt ?? t.openedAt, tradeId: t.tradeId, symbol: t.symbol, pnl: r(p), cum: r(cum), peak: r(peak), drawdown: r(dd) });
|
|
52
|
+
}
|
|
53
|
+
return { unit, start: firstBook, points, maxDrawdown: r(maxDd), maxDrawdownPct: maxDdPct === null ? null : r(maxDdPct, 4), final: r(cum) };
|
|
54
|
+
}
|
|
55
|
+
function group(rows, key, unit) {
|
|
56
|
+
const m = new Map();
|
|
57
|
+
for (const t of rows) {
|
|
58
|
+
const k = key(t);
|
|
59
|
+
m.set(k, [...(m.get(k) ?? []), t]);
|
|
60
|
+
}
|
|
61
|
+
return [...m.entries()].map(([k, ts]) => {
|
|
62
|
+
const c = ts.filter((t) => t.status === 'closed');
|
|
63
|
+
const wins = c.filter((t) => (pnlIn(t, unit) ?? 0) > 0).length;
|
|
64
|
+
const pcts = c.map((t) => t.pnlPct).filter((x) => x !== null);
|
|
65
|
+
const rugs = c.filter((t) => t.rug).length;
|
|
66
|
+
return {
|
|
67
|
+
key: k, trades: c.length, open: ts.length - c.length, wins, losses: c.length - wins, winRate: c.length ? r(wins / c.length, 4) : null,
|
|
68
|
+
pnl: r(c.reduce((s, t) => s + (pnlIn(t, unit) ?? 0), 0)), avgPnlPct: pcts.length ? r(pcts.reduce((a, b) => a + b, 0) / pcts.length, 4) : null,
|
|
69
|
+
rugs, rugRate: c.length ? r(rugs / c.length, 4) : null,
|
|
70
|
+
};
|
|
71
|
+
}).sort((a, b) => b.trades - a.trades || a.key.localeCompare(b.key));
|
|
72
|
+
}
|
|
73
|
+
export const strategyKey = (t) => (t.strategy ? `${t.strategy.id}@${t.strategy.version ?? (t.strategy.hash ?? '').slice(0, 8)}` : 'manual');
|
|
74
|
+
const mean = (xs) => (xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : null);
|
|
75
|
+
export function summarize(rows, unit = unitFor(rows)) {
|
|
76
|
+
const closed = rows.filter((t) => t.status === 'closed');
|
|
77
|
+
const pnls = closed.map((t) => pnlIn(t, unit) ?? 0);
|
|
78
|
+
const wins = pnls.filter((p) => p > 0).length;
|
|
79
|
+
const pcts = closed.map((t) => t.pnlPct).filter((x) => x !== null).sort((a, b) => a - b);
|
|
80
|
+
const gain = pnls.filter((p) => p > 0).reduce((a, b) => a + b, 0);
|
|
81
|
+
const loss = -pnls.filter((p) => p < 0).reduce((a, b) => a + b, 0);
|
|
82
|
+
const curve = equityCurve(rows, unit);
|
|
83
|
+
const rugs = closed.filter((t) => t.rug).length;
|
|
84
|
+
const sumOf = (f) => { const xs = closed.map(f).filter((x) => x !== null); return xs.length ? r(xs.reduce((a, b) => a + b, 0)) : null; };
|
|
85
|
+
const declared = (f, set) => (set.length ? r(set.filter(f).length / set.length, 4) : null);
|
|
86
|
+
const good = (s) => s !== 'missing' && s !== 'default';
|
|
87
|
+
return {
|
|
88
|
+
unit, modes: [...new Set(rows.map((t) => t.walletMode))],
|
|
89
|
+
trades: closed.length, open: rows.length - closed.length, wins, losses: closed.length - wins,
|
|
90
|
+
winRate: wilson(wins, closed.length),
|
|
91
|
+
pnl: r(pnls.reduce((a, b) => a + b, 0)), pnlSol: sumOf((t) => t.pnlSol), pnlUsd: sumOf((t) => t.pnlUsd),
|
|
92
|
+
avgPnlPct: r(mean(pcts), 4), medianPnlPct: pcts.length ? r(pcts.length % 2 ? pcts[(pcts.length - 1) / 2] : (pcts[pcts.length / 2 - 1] + pcts[pcts.length / 2]) / 2, 4) : null,
|
|
93
|
+
bestPct: pcts.length ? r(pcts[pcts.length - 1], 4) : null, worstPct: pcts.length ? r(pcts[0], 4) : null,
|
|
94
|
+
profitFactor: loss > 0 ? r(gain / loss, 4) : null,
|
|
95
|
+
maxDrawdown: curve.maxDrawdown, maxDrawdownPct: curve.maxDrawdownPct,
|
|
96
|
+
rugs, rugRate: closed.length ? r(rugs / closed.length, 4) : null,
|
|
97
|
+
avgHoldSec: closed.length ? Math.round(mean(closed.map((t) => t.holdSec))) : null,
|
|
98
|
+
avgMfePct: r(mean(closed.map((t) => t.mfePct).filter((x) => x !== null)), 4),
|
|
99
|
+
avgMaePct: r(mean(closed.map((t) => t.maePct).filter((x) => x !== null)), 4),
|
|
100
|
+
feesSol: sumOf((t) => t.feesSol), modelCostUsd: (() => { const xs = rows.map((t) => t.model.costUsd).filter((x) => x !== null); return xs.length ? r(xs.reduce((a, b) => a + b, 0)) : null; })(),
|
|
101
|
+
tagCoverage: { profileDeclared: declared((t) => good(t.profile.source), rows), exitDeclared: declared((t) => good(t.exitReason.source), closed) },
|
|
102
|
+
byProfile: group(rows, (t) => t.profile.id ?? 'undeclared', unit),
|
|
103
|
+
byExitReason: group(closed, (t) => t.exitReason.code ?? 'untagged', unit),
|
|
104
|
+
byStrategy: group(rows, strategyKey, unit),
|
|
105
|
+
bySource: group(rows, (t) => t.source, unit),
|
|
106
|
+
byMode: group(rows, (t) => t.walletMode, unit),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** One row per strategy version (a run of the same id at another version is another row): the forward-test table. */
|
|
110
|
+
export function compareStrategies(rows, unit = unitFor(rows)) {
|
|
111
|
+
const m = new Map();
|
|
112
|
+
for (const t of rows) {
|
|
113
|
+
const k = strategyKey(t);
|
|
114
|
+
m.set(k, [...(m.get(k) ?? []), t]);
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
unit,
|
|
118
|
+
rows: [...m.entries()].map(([key, ts]) => {
|
|
119
|
+
const s = summarize(ts, unit);
|
|
120
|
+
return { key, strategy: ts[0].strategy, modes: s.modes, trades: s.trades, open: s.open, winRate: s.winRate, pnl: s.pnl, avgPnlPct: s.avgPnlPct, maxDrawdown: s.maxDrawdown, rugRate: s.rugRate, avgHoldSec: s.avgHoldSec, avgMfePct: s.avgMfePct, avgMaePct: s.avgMaePct, modelCostUsd: s.modelCostUsd };
|
|
121
|
+
}).sort((a, b) => b.pnl - a.pnl || a.key.localeCompare(b.key)),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// task-1530 — the agent's PROFILE / REPROFILE / EXIT declarations as STRUCTURED fields.
|
|
2
|
+
//
|
|
3
|
+
// Before this card the only record of "why this entry, under which exit profile" and "why this exit" was the
|
|
4
|
+
// agent's free text, scraped afterwards by whoever read the transcript (a challenge driver read thinking blocks
|
|
5
|
+
// that the CLI streams as a paraphrase, so every fill of two sessions came out "undeclared profile / untagged
|
|
6
|
+
// exit" although the agent had written both lines). Now the trade tools take the declaration as typed arguments
|
|
7
|
+
// and the desk persists it on the fill itself (`agent.paper.fill` data.tag), so nothing is ever recovered from
|
|
8
|
+
// text again:
|
|
9
|
+
//
|
|
10
|
+
// paper_confirm profile, reason (and/or tag_line: the verbatim PROFILE line, parsed here)
|
|
11
|
+
// paper_close exit_reason, reason (and/or tag_line: the verbatim EXIT line)
|
|
12
|
+
// paper_tag a REPROFILE, or a PROFILE / EXIT line that was not passed with its fill (journaled as late)
|
|
13
|
+
//
|
|
14
|
+
// A typed field always wins over a parsed line; a line that does not parse is kept verbatim (capped) with source
|
|
15
|
+
// 'tag-line' only when it yields a profile or reason. Nothing here is an instruction: every string is one line,
|
|
16
|
+
// capped, control characters out, key-shaped runs redacted.
|
|
17
|
+
import { cleanCode, cleanLedgerText } from "./schema.js";
|
|
18
|
+
const B58 = '[1-9A-HJ-NP-Za-km-z]';
|
|
19
|
+
const stripMd = (s) => s.replace(/^[\s>*_`#-]+/, '').replace(/[*_`]+/g, '').trim();
|
|
20
|
+
const symKey = (s) => s.replace(/^\$/, '').replace(/[.,;:]+$/, '').toUpperCase();
|
|
21
|
+
/**
|
|
22
|
+
* Parse the tagged lines in a block of agent text (the format the exit-profile skills teach):
|
|
23
|
+
* PROFILE <profile> for <SYMBOL> (<first 4>…<last 4>): <why> · size <x> SOL (<p>% of bankroll)
|
|
24
|
+
* REPROFILE <SYMBOL> <from> -> <to>: <why>
|
|
25
|
+
* EXIT <SYMBOL> <reason>: <why>
|
|
26
|
+
*/
|
|
27
|
+
export function parseTagLines(text) {
|
|
28
|
+
const out = [];
|
|
29
|
+
if (typeof text !== 'string')
|
|
30
|
+
return out;
|
|
31
|
+
for (const raw of text.slice(0, 4000).split('\n')) {
|
|
32
|
+
const line = stripMd(raw);
|
|
33
|
+
let m;
|
|
34
|
+
if ((m = line.match(/^PROFILE\s+([a-z][a-z0-9-]*)\s+for\s+(\S+?)\s*(?:\(([^)]*)\))?\s*:\s*(.+)$/i))) {
|
|
35
|
+
const hint = (m[3] ?? '').match(new RegExp(`^(${B58}{3,8})\\s*(?:…|\\.\\.\\.)\\s*(${B58}{3,8})$`));
|
|
36
|
+
const size = m[4].match(/size\s+([0-9]*\.?[0-9]+)\s*SOL/i);
|
|
37
|
+
out.push({ kind: 'profile', profile: m[1].toLowerCase(), symbol: symKey(m[2]), mintHead: hint?.[1] ?? null, mintTail: hint?.[2] ?? null, why: m[4].trim(), sizeSol: size ? Number(size[1]) : null, line });
|
|
38
|
+
}
|
|
39
|
+
else if ((m = line.match(/^REPROFILE\s+(\S+)\s+([a-z][a-z0-9-]*)\s*(?:->|→)\s*([a-z][a-z0-9-]*)\s*:\s*(.+)$/i))) {
|
|
40
|
+
out.push({ kind: 'reprofile', symbol: symKey(m[1]), from: m[2].toLowerCase(), to: m[3].toLowerCase(), why: m[4].trim(), line });
|
|
41
|
+
}
|
|
42
|
+
else if ((m = line.match(/^EXIT\s+(\S+)\s+([a-z0-9-]+)\s*:\s*(.+)$/i))) {
|
|
43
|
+
out.push({ kind: 'exit', symbol: symKey(m[1]), reason: m[2].toLowerCase(), why: m[3].trim(), line });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
/** The profile / exit-reason alphabet: the strategy id rule (`^[a-z][a-z0-9-]{1,31}$`). Anything else is dropped. */
|
|
49
|
+
export function tagCode(x) {
|
|
50
|
+
const c = cleanCode(x, 32);
|
|
51
|
+
return c && /^[a-z][a-z0-9-]{0,31}$/.test(c) ? c : null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The structured tag a fill carries, or null when the agent declared nothing. Typed fields win; a `line` fills
|
|
55
|
+
* whatever the typed fields left empty (source 'tag-line' when the line supplied the profile / reason code).
|
|
56
|
+
*/
|
|
57
|
+
export function buildFillTag(side, input) {
|
|
58
|
+
if (!input || typeof input !== 'object')
|
|
59
|
+
return null;
|
|
60
|
+
let profile = side === 'buy' ? tagCode(input.profile) : null;
|
|
61
|
+
let exitReason = side === 'sell' ? tagCode(input.exitReason) : null;
|
|
62
|
+
let reason = cleanLedgerText(input.reason, 200);
|
|
63
|
+
const line = cleanLedgerText(input.line, 300);
|
|
64
|
+
const structured = side === 'buy' ? profile !== null : exitReason !== null;
|
|
65
|
+
let fromLine = false;
|
|
66
|
+
if (line) {
|
|
67
|
+
const tags = parseTagLines(line);
|
|
68
|
+
const p = side === 'buy' ? tags.find((g) => g.kind === 'profile') : tags.find((g) => g.kind === 'exit');
|
|
69
|
+
if (p && p.kind === 'profile') {
|
|
70
|
+
if (!profile) {
|
|
71
|
+
profile = tagCode(p.profile);
|
|
72
|
+
fromLine = profile !== null;
|
|
73
|
+
}
|
|
74
|
+
if (!reason)
|
|
75
|
+
reason = cleanLedgerText(p.why, 200);
|
|
76
|
+
}
|
|
77
|
+
if (p && p.kind === 'exit') {
|
|
78
|
+
if (!exitReason) {
|
|
79
|
+
exitReason = tagCode(p.reason);
|
|
80
|
+
fromLine = exitReason !== null;
|
|
81
|
+
}
|
|
82
|
+
if (!reason)
|
|
83
|
+
reason = cleanLedgerText(p.why, 200);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!profile && !exitReason && !reason && !line)
|
|
87
|
+
return null;
|
|
88
|
+
const source = structured ? 'structured' : fromLine ? 'tag-line' : 'missing';
|
|
89
|
+
return { profile, reason, exitReason, line, source };
|
|
90
|
+
}
|
|
91
|
+
export function buildLateTag(input) {
|
|
92
|
+
const line = cleanLedgerText(input.line, 300);
|
|
93
|
+
const reason = cleanLedgerText(input.reason, 200);
|
|
94
|
+
const kind = input.kind;
|
|
95
|
+
const parsed = line ? parseTagLines(line)[0] ?? null : null;
|
|
96
|
+
const k = kind === 'profile' || kind === 'reprofile' || kind === 'exit' ? kind : parsed?.kind ?? null;
|
|
97
|
+
if (!k)
|
|
98
|
+
return { ok: false, why: 'say what this declares: kind profile | reprofile | exit, or a PROFILE / REPROFILE / EXIT line' };
|
|
99
|
+
if (k === 'profile') {
|
|
100
|
+
const profile = tagCode(input.profile) ?? (parsed?.kind === 'profile' ? tagCode(parsed.profile) : null);
|
|
101
|
+
if (!profile)
|
|
102
|
+
return { ok: false, why: 'a profile tag needs a profile id (or a PROFILE line that names one)' };
|
|
103
|
+
return { ok: true, tagKind: 'profile', profile, reason: reason ?? (parsed?.kind === 'profile' ? cleanLedgerText(parsed.why, 200) : null), line };
|
|
104
|
+
}
|
|
105
|
+
if (k === 'reprofile') {
|
|
106
|
+
const to = tagCode(input.to) ?? (parsed?.kind === 'reprofile' ? tagCode(parsed.to) : null);
|
|
107
|
+
if (!to)
|
|
108
|
+
return { ok: false, why: 'a reprofile tag needs `to` (or a REPROFILE line)' };
|
|
109
|
+
const from = tagCode(input.from) ?? (parsed?.kind === 'reprofile' ? tagCode(parsed.from) : null);
|
|
110
|
+
return { ok: true, tagKind: 'reprofile', from, to, reason: reason ?? (parsed?.kind === 'reprofile' ? cleanLedgerText(parsed.why, 200) : null), line };
|
|
111
|
+
}
|
|
112
|
+
const exitReason = tagCode(input.exitReason) ?? (parsed?.kind === 'exit' ? tagCode(parsed.reason) : null);
|
|
113
|
+
if (!exitReason)
|
|
114
|
+
return { ok: false, why: 'an exit tag needs an exit reason code (or an EXIT line)' };
|
|
115
|
+
return { ok: true, tagKind: 'exit', exitReason, reason: reason ?? (parsed?.kind === 'exit' ? cleanLedgerText(parsed.why, 200) : null), line };
|
|
116
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// task-1530 — the ledger VIEW: what the file holds plus what the sources hold that it does not have yet.
|
|
2
|
+
//
|
|
3
|
+
// Reads (`dexterm ledger trades|summary|export`, the MCP ledger tools, the PnL card) never wait for a sync and
|
|
4
|
+
// never write: they fold the file's events together with fresh drafts from the sources, in memory. Only
|
|
5
|
+
// `dexterm ledger sync` appends those drafts to the file (and so gives them a place in the hash chain).
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { paperAgentJournalPath } from "../agent/rendezvous.js";
|
|
8
|
+
import { dextermHomeDir, readTradeLedger, tradeLedgerPath } from "./file.js";
|
|
9
|
+
import { foldTrades } from "./fold.js";
|
|
10
|
+
import { importDeskJournal, importPaperWallet } from "./sources.js";
|
|
11
|
+
import { LEDGER_VERSION } from "./schema.js";
|
|
12
|
+
export function defaultSourcePaths(env = process.env, walletDb = null) {
|
|
13
|
+
return {
|
|
14
|
+
deskJournal: paperAgentJournalPath(env),
|
|
15
|
+
walletDb,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Drafts from every data-layer source (run ledgers are added by the caller that can read them). */
|
|
19
|
+
export function collectDataSources(p, o = {}) {
|
|
20
|
+
const desk = importDeskJournal(p.deskJournal, { walletDb: p.walletDb, ...(o.runs ? { runs: o.runs } : {}) });
|
|
21
|
+
const out = [desk];
|
|
22
|
+
if (p.walletDb)
|
|
23
|
+
out.push(importPaperWallet(p.walletDb, desk.fillIds));
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
/** Fold the file plus the pending drafts. A file that fails its check still folds (the view says so). */
|
|
27
|
+
export function buildLedgerView(path, sources, now = Date.now()) {
|
|
28
|
+
const file = readTradeLedger(path);
|
|
29
|
+
const have = new Set();
|
|
30
|
+
for (const e of file.events)
|
|
31
|
+
if ('ref' in e && typeof e.ref === 'string')
|
|
32
|
+
have.add(e.ref);
|
|
33
|
+
const pending = [];
|
|
34
|
+
const meta = [];
|
|
35
|
+
for (const s of sources) {
|
|
36
|
+
let n = 0;
|
|
37
|
+
for (const d of s.drafts) {
|
|
38
|
+
const ref = d.ref;
|
|
39
|
+
if (!ref || have.has(ref))
|
|
40
|
+
continue;
|
|
41
|
+
have.add(ref);
|
|
42
|
+
pending.push(d);
|
|
43
|
+
n++;
|
|
44
|
+
}
|
|
45
|
+
meta.push({ label: s.label, source: s.source, events: s.events, pending: n, note: s.note, problem: s.problem });
|
|
46
|
+
}
|
|
47
|
+
let seq = file.events.length;
|
|
48
|
+
const events = [...file.events, ...pending.map((d) => ({ ...d, seq: ++seq, v: LEDGER_VERSION, prev: 'pending', hash: 'pending' }))];
|
|
49
|
+
return { path, file, pending, sources: meta, events, trades: foldTrades(events, { now }) };
|
|
50
|
+
}
|
|
51
|
+
export { tradeLedgerPath };
|
|
@@ -7,6 +7,5 @@ export { HOLD_MS, CHALLENGE_TTL_MS, ECHO_GUARD_MS, RISK_ACCEPT_TTL_MS, ConfirmEr
|
|
|
7
7
|
export { Journal, redact, eventHash } from "./journal.js";
|
|
8
8
|
export { RpcOutage } from "./executor.js";
|
|
9
9
|
export { PaperPlanExecutor } from "./executor-paper.js";
|
|
10
|
-
export { LivePlanExecutor, LIVE_BUILD_TTL_MS } from "./executor-live.js";
|
|
11
10
|
export { PlanEngine, PolicyRefusal, TERMINAL } from "./engine.js";
|
|
12
11
|
export { PlanService, parseAmount, parseTrigger, parseExit, planStatusLabel, defaultPlansJournalPath } from "./service.js";
|
|
@@ -66,38 +66,14 @@ export class PlanService {
|
|
|
66
66
|
this.gate = new ConfirmGate({ clock: this.clock, journal: this.journal, settings: this.settings, rng: opts.rng });
|
|
67
67
|
this.paper = opts.executor ?? new PaperPlanExecutor({ clock: this.clock, solUsd: opts.solUsd });
|
|
68
68
|
const deps = { clock: this.clock, journal: this.journal, executor: this.paper, gate: this.gate, safety: opts.safety, priceMaxAgeMs: opts.priceMaxAgeMs, debounceTicks: opts.debounceTicks };
|
|
69
|
-
// Recover from an existing journal (replay + settle in-flight) or start fresh. The fire path
|
|
70
|
-
//
|
|
69
|
+
// Recover from an existing journal (replay + settle in-flight) or start fresh. The fire path is PAPER
|
|
70
|
+
// (task-1571: there is no live executor in the build; dexterm holds no key).
|
|
71
71
|
this.engine = path && existsSync(path) ? PlanEngine.recover(deps) : new PlanEngine(deps);
|
|
72
72
|
}
|
|
73
|
-
/** The executor the engine fires through
|
|
73
|
+
/** The executor the engine fires through (the paper executor unless a test injects one). */
|
|
74
74
|
get executor() { return this.engine.executor; }
|
|
75
75
|
/** 'paper' unless a live executor is installed. Surfaces that cannot prove 'paper' must render LIVE. */
|
|
76
76
|
mode() { return this.engine.executor.kind === 'live' ? 'live' : 'paper'; }
|
|
77
|
-
// ── the fire path (task-1363). Switching to LIVE is a physical keypress on the plans screen (never voice,
|
|
78
|
-
// an agent, a pipe or the popup): the gesture is required here AND the signer only fills from a fast wallet
|
|
79
|
-
// that was itself created by a TTY keypress. A switch while plans are armed/filled would strand their legs on
|
|
80
|
-
// the other executor's ledger, so both directions refuse until those plans are cancelled or finished. ──
|
|
81
|
-
useLive(executor, gesture) {
|
|
82
|
-
if (gesture?.gesture !== 'keypress')
|
|
83
|
-
throw new ConfirmError('live-needs-keypress', 'switching plan fills to LIVE needs a physical keypress');
|
|
84
|
-
if (executor.kind !== 'live')
|
|
85
|
-
throw new PlanError('bad-request', 'useLive needs a live executor');
|
|
86
|
-
const open = this.engine.openPlans();
|
|
87
|
-
if (open.length)
|
|
88
|
-
throw new PolicyRefusal('open-plans', `cancel or finish ${open.length} open plan(s) before switching the fire path`);
|
|
89
|
-
this.engine.replaceExecutor(executor);
|
|
90
|
-
this.journal.append('settings.executor', { data: { kind: 'live', network: 'devnet', by: 'keypress', label: executor.describe?.() ?? 'live' } });
|
|
91
|
-
}
|
|
92
|
-
usePaper() {
|
|
93
|
-
if (this.engine.executor === this.paper)
|
|
94
|
-
return;
|
|
95
|
-
const open = this.engine.openPlans();
|
|
96
|
-
if (open.length)
|
|
97
|
-
throw new PolicyRefusal('open-plans', `cancel or finish ${open.length} open plan(s) before switching the fire path`);
|
|
98
|
-
this.engine.replaceExecutor(this.paper);
|
|
99
|
-
this.journal.append('settings.executor', { data: { kind: 'paper' } });
|
|
100
|
-
}
|
|
101
77
|
// ── proposals (voice/claude/user/batch all land here; a proposal is inert until armed) ──
|
|
102
78
|
propose(input, ctx) {
|
|
103
79
|
return this.engine.propose(input, { supply: ctx.supply, by: ctx.by ?? 'user', ref: ctx.ref ?? null });
|