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,330 @@
|
|
|
1
|
+
// dexterm autotrade — trade-fire receipts, drift and the read-only wallet reconcile (task-1645, 0.2.0 card A).
|
|
2
|
+
//
|
|
3
|
+
// The user's executor reports what it did with a signal, as DATA only: `dexterm autotrade receipt …`, or by
|
|
4
|
+
// appending a `dexterm-trade-receipt/1` line to $DEXTERM_HOME/autotrade/real/receipts.jsonl itself.
|
|
5
|
+
// status executed | rejected | failed, optional fill {priceUsd, sizeSol, tokens, txSig}, a short reason
|
|
6
|
+
// A receipt never makes dexterm do anything: it only moves a real-mode position from `unconfirmed` to
|
|
7
|
+
// executed / rejected / failed in the ledger, the Q screen and `autotrade audit`, and measures the drift between
|
|
8
|
+
// what was signalled and what was filled. Exits keep firing on the monitor's own (paper-model) view regardless.
|
|
9
|
+
//
|
|
10
|
+
// The wallet reconcile reads a PUBLIC address the user supplies (SOL balance and token holdings, two read-only RPC
|
|
11
|
+
// methods on an allowlist) and lists the mints dexterm thinks are held that the wallet does not show, and back.
|
|
12
|
+
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readSync, statSync, writeSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { realDir } from "./outbox.js";
|
|
15
|
+
import { ACCOUNT_ID_RE } from "./signal.js";
|
|
16
|
+
export const RECEIPT_SCHEMA = 'dexterm-trade-receipt/1';
|
|
17
|
+
export const RECEIPT_STATUSES = ['executed', 'rejected', 'failed'];
|
|
18
|
+
export const receiptsPath = (home) => join(realDir(home), 'receipts.jsonl');
|
|
19
|
+
const TX_SIG = /^[1-9A-HJ-NP-Za-km-z]{64,90}$/;
|
|
20
|
+
const HEX32 = /^[0-9a-f]{32}$/;
|
|
21
|
+
const pos = (v) => (typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : null);
|
|
22
|
+
/** Free text from an executor is data: printable ASCII only, 120 chars. */
|
|
23
|
+
const text = (v, max) => { if (typeof v !== 'string')
|
|
24
|
+
return null; const t = v.replace(/[^\x20-\x7e]/g, '').trim().slice(0, max); return t || null; };
|
|
25
|
+
export function validateReceipt(x) {
|
|
26
|
+
const r = x;
|
|
27
|
+
if (!r || typeof r !== 'object')
|
|
28
|
+
return { ok: false, error: 'not an object' };
|
|
29
|
+
if (r.schema !== RECEIPT_SCHEMA)
|
|
30
|
+
return { ok: false, error: 'schema' };
|
|
31
|
+
if (typeof r.signalId !== 'string' || !HEX32.test(r.signalId))
|
|
32
|
+
return { ok: false, error: 'signalId' };
|
|
33
|
+
if (!Number.isSafeInteger(r.nonce) || r.nonce < 1)
|
|
34
|
+
return { ok: false, error: 'nonce' };
|
|
35
|
+
if (typeof r.accountId !== 'string' || !ACCOUNT_ID_RE.test(r.accountId))
|
|
36
|
+
return { ok: false, error: 'accountId' };
|
|
37
|
+
if (!RECEIPT_STATUSES.includes(r.status))
|
|
38
|
+
return { ok: false, error: 'status' };
|
|
39
|
+
if (typeof r.at !== 'number' || !Number.isFinite(r.at))
|
|
40
|
+
return { ok: false, error: 'at' };
|
|
41
|
+
let fill = null;
|
|
42
|
+
if (r.fill !== undefined && r.fill !== null) {
|
|
43
|
+
const f = r.fill;
|
|
44
|
+
if (typeof f !== 'object')
|
|
45
|
+
return { ok: false, error: 'fill' };
|
|
46
|
+
if (f.txSig !== undefined && f.txSig !== null && (typeof f.txSig !== 'string' || !TX_SIG.test(f.txSig)))
|
|
47
|
+
return { ok: false, error: 'fill.txSig is not a base58 signature' };
|
|
48
|
+
for (const k of ['priceUsd', 'sizeSol', 'tokens'])
|
|
49
|
+
if (f[k] !== undefined && f[k] !== null && pos(f[k]) === null)
|
|
50
|
+
return { ok: false, error: `fill.${k}` };
|
|
51
|
+
fill = { priceUsd: pos(f.priceUsd), sizeSol: pos(f.sizeSol), tokens: pos(f.tokens), txSig: typeof f.txSig === 'string' ? f.txSig : null };
|
|
52
|
+
}
|
|
53
|
+
if (r.status !== 'executed' && fill && (fill.sizeSol || fill.tokens))
|
|
54
|
+
return { ok: false, error: 'only an executed receipt carries a fill size' };
|
|
55
|
+
return { ok: true, receipt: { schema: RECEIPT_SCHEMA, signalId: r.signalId, nonce: r.nonce, accountId: r.accountId, status: r.status, at: r.at, fill, reason: text(r.reason, 120), executor: text(r.executor, 32) } };
|
|
56
|
+
}
|
|
57
|
+
export function appendReceipt(home, r) {
|
|
58
|
+
const v = validateReceipt(r);
|
|
59
|
+
if (!v.ok)
|
|
60
|
+
throw new Error(`receipt: ${v.error}`);
|
|
61
|
+
mkdirSync(realDir(home), { recursive: true, mode: 0o700 });
|
|
62
|
+
const fd = openSync(receiptsPath(home), 'a', 0o600);
|
|
63
|
+
try {
|
|
64
|
+
writeSync(fd, `${JSON.stringify(v.receipt)}\n`);
|
|
65
|
+
fsyncSync(fd);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
closeSync(fd);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Why a receipt does not belong to a known signal (or null): unknown nonce, another account, an id mismatch. */
|
|
72
|
+
export function receiptMismatch(r, s) {
|
|
73
|
+
if (!s)
|
|
74
|
+
return 'unknown-signal';
|
|
75
|
+
if (s.id !== r.signalId)
|
|
76
|
+
return 'signal-id-mismatch';
|
|
77
|
+
if (s.accountId !== r.accountId)
|
|
78
|
+
return 'wrong-account';
|
|
79
|
+
// task-1654: a receipt for a `signal test` proves the round trip (signal test reports it); it is not an orphan.
|
|
80
|
+
if (s.type === 'test')
|
|
81
|
+
return 'test-signal';
|
|
82
|
+
if (s.type !== 'trade')
|
|
83
|
+
return 'not-a-trade-signal';
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Reads receipts.jsonl incrementally (byte offset). Every valid receipt for a known signal is returned once; the
|
|
88
|
+
* rest are counted (`invalid`, `orphan`) and never acted on. A partial last line waits for its newline.
|
|
89
|
+
*/
|
|
90
|
+
export class ReceiptsTail {
|
|
91
|
+
offset = 0;
|
|
92
|
+
carry = '';
|
|
93
|
+
invalid = 0;
|
|
94
|
+
orphan = 0;
|
|
95
|
+
path;
|
|
96
|
+
lookup;
|
|
97
|
+
constructor(home, lookup) { this.path = receiptsPath(home); this.lookup = lookup; }
|
|
98
|
+
poll() {
|
|
99
|
+
if (!existsSync(this.path))
|
|
100
|
+
return [];
|
|
101
|
+
const size = statSync(this.path).size;
|
|
102
|
+
if (size < this.offset) {
|
|
103
|
+
this.offset = 0;
|
|
104
|
+
this.carry = '';
|
|
105
|
+
} // truncated: read again
|
|
106
|
+
if (size === this.offset)
|
|
107
|
+
return [];
|
|
108
|
+
const fd = openSync(this.path, 'r');
|
|
109
|
+
const buf = Buffer.alloc(Math.min(size - this.offset, 4 << 20));
|
|
110
|
+
let n = 0;
|
|
111
|
+
try {
|
|
112
|
+
n = readSync(fd, buf, 0, buf.length, this.offset);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
closeSync(fd);
|
|
116
|
+
}
|
|
117
|
+
this.offset += n;
|
|
118
|
+
const chunk = this.carry + buf.subarray(0, n).toString('utf8');
|
|
119
|
+
const lines = chunk.split('\n');
|
|
120
|
+
this.carry = lines.pop() ?? '';
|
|
121
|
+
const out = [];
|
|
122
|
+
for (const l of lines) {
|
|
123
|
+
if (!l.trim())
|
|
124
|
+
continue;
|
|
125
|
+
let raw;
|
|
126
|
+
try {
|
|
127
|
+
raw = JSON.parse(l);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
this.invalid++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const v = validateReceipt(raw);
|
|
134
|
+
if (!v.ok) {
|
|
135
|
+
this.invalid++;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const mm = receiptMismatch(v.receipt, this.lookup(v.receipt.nonce));
|
|
139
|
+
if (mm === 'test-signal')
|
|
140
|
+
continue;
|
|
141
|
+
if (mm) {
|
|
142
|
+
this.orphan++;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
out.push(v.receipt);
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export function readReceipts(home) {
|
|
151
|
+
const p = receiptsPath(home);
|
|
152
|
+
if (!existsSync(p))
|
|
153
|
+
return { receipts: [], invalid: 0 };
|
|
154
|
+
const out = [];
|
|
155
|
+
let invalid = 0;
|
|
156
|
+
for (const l of readFileSync(p, 'utf8').split('\n')) {
|
|
157
|
+
if (!l.trim())
|
|
158
|
+
continue;
|
|
159
|
+
try {
|
|
160
|
+
const v = validateReceipt(JSON.parse(l));
|
|
161
|
+
if (v.ok)
|
|
162
|
+
out.push(v.receipt);
|
|
163
|
+
else
|
|
164
|
+
invalid++;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
invalid++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { receipts: out, invalid };
|
|
171
|
+
}
|
|
172
|
+
const r1 = (v) => Math.round(v * 10) / 10;
|
|
173
|
+
/** Signals vs receipts: one row per trade signal. */
|
|
174
|
+
export function driftReport(signals, receipts, now, tolPct = 5) {
|
|
175
|
+
const trades = signals.filter((s) => s.type === 'trade');
|
|
176
|
+
const byNonce = new Map();
|
|
177
|
+
let orphanReceipts = 0;
|
|
178
|
+
const known = new Map(trades.map((s) => [s.nonce, s]));
|
|
179
|
+
const tests = new Set(signals.filter((s) => s.type === 'test').map((s) => s.nonce));
|
|
180
|
+
for (const r of receipts) {
|
|
181
|
+
if (tests.has(r.nonce))
|
|
182
|
+
continue;
|
|
183
|
+
if (receiptMismatch(r, known.get(r.nonce))) {
|
|
184
|
+
orphanReceipts++;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const l = byNonce.get(r.nonce) ?? [];
|
|
188
|
+
l.push(r);
|
|
189
|
+
byNonce.set(r.nonce, l);
|
|
190
|
+
}
|
|
191
|
+
const counts = { unconfirmed: 0, executed: 0, rejected: 0, failed: 0, 'expired-unconfirmed': 0 };
|
|
192
|
+
let sizeDrift = 0;
|
|
193
|
+
const rows = trades.map((s) => {
|
|
194
|
+
const rs = (byNonce.get(s.nonce) ?? []).sort((a, b) => a.at - b.at);
|
|
195
|
+
const last = rs[rs.length - 1];
|
|
196
|
+
const state = last ? last.status : now > s.expiresAt ? 'expired-unconfirmed' : 'unconfirmed';
|
|
197
|
+
counts[state]++;
|
|
198
|
+
const filled = last?.status === 'executed' ? last.fill?.sizeSol ?? null : null;
|
|
199
|
+
const sizeDriftPct = filled !== null && s.sizeSol > 0 ? r1(((filled - s.sizeSol) / s.sizeSol) * 100) : null;
|
|
200
|
+
const px = last?.status === 'executed' ? last.fill?.priceUsd ?? null : null;
|
|
201
|
+
const priceDriftPct = px !== null && s.price.usd ? r1(((px - s.price.usd) / s.price.usd) * 100) : null;
|
|
202
|
+
if (sizeDriftPct !== null && Math.abs(sizeDriftPct) > tolPct)
|
|
203
|
+
sizeDrift++;
|
|
204
|
+
return {
|
|
205
|
+
nonce: s.nonce, id: s.id, runId: s.runId, side: s.side, mint: s.mint, symbol: s.symbol, issuedAt: s.issuedAt, state, signalSol: s.sizeSol, filledSol: filled, sizeDriftPct, priceDriftPct,
|
|
206
|
+
txSig: last?.fill?.txSig ?? null, conflict: new Set(rs.map((r) => r.status)).size > 1, reason: last?.reason ?? null,
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
return { rows, counts, unconfirmed: counts.unconfirmed + counts['expired-unconfirmed'], sizeDrift, orphanReceipts };
|
|
210
|
+
}
|
|
211
|
+
const r2 = (v) => Math.round(v * 100) / 100;
|
|
212
|
+
/** One receipt against its trade signal (null for anything that is not a known trade signal). */
|
|
213
|
+
export function divergenceOf(s, r) {
|
|
214
|
+
if (!s || s.type !== 'trade' || receiptMismatch(r, s))
|
|
215
|
+
return null;
|
|
216
|
+
const f = r.status === 'executed' ? r.fill : null;
|
|
217
|
+
const px = f?.priceUsd ?? (f?.sizeSol && f.tokens ? (f.sizeSol * s.price.solUsd) / f.tokens : null);
|
|
218
|
+
const adverse = (ref) => (px && ref && ref > 0 ? r2(((s.side === 'buy' ? px - ref : ref - px) / ref) * 100) : null);
|
|
219
|
+
const modelTokens = s.model?.tokens ?? null;
|
|
220
|
+
const fillRatio = !f ? null : s.side === 'buy' ? (f.sizeSol && s.sizeSol > 0 ? Math.round((f.sizeSol / s.sizeSol) * 1000) / 1000 : null) : (f.tokens && modelTokens ? Math.round((f.tokens / modelTokens) * 1000) / 1000 : null);
|
|
221
|
+
return {
|
|
222
|
+
nonce: s.nonce, side: s.side, status: r.status, slippageVsPaperPct: adverse(s.model?.priceUsd ?? null), slippageVsQuotePct: adverse(s.price.usd),
|
|
223
|
+
latencyMs: Math.max(0, r.at - s.issuedAt), fillRatio, partial: fillRatio !== null && fillRatio < 0.95, reason: r.reason,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const quantile = (xs, q) => { if (!xs.length)
|
|
227
|
+
return null; const a = [...xs].sort((x, y) => x - y); return a[Math.min(a.length - 1, Math.floor(q * a.length))]; };
|
|
228
|
+
/** Every trade signal's newest receipt, summarized. */
|
|
229
|
+
export function divergenceReport(signals, receipts) {
|
|
230
|
+
const bySig = new Map(signals.filter((s) => s.type === 'trade').map((s) => [s.nonce, s]));
|
|
231
|
+
const newest = new Map();
|
|
232
|
+
for (const r of receipts) {
|
|
233
|
+
const s = bySig.get(r.nonce);
|
|
234
|
+
if (!s || receiptMismatch(r, s))
|
|
235
|
+
continue;
|
|
236
|
+
const p = newest.get(r.nonce);
|
|
237
|
+
if (!p || r.at >= p.at)
|
|
238
|
+
newest.set(r.nonce, r);
|
|
239
|
+
}
|
|
240
|
+
const rows = [...newest.values()].map((r) => divergenceOf(bySig.get(r.nonce), r)).filter((d) => !!d).sort((a, b) => a.nonce - b.nonce);
|
|
241
|
+
const ex = rows.filter((d) => d.status === 'executed');
|
|
242
|
+
const sl = ex.map((d) => d.slippageVsPaperPct ?? d.slippageVsQuotePct).filter((v) => v !== null);
|
|
243
|
+
const lat = rows.map((d) => d.latencyMs);
|
|
244
|
+
const reasons = {};
|
|
245
|
+
for (const d of rows)
|
|
246
|
+
if (d.status !== 'executed') {
|
|
247
|
+
const k = `${d.status}: ${(d.reason ?? 'no reason').slice(0, 40)}`;
|
|
248
|
+
reasons[k] = (reasons[k] ?? 0) + 1;
|
|
249
|
+
}
|
|
250
|
+
const n = ex.length;
|
|
251
|
+
return {
|
|
252
|
+
rows, trades: bySig.size, receipted: rows.length, executed: n, rejected: rows.filter((d) => d.status === 'rejected').length, failed: rows.filter((d) => d.status === 'failed').length, partial: rows.filter((d) => d.partial).length,
|
|
253
|
+
slippage: { n: sl.length, medianPct: quantile(sl, 0.5), p90Pct: quantile(sl, 0.9), meanPct: sl.length ? r2(sl.reduce((a, b) => a + b, 0) / sl.length) : null },
|
|
254
|
+
latency: { n: lat.length, medianMs: quantile(lat, 0.5), p90Ms: quantile(lat, 0.9) },
|
|
255
|
+
reasons,
|
|
256
|
+
note: n === 0 ? 'no executed receipt yet: nothing to compare paper with' : n < 30 ? `only ${n} executed fill(s): too few to calibrate paper fills with confidence` : `${n} executed fills`,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
export function divergenceLines(d) {
|
|
260
|
+
const out = [`paper vs real: ${d.receipted}/${d.trades} trade signals have a receipt · executed ${d.executed} · rejected ${d.rejected} · failed ${d.failed} · partial ${d.partial}`];
|
|
261
|
+
if (d.slippage.n)
|
|
262
|
+
out.push(` slippage vs paper fill: median ${d.slippage.medianPct}% · p90 ${d.slippage.p90Pct}% · mean ${d.slippage.meanPct}% (positive = the real fill was worse than paper)`);
|
|
263
|
+
if (d.latency.n)
|
|
264
|
+
out.push(` latency signal -> receipt: median ${Math.round((d.latency.medianMs ?? 0) / 100) / 10} s · p90 ${Math.round((d.latency.p90Ms ?? 0) / 100) / 10} s`);
|
|
265
|
+
for (const [k, v] of Object.entries(d.reasons).slice(0, 5))
|
|
266
|
+
out.push(` ${v}× ${k}`);
|
|
267
|
+
out.push(` ${d.note}`);
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
// ───────────────────────────── read-only wallet reconcile ─────────────────────────────
|
|
271
|
+
/** The only RPC methods the reconcile may call. Anything else is refused before it leaves the process. */
|
|
272
|
+
export const RECONCILE_RPC_METHODS = ['getBalance', 'getTokenAccountsByOwner'];
|
|
273
|
+
const TOKEN_PROGRAMS = ['TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'];
|
|
274
|
+
const PUBKEY = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
275
|
+
/** Read a public wallet (balance + token holdings) and compare with the mints the real-mode book holds / closed. */
|
|
276
|
+
export async function reconcileWallet(wallet, rpc, book) {
|
|
277
|
+
if (!PUBKEY.test(wallet))
|
|
278
|
+
throw new Error('reconcile: the wallet is a public base58 address (never a key)');
|
|
279
|
+
const call = (m, p) => { if (!RECONCILE_RPC_METHODS.includes(m))
|
|
280
|
+
return Promise.reject(new Error(`rpc method ${m} is not on the read-only allowlist`)); return rpc(m, p); };
|
|
281
|
+
const errors = [];
|
|
282
|
+
let sol = null;
|
|
283
|
+
try {
|
|
284
|
+
const b = (await call('getBalance', [wallet, { commitment: 'confirmed' }]));
|
|
285
|
+
sol = typeof b?.value === 'number' ? b.value / 1e9 : null;
|
|
286
|
+
}
|
|
287
|
+
catch (e) {
|
|
288
|
+
errors.push(`getBalance: ${e.message.slice(0, 120)}`);
|
|
289
|
+
}
|
|
290
|
+
const holdings = {};
|
|
291
|
+
for (const programId of TOKEN_PROGRAMS) {
|
|
292
|
+
try {
|
|
293
|
+
const r = (await call('getTokenAccountsByOwner', [wallet, { programId }, { encoding: 'jsonParsed', commitment: 'confirmed' }]));
|
|
294
|
+
for (const a of r?.value ?? []) {
|
|
295
|
+
const info = a.account?.data?.parsed?.info;
|
|
296
|
+
if (!info?.mint || !PUBKEY.test(info.mint))
|
|
297
|
+
continue;
|
|
298
|
+
holdings[info.mint] = (holdings[info.mint] ?? 0) + (Number(info.tokenAmount?.uiAmount) || 0);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (e) {
|
|
302
|
+
errors.push(`getTokenAccountsByOwner: ${e.message.slice(0, 120)}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const ok = errors.length === 0;
|
|
306
|
+
return {
|
|
307
|
+
wallet, sol, holdings,
|
|
308
|
+
missing: ok ? book.held.filter((m) => !(holdings[m] > 0)) : [],
|
|
309
|
+
stillHeld: ok ? book.closed.filter((m) => holdings[m] > 0 && !book.held.includes(m)) : [],
|
|
310
|
+
errors,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/** From the trade signals alone (nonce order): the mints a real-mode book holds, and those it closed. */
|
|
314
|
+
export function heldFromSignals(signals) {
|
|
315
|
+
const held = new Set();
|
|
316
|
+
const closed = new Set();
|
|
317
|
+
for (const s of [...signals].sort((a, b) => a.nonce - b.nonce)) {
|
|
318
|
+
if (s.type !== 'trade')
|
|
319
|
+
continue;
|
|
320
|
+
if (s.side === 'buy') {
|
|
321
|
+
held.add(s.mint);
|
|
322
|
+
closed.delete(s.mint);
|
|
323
|
+
}
|
|
324
|
+
else if ((s.sellFraction ?? 1) >= 0.999) {
|
|
325
|
+
held.delete(s.mint);
|
|
326
|
+
closed.add(s.mint);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return { held: [...held], closed: [...closed] };
|
|
330
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// dexterm autotrade — the replay tape (task-1498 spike, AUTOTRADE-SPEC §3).
|
|
2
|
+
//
|
|
3
|
+
// A tape is recorded market history in time order: one row per (mint, t) taken from the recorder's
|
|
4
|
+
// `token_snapshots` (the app's own SQLite file, read-only). The same MarketTick type feeds the live screener
|
|
5
|
+
// daemon, the paper runner, the monitor and the backtester, so a backtest replays exactly what a live run
|
|
6
|
+
// would have seen. `tapeFromDb` extracts one; `saveTape` / `loadTape` keep a compact committed fixture.
|
|
7
|
+
//
|
|
8
|
+
// Honest limits of the recorded data (written into every report): rows are written on material change
|
|
9
|
+
// (≥ 5 % price/liquidity) or every 30 min, at the app's ~2-minute refresh cadence, and only while a coin is on
|
|
10
|
+
// a screen, plus sweep re-observations at 1 h…72 h. A gap therefore means "moved less than ~5 %" while the coin
|
|
11
|
+
// was listed, and "unknown" after it left. There are no trades or holder lists in the history.
|
|
12
|
+
//
|
|
13
|
+
// task-1504: where the recorder's tick lane ran (`autotrade_ticks`: a sample every 5–10 s for watchlist / open /
|
|
14
|
+
// candidate / held coins), `tapeFromDb` merges those samples in tape order (t, a snapshot row before a tick at the
|
|
15
|
+
// same t). A tick carries price, liquidity, mcap and the 5 m / 1 h windows; the slower fields (24 h windows, holders,
|
|
16
|
+
// top-10, dev, authorities, organic score) are carried forward from the mint's latest earlier snapshot, never from a
|
|
17
|
+
// later one, and the resolved mcap is scaled by the price move. Coins without ticks replay exactly as before.
|
|
18
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import { deriveFlags } from "../../cli/src/screener/rows.js";
|
|
20
|
+
export const TAPE_SCHEMA = 'dexterm-autotrade-tape/1';
|
|
21
|
+
/** Column order of a committed tape row (compact arrays keep fixtures small). */
|
|
22
|
+
export const TAPE_COLUMNS = [
|
|
23
|
+
't', 'kind', 'price', 'mcap', 'mcapResolved', 'mcapFlagged', 'liq', 'vol5m', 'vol1h', 'vol24', 'buys5m', 'sells5m', 'buys24', 'sells24',
|
|
24
|
+
'chg5m', 'chg1h', 'chg6h', 'chg24h', 'holders', 'top10', 'dev', 'mintAuth', 'freezeAuth', 'org',
|
|
25
|
+
];
|
|
26
|
+
const SIG = 6;
|
|
27
|
+
const sig = (x) => (x === null || !Number.isFinite(x) ? null : Number(x.toPrecision(SIG)));
|
|
28
|
+
const bool = (x) => (x === null || x === undefined ? null : Boolean(x));
|
|
29
|
+
const numOrNull = (x) => (typeof x === 'number' && Number.isFinite(x) ? x : null);
|
|
30
|
+
const SNAP_COLS = `t, kind, symbol, launchpad, created_at, pair_created_at, price_usd, mcap, mcap_resolved, mcap_flagged, liquidity_usd,
|
|
31
|
+
vol_m5, vol_h1, vol_h24, buys_m5, sells_m5, buys_h24, sells_h24, chg_m5, chg_h1, chg_h6, chg_h24,
|
|
32
|
+
holder_count, top_holders_pct, dev_pct, mint_auth_enabled, freeze_auth_enabled, organic_score`;
|
|
33
|
+
function hasTicks(db, since, until) {
|
|
34
|
+
const t = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'autotrade_ticks'").get();
|
|
35
|
+
return !!t && !!db.prepare('SELECT 1 FROM autotrade_ticks WHERE t >= ? AND t <= ? LIMIT 1').get(since, until);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Merge a mint's snapshot rows (time order) with its tick rows (time order) into snapshot-shaped rows: a tick takes
|
|
39
|
+
* its own price / liquidity / mcap / 5 m + 1 h windows and carries the rest forward from the latest snapshot at or
|
|
40
|
+
* before it (null before the first one).
|
|
41
|
+
*/
|
|
42
|
+
function mergeTicks(snaps, ticks) {
|
|
43
|
+
const out = [];
|
|
44
|
+
let i = 0;
|
|
45
|
+
let base = null;
|
|
46
|
+
for (const k of ticks) {
|
|
47
|
+
while (i < snaps.length && snaps[i].t <= k.t) {
|
|
48
|
+
base = snaps[i];
|
|
49
|
+
out.push(snaps[i++]);
|
|
50
|
+
}
|
|
51
|
+
const price = numOrNull(k.price_usd);
|
|
52
|
+
const basePrice = base ? numOrNull(base.price_usd) : null;
|
|
53
|
+
const baseResolved = base ? numOrNull(base.mcap_resolved) : null;
|
|
54
|
+
out.push({
|
|
55
|
+
t: k.t, kind: 'live',
|
|
56
|
+
symbol: base?.symbol ?? null, launchpad: base?.launchpad ?? null, created_at: base?.created_at ?? null, pair_created_at: k.pair_created_at ?? base?.pair_created_at ?? null,
|
|
57
|
+
price_usd: price, mcap: numOrNull(k.mcap),
|
|
58
|
+
mcap_resolved: baseResolved !== null && basePrice && price !== null ? baseResolved * (price / basePrice) : null,
|
|
59
|
+
mcap_flagged: base?.mcap_flagged ?? 0, liquidity_usd: numOrNull(k.liquidity_usd),
|
|
60
|
+
vol_m5: k.vol_m5, vol_h1: k.vol_h1, vol_h24: base?.vol_h24 ?? null, buys_m5: k.buys_m5, sells_m5: k.sells_m5, buys_h24: base?.buys_h24 ?? null, sells_h24: base?.sells_h24 ?? null,
|
|
61
|
+
chg_m5: k.chg_m5, chg_h1: k.chg_h1, chg_h6: base?.chg_h6 ?? null, chg_h24: base?.chg_h24 ?? null,
|
|
62
|
+
holder_count: base?.holder_count ?? null, top_holders_pct: base?.top_holders_pct ?? null, dev_pct: base?.dev_pct ?? null,
|
|
63
|
+
mint_auth_enabled: base?.mint_auth_enabled ?? null, freeze_auth_enabled: base?.freeze_auth_enabled ?? null, organic_score: base?.organic_score ?? null,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
while (i < snaps.length)
|
|
67
|
+
out.push(snaps[i++]);
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Extract a tape from the recorder tables (read-only handle from `openHistoryReadOnly`): `token_snapshots` and, where
|
|
72
|
+
* the tick lane ran, `autotrade_ticks` (task-1504), in tape order. A mint qualifies on its live rows of either kind.
|
|
73
|
+
*/
|
|
74
|
+
export function tapeFromDb(db, opts = {}) {
|
|
75
|
+
const since = opts.since ?? 0;
|
|
76
|
+
const until = opts.until ?? Number.MAX_SAFE_INTEGER;
|
|
77
|
+
const minLive = opts.minLiveRows ?? 20;
|
|
78
|
+
const minPeak = opts.minPeakLiqUsd ?? 20_000;
|
|
79
|
+
const span = (opts.spanHours ?? 24) * 3600e3;
|
|
80
|
+
const withTicks = opts.ticks !== false && hasTicks(db, since, until);
|
|
81
|
+
// Without ticks in the window the pick is the exact pre-1504 query, so earlier backtests reproduce bit for bit.
|
|
82
|
+
const picked = opts.mints ?? db.prepare(withTicks
|
|
83
|
+
? `SELECT mint, SUM(n) AS n, MAX(peak) AS peak, MIN(first) AS first FROM (
|
|
84
|
+
SELECT mint, COUNT(*) AS n, MAX(liquidity_usd) AS peak, MIN(t) AS first FROM token_snapshots
|
|
85
|
+
WHERE kind = 'live' AND t >= ? AND t <= ? AND price_usd > 0 GROUP BY mint
|
|
86
|
+
UNION ALL
|
|
87
|
+
SELECT mint, COUNT(*) AS n, MAX(liquidity_usd) AS peak, MIN(t) AS first FROM autotrade_ticks
|
|
88
|
+
WHERE t >= ? AND t <= ? AND price_usd > 0 GROUP BY mint
|
|
89
|
+
) GROUP BY mint HAVING n >= ? AND peak >= ? ORDER BY first, mint LIMIT ?`
|
|
90
|
+
: `SELECT mint, COUNT(*) AS n, MAX(liquidity_usd) AS peak, MIN(t) AS first FROM token_snapshots
|
|
91
|
+
WHERE kind = 'live' AND t >= ? AND t <= ? AND price_usd > 0 GROUP BY mint
|
|
92
|
+
HAVING n >= ? AND peak >= ? ORDER BY first LIMIT ?`).all(...(withTicks ? [since, until, since, until] : [since, until]), minLive, minPeak, opts.maxMints ?? 100_000).map((r) => r.mint);
|
|
93
|
+
const rowsQ = db.prepare(`SELECT ${SNAP_COLS} FROM token_snapshots WHERE mint = ? AND t >= ? AND t <= ? ORDER BY t, snap_id`);
|
|
94
|
+
const ticksQ = withTicks ? db.prepare(`SELECT t, price_usd, liquidity_usd, mcap, vol_m5, vol_h1, buys_m5, sells_m5, chg_m5, chg_h1, pair_created_at
|
|
95
|
+
FROM autotrade_ticks WHERE mint = ? AND t >= ? AND t <= ? ORDER BY t`) : null;
|
|
96
|
+
// The carry-forward base may sit before `since`: the mint's latest snapshot at or before the window start.
|
|
97
|
+
const baseQ = withTicks ? db.prepare(`SELECT ${SNAP_COLS} FROM token_snapshots WHERE mint = ? AND t < ? ORDER BY t DESC, snap_id DESC LIMIT 1`) : null;
|
|
98
|
+
const mints = [];
|
|
99
|
+
let from = Number.MAX_SAFE_INTEGER, to = 0;
|
|
100
|
+
for (const mint of picked) {
|
|
101
|
+
let rs = rowsQ.all(mint, since, until);
|
|
102
|
+
if (ticksQ) {
|
|
103
|
+
const ks = ticksQ.all(mint, since, until);
|
|
104
|
+
if (ks.length) {
|
|
105
|
+
const prior = baseQ.get(mint, since);
|
|
106
|
+
rs = mergeTicks(prior ? [prior, ...rs] : rs, ks);
|
|
107
|
+
if (prior)
|
|
108
|
+
rs = rs.filter((r) => r !== prior);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const first = rs.find((r) => r.kind === 'live');
|
|
112
|
+
if (!first)
|
|
113
|
+
continue;
|
|
114
|
+
const t0 = first.t;
|
|
115
|
+
const kept = rs.filter((r) => r.t >= t0 && r.t <= t0 + span && numOrNull(r.price_usd) !== null);
|
|
116
|
+
if (!kept.length)
|
|
117
|
+
continue;
|
|
118
|
+
// Identity from the first row that has one (a mint seen by ticks before any snapshot has none on its first row).
|
|
119
|
+
const named = rs.find((r) => r.symbol !== null && r.symbol !== undefined) ?? first;
|
|
120
|
+
const created = numOrNull(named.created_at) ?? numOrNull(first.created_at) ?? numOrNull(first.pair_created_at);
|
|
121
|
+
mints.push({
|
|
122
|
+
mint,
|
|
123
|
+
symbol: String(named.symbol ?? '?').slice(0, 16),
|
|
124
|
+
launchpad: String(named.launchpad ?? 'unknown'),
|
|
125
|
+
createdAt: created,
|
|
126
|
+
rows: kept.map((r) => [
|
|
127
|
+
r.t, r.kind, sig(numOrNull(r.price_usd)), sig(numOrNull(r.mcap)), sig(numOrNull(r.mcap_resolved)), r.mcap_flagged ? 1 : 0, sig(numOrNull(r.liquidity_usd)),
|
|
128
|
+
sig(numOrNull(r.vol_m5)), sig(numOrNull(r.vol_h1)), sig(numOrNull(r.vol_h24)), numOrNull(r.buys_m5), numOrNull(r.sells_m5), numOrNull(r.buys_h24), numOrNull(r.sells_h24),
|
|
129
|
+
sig(numOrNull(r.chg_m5)), sig(numOrNull(r.chg_h1)), sig(numOrNull(r.chg_h6)), sig(numOrNull(r.chg_h24)),
|
|
130
|
+
numOrNull(r.holder_count), sig(numOrNull(r.top_holders_pct)), sig(numOrNull(r.dev_pct)),
|
|
131
|
+
r.mint_auth_enabled === null ? null : r.mint_auth_enabled ? 1 : 0, r.freeze_auth_enabled === null ? null : r.freeze_auth_enabled ? 1 : 0, sig(numOrNull(r.organic_score)),
|
|
132
|
+
]),
|
|
133
|
+
});
|
|
134
|
+
from = Math.min(from, kept[0].t);
|
|
135
|
+
to = Math.max(to, kept[kept.length - 1].t);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
schema: TAPE_SCHEMA,
|
|
139
|
+
source: withTicks ? 'recorder token_snapshots + autotrade_ticks (read-only)' : 'recorder token_snapshots (read-only)',
|
|
140
|
+
extractedAt: new Date().toISOString(),
|
|
141
|
+
window: { from: mints.length ? from : 0, to },
|
|
142
|
+
note: withTicks
|
|
143
|
+
? 'Change-driven rows while listed, sweep re-observations, and 5-10 s tick samples for focus coins (slow fields carried forward). No trades.'
|
|
144
|
+
: 'Change-driven rows at the app refresh cadence while listed, plus sweep re-observations. No candles or trades.',
|
|
145
|
+
columns: TAPE_COLUMNS,
|
|
146
|
+
mints,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export function saveTape(path, doc) {
|
|
150
|
+
// One mint row array per line: diffable and still small.
|
|
151
|
+
const head = { ...doc, mints: undefined };
|
|
152
|
+
const lines = doc.mints.map((m) => ` ${JSON.stringify(m)}`).join(',\n');
|
|
153
|
+
const json = JSON.stringify(head, null, 2).replace(/\n}$/, `,\n "mints": [\n${lines}\n ]\n}\n`);
|
|
154
|
+
writeFileSync(path, json);
|
|
155
|
+
}
|
|
156
|
+
export function readTapeDoc(path) {
|
|
157
|
+
const doc = JSON.parse(readFileSync(path, 'utf8'));
|
|
158
|
+
if (doc.schema !== TAPE_SCHEMA)
|
|
159
|
+
throw new Error(`not a ${TAPE_SCHEMA} file`);
|
|
160
|
+
return doc;
|
|
161
|
+
}
|
|
162
|
+
/** Expand a tape document into ticks in deterministic time order (t, then mint). */
|
|
163
|
+
export function tapeFromDoc(doc) {
|
|
164
|
+
const col = new Map(doc.columns.map((c, i) => [c, i]));
|
|
165
|
+
const at = (r, k) => r[col.get(k) ?? -1];
|
|
166
|
+
const ticks = [];
|
|
167
|
+
for (const m of doc.mints) {
|
|
168
|
+
for (const r of m.rows) {
|
|
169
|
+
ticks.push({
|
|
170
|
+
mint: m.mint, t: at(r, 't'), kind: at(r, 'kind') === 'sweep' ? 'sweep' : 'live',
|
|
171
|
+
symbol: m.symbol, launchpad: m.launchpad, createdAt: m.createdAt,
|
|
172
|
+
price: numOrNull(at(r, 'price')), mcap: numOrNull(at(r, 'mcap')), mcapResolved: numOrNull(at(r, 'mcapResolved')), mcapFlagged: Boolean(at(r, 'mcapFlagged')),
|
|
173
|
+
liq: numOrNull(at(r, 'liq')), vol5m: numOrNull(at(r, 'vol5m')), vol1h: numOrNull(at(r, 'vol1h')), vol24: numOrNull(at(r, 'vol24')),
|
|
174
|
+
buys5m: numOrNull(at(r, 'buys5m')), sells5m: numOrNull(at(r, 'sells5m')), buys24: numOrNull(at(r, 'buys24')), sells24: numOrNull(at(r, 'sells24')),
|
|
175
|
+
chg5m: numOrNull(at(r, 'chg5m')), chg1h: numOrNull(at(r, 'chg1h')), chg6h: numOrNull(at(r, 'chg6h')), chg24h: numOrNull(at(r, 'chg24h')),
|
|
176
|
+
holders: numOrNull(at(r, 'holders')), top10: numOrNull(at(r, 'top10')), dev: numOrNull(at(r, 'dev')),
|
|
177
|
+
mintAuth: bool(at(r, 'mintAuth')), freezeAuth: bool(at(r, 'freezeAuth')), org: numOrNull(at(r, 'org')),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
ticks.sort((a, b) => a.t - b.t || (a.mint < b.mint ? -1 : a.mint > b.mint ? 1 : 0));
|
|
182
|
+
return { source: doc.source, ticks, mints: doc.mints.map((m) => m.mint) };
|
|
183
|
+
}
|
|
184
|
+
export const loadTape = (path) => tapeFromDoc(readTapeDoc(path));
|
|
185
|
+
/** The screener's row shape for a tick, so strategies reuse the exact filter grammar of the screen. */
|
|
186
|
+
export function tickRow(k) {
|
|
187
|
+
const base = { mintAuth: k.mintAuth, freezeAuth: k.freezeAuth, top10: k.top10, dev: k.dev, bundled: null, lpLocked: k.launchpad === 'pump.fun' ? 100 : null };
|
|
188
|
+
return {
|
|
189
|
+
mint: k.mint, symbol: k.symbol, name: '', launchpad: k.launchpad,
|
|
190
|
+
price: k.price, mcap: k.mcap, mcapFlagged: k.mcapFlagged, fdv: null, liq: k.liq,
|
|
191
|
+
vol24: k.vol24, vol6h: null, vol1h: k.vol1h, vol5m: k.vol5m,
|
|
192
|
+
ageMin: k.createdAt ? Math.max(0, (k.t - k.createdAt) / 60000) : null,
|
|
193
|
+
holders: k.holders, holders24: null, holdersChgPct: null,
|
|
194
|
+
chg: { m5: k.chg5m, h1: k.chg1h, h6: k.chg6h, h24: k.chg24h }, spark: undefined,
|
|
195
|
+
buys: k.buys24, sells: k.sells24, org: k.org, ...base, snipers: null, flags: deriveFlags(base),
|
|
196
|
+
verified: false, sus: false, bonding: null, pair: null, sources: [], stale: k.kind === 'sweep', fetchedAt: k.t, aiNote: null, image: null,
|
|
197
|
+
};
|
|
198
|
+
}
|