dexterm-cli 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. package/dist/packages/agents/src/index.js +2 -0
  2. package/dist/packages/agents/src/schema.js +22 -1
  3. package/dist/packages/agents/src/spectator.js +54 -0
  4. package/dist/packages/agents/src/story.js +532 -0
  5. package/dist/packages/agents/src/tiers.js +28 -2
  6. package/dist/packages/agents/src/tools.js +31 -2
  7. package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
  8. package/dist/packages/autotrade/assets/profiles/moonbag.json +46 -0
  9. package/dist/packages/autotrade/assets/profiles/runner.json +88 -0
  10. package/dist/packages/autotrade/assets/profiles/scalp.json +88 -0
  11. package/dist/packages/autotrade/assets/profiles/swing.json +88 -0
  12. package/dist/packages/autotrade/assets/strategies/moonbag.json +101 -0
  13. package/dist/packages/autotrade/assets/strategies/runner.json +101 -0
  14. package/dist/packages/autotrade/assets/strategies/sample-dip-ladder.json +188 -0
  15. package/dist/packages/autotrade/assets/strategies/scalp.json +98 -0
  16. package/dist/packages/autotrade/assets/strategies/swing.json +97 -0
  17. package/dist/packages/autotrade/assets/strategies/yolo.json +157 -0
  18. package/dist/packages/autotrade/assets/tapes/yolo-rehearsal.json +1 -0
  19. package/dist/packages/autotrade/src/account.js +74 -0
  20. package/dist/packages/autotrade/src/backtest.js +357 -0
  21. package/dist/packages/autotrade/src/basis-text.js +32 -0
  22. package/dist/packages/autotrade/src/chat/approvals.js +484 -0
  23. package/dist/packages/autotrade/src/chat/audit.js +140 -0
  24. package/dist/packages/autotrade/src/chat/config.js +93 -0
  25. package/dist/packages/autotrade/src/chat/connector.js +24 -0
  26. package/dist/packages/autotrade/src/chat/control.js +103 -0
  27. package/dist/packages/autotrade/src/chat/fake.js +100 -0
  28. package/dist/packages/autotrade/src/chat/format.js +71 -0
  29. package/dist/packages/autotrade/src/chat/hub.js +187 -0
  30. package/dist/packages/autotrade/src/chat/index.js +18 -0
  31. package/dist/packages/autotrade/src/chat/notify.js +292 -0
  32. package/dist/packages/autotrade/src/chat/pairing.js +46 -0
  33. package/dist/packages/autotrade/src/chat/redact.js +48 -0
  34. package/dist/packages/autotrade/src/chat/secrets.js +99 -0
  35. package/dist/packages/autotrade/src/chat/service.js +248 -0
  36. package/dist/packages/autotrade/src/chat/telegram.js +290 -0
  37. package/dist/packages/autotrade/src/desk-executor.js +188 -0
  38. package/dist/packages/autotrade/src/engine.js +387 -0
  39. package/dist/packages/autotrade/src/execution-port.js +97 -0
  40. package/dist/packages/autotrade/src/fleet-config.js +169 -0
  41. package/dist/packages/autotrade/src/fleet-control.js +276 -0
  42. package/dist/packages/autotrade/src/fleet.js +639 -0
  43. package/dist/packages/autotrade/src/index.js +53 -0
  44. package/dist/packages/autotrade/src/ledger.js +108 -0
  45. package/dist/packages/autotrade/src/live-source.js +182 -0
  46. package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
  47. package/dist/packages/autotrade/src/monitor.js +190 -0
  48. package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
  49. package/dist/packages/autotrade/src/paper-accounts.js +72 -0
  50. package/dist/packages/autotrade/src/replay.js +198 -0
  51. package/dist/packages/autotrade/src/runner-config.js +99 -0
  52. package/dist/packages/autotrade/src/runner.js +872 -0
  53. package/dist/packages/autotrade/src/safety.js +243 -0
  54. package/dist/packages/autotrade/src/session.js +269 -0
  55. package/dist/packages/autotrade/src/sessions.js +503 -0
  56. package/dist/packages/autotrade/src/store.js +250 -0
  57. package/dist/packages/autotrade/src/strategy.js +256 -0
  58. package/dist/packages/autotrade/src/trade-ledger.js +241 -0
  59. package/dist/packages/autotrade/src/yolo.js +140 -0
  60. package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
  61. package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +1 -1
  62. package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +15 -8
  63. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
  64. package/dist/packages/cli/src/agent-cmd.js +6 -4
  65. package/dist/packages/cli/src/agent-spectator.js +17 -1
  66. package/dist/packages/cli/src/ai-cmd.js +412 -0
  67. package/dist/packages/cli/src/analyst/backend.js +105 -15
  68. package/dist/packages/cli/src/analyst/codex.js +14 -9
  69. package/dist/packages/cli/src/analyst/larp-gauge.js +60 -0
  70. package/dist/packages/cli/src/analyst/ollama.js +263 -0
  71. package/dist/packages/cli/src/analyst/session.js +17 -3
  72. package/dist/packages/cli/src/app-help-gen.js +51 -15
  73. package/dist/packages/cli/src/autotrade-cmd/audit.js +57 -0
  74. package/dist/packages/cli/src/autotrade-cmd/backtest.js +199 -0
  75. package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
  76. package/dist/packages/cli/src/autotrade-cmd/fleet.js +397 -0
  77. package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
  78. package/dist/packages/cli/src/autotrade-cmd/run.js +413 -0
  79. package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
  80. package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
  81. package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
  82. package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
  83. package/dist/packages/cli/src/autotrade-cmd.js +168 -0
  84. package/dist/packages/cli/src/backtest-cmd.js +13 -2
  85. package/dist/packages/cli/src/display.js +208 -23
  86. package/dist/packages/cli/src/doctor.js +62 -3
  87. package/dist/packages/cli/src/export.js +10 -2
  88. package/dist/packages/cli/src/index.js +1 -1
  89. package/dist/packages/cli/src/ledger-cmd.js +288 -0
  90. package/dist/packages/cli/src/live/chart-fills.js +46 -0
  91. package/dist/packages/cli/src/live/dev-history.js +31 -15
  92. package/dist/packages/cli/src/live/index.js +28 -90
  93. package/dist/packages/cli/src/live/screens/newpairs-live.js +1 -1
  94. package/dist/packages/cli/src/live/screens/token-live.js +48 -15
  95. package/dist/packages/cli/src/live/screens/watchlist-live.js +1 -1
  96. package/dist/packages/cli/src/live/trade.js +30 -453
  97. package/dist/packages/cli/src/live/watchlist.js +1 -1
  98. package/dist/packages/cli/src/main.js +290 -199
  99. package/dist/packages/cli/src/mcp-cmd.js +7 -1
  100. package/dist/packages/cli/src/mcp-follow.js +103 -6
  101. package/dist/packages/cli/src/plan-cmd.js +15 -5
  102. package/dist/packages/cli/src/pnl-app.js +31 -21
  103. package/dist/packages/cli/src/pnl-card.js +158 -21
  104. package/dist/packages/cli/src/popup.js +38 -6
  105. package/dist/packages/cli/src/record-cmd.js +141 -20
  106. package/dist/packages/cli/src/scan/ui.js +7 -1
  107. package/dist/packages/cli/src/screens/agent-hud.js +773 -0
  108. package/dist/packages/cli/src/screens/analyst-session.js +11 -4
  109. package/dist/packages/cli/src/screens/analyst.js +3 -2
  110. package/dist/packages/cli/src/screens/autotrade-keys.js +169 -0
  111. package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
  112. package/dist/packages/cli/src/screens/autotrade-screen.js +725 -0
  113. package/dist/packages/cli/src/screens/autotrade-ui.js +640 -0
  114. package/dist/packages/cli/src/screens/autotrade-worker.js +89 -0
  115. package/dist/packages/cli/src/screens/brain.js +634 -0
  116. package/dist/packages/cli/src/screens/newpairs.js +1 -1
  117. package/dist/packages/cli/src/screens/overlay.js +5 -2
  118. package/dist/packages/cli/src/screens/plans.js +5 -17
  119. package/dist/packages/cli/src/screens/pnl-chip.js +144 -0
  120. package/dist/packages/cli/src/screens/positions.js +6 -28
  121. package/dist/packages/cli/src/screens/screener.js +1 -0
  122. package/dist/packages/cli/src/screens/ticket.js +38 -205
  123. package/dist/packages/cli/src/screens/token.js +18 -9
  124. package/dist/packages/cli/src/screens/trending.js +1 -1
  125. package/dist/packages/cli/src/state.js +88 -18
  126. package/dist/packages/cli/src/telegram-cmd.js +451 -0
  127. package/dist/packages/cli/src/trade.js +3 -128
  128. package/dist/packages/cli/src/version.js +1 -1
  129. package/dist/packages/cli/src/wallet-cmd.js +39 -340
  130. package/dist/packages/cli/src/wallet-command.js +8 -11
  131. package/dist/packages/cli/src/widgets.js +0 -12
  132. package/dist/packages/custom/src/config.js +20 -2
  133. package/dist/packages/custom/src/schema.js +41 -10
  134. package/dist/packages/custom/src/workspace.js +3 -3
  135. package/dist/packages/data/src/basis.js +191 -0
  136. package/dist/packages/data/src/detail.js +53 -2
  137. package/dist/packages/data/src/index.js +2 -14
  138. package/dist/packages/data/src/larp/score.js +220 -0
  139. package/dist/packages/data/src/larp/x-account.js +316 -0
  140. package/dist/packages/data/src/store.js +14 -5
  141. package/dist/packages/data/src/trade/agent/paper-desk.js +300 -23
  142. package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
  143. package/dist/packages/data/src/trade/bridge.js +4 -102
  144. package/dist/packages/data/src/trade/ledger/export.js +101 -0
  145. package/dist/packages/data/src/trade/ledger/file.js +135 -0
  146. package/dist/packages/data/src/trade/ledger/fold.js +191 -0
  147. package/dist/packages/data/src/trade/ledger/index.js +9 -0
  148. package/dist/packages/data/src/trade/ledger/schema.js +83 -0
  149. package/dist/packages/data/src/trade/ledger/sources.js +307 -0
  150. package/dist/packages/data/src/trade/ledger/stats.js +123 -0
  151. package/dist/packages/data/src/trade/ledger/tags.js +116 -0
  152. package/dist/packages/data/src/trade/ledger/view.js +51 -0
  153. package/dist/packages/data/src/trade/plans/engine.js +7 -0
  154. package/dist/packages/data/src/trade/plans/index.js +0 -1
  155. package/dist/packages/data/src/trade/plans/mandate.js +28 -15
  156. package/dist/packages/data/src/trade/plans/service.js +4 -28
  157. package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
  158. package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
  159. package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
  160. package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
  161. package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
  162. package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
  163. package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
  164. package/dist/packages/mcp/assets/kb/app-help/dev-history.md +1 -1
  165. package/dist/packages/mcp/assets/kb/app-help/larp-check.md +5 -1
  166. package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
  167. package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
  168. package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
  169. package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
  170. package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
  171. package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +1 -1
  172. package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
  173. package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +217 -0
  174. package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
  175. package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
  176. package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
  177. package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
  178. package/dist/packages/mcp/assets/kb/app-help/screen-token.md +6 -0
  179. package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
  180. package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
  181. package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +2 -1
  182. package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
  183. package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
  184. package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
  185. package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +7 -3
  186. package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
  187. package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
  188. package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
  189. package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +2 -0
  190. package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
  191. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
  192. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
  193. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +49 -0
  194. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
  195. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
  196. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
  197. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
  198. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +37 -0
  199. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
  200. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
  201. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
  202. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
  203. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
  204. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
  205. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +35 -0
  206. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
  207. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
  208. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
  209. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
  210. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
  211. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
  212. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
  213. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +43 -0
  214. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
  215. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
  216. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
  217. package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
  218. package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
  219. package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
  220. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
  221. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +32 -0
  222. package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
  223. package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
  224. package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
  225. package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +9 -7
  226. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
  227. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
  228. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
  229. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
  230. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
  231. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
  232. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
  233. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
  234. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
  235. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +1 -1
  236. package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
  237. package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +1 -0
  238. package/dist/packages/mcp/assets/kb/docs/docs-larp-the-larp-score.md +60 -0
  239. package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +0 -2
  240. package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +4 -1
  241. package/dist/packages/mcp/assets/kb/docs/docs-larp.md +1 -1
  242. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
  243. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
  244. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
  245. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +33 -0
  246. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
  247. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
  248. package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
  249. package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
  250. package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
  251. package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
  252. package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
  253. package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
  254. package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
  255. package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
  256. package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
  257. package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
  258. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
  259. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
  260. package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
  261. package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
  262. package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
  263. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
  264. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
  265. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +4 -13
  266. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
  267. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +40 -0
  268. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
  269. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
  270. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
  271. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
  272. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
  273. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
  274. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
  275. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
  276. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +1 -1
  277. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +3 -1
  278. package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +2 -0
  279. package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
  280. package/dist/packages/mcp/assets/kb/docs/docs-screens-dev-history.md +10 -7
  281. package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
  282. package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
  283. package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +1 -1
  284. package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +2 -0
  285. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-agent-hud-spectator-v2.md +58 -0
  286. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +46 -0
  287. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +1 -1
  288. package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +1 -1
  289. package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
  290. package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
  291. package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
  292. package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
  293. package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
  294. package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
  295. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
  296. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
  297. package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
  298. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
  299. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
  300. package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
  301. package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
  302. package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
  303. package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
  304. package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
  305. package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
  306. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
  307. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
  308. package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
  309. package/dist/packages/mcp/assets/kb/entries/autotrade.md +38 -0
  310. package/dist/packages/mcp/src/app-help.js +1 -1
  311. package/dist/packages/mcp/src/autotrade-tools.js +944 -0
  312. package/dist/packages/mcp/src/backtest-requests.js +71 -0
  313. package/dist/packages/mcp/src/engine/config.js +159 -0
  314. package/dist/packages/mcp/src/engine/index.js +10 -0
  315. package/dist/packages/mcp/src/engine/loop.js +101 -0
  316. package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
  317. package/dist/packages/mcp/src/engine/ollama.js +258 -0
  318. package/dist/packages/mcp/src/engine/probe.js +110 -0
  319. package/dist/packages/mcp/src/engine/reason.js +35 -0
  320. package/dist/packages/mcp/src/engine/restrictions.js +111 -0
  321. package/dist/packages/mcp/src/index.js +7 -1
  322. package/dist/packages/mcp/src/ledger-tools.js +346 -0
  323. package/dist/packages/mcp/src/paper-agent.js +5 -2
  324. package/dist/packages/mcp/src/server.js +285 -36
  325. package/dist/packages/mcp/src/spectator-sink.js +85 -0
  326. package/dist/packages/packs/src/backtest.js +3 -1
  327. package/dist/packages/packs/src/index.js +1 -0
  328. package/dist/packages/packs/src/larp-score-backtest.js +119 -0
  329. package/dist/packages/popup/src/pages/chart-lib.js +169 -35
  330. package/dist/packages/popup/src/pages/chart.css +18 -0
  331. package/dist/packages/popup/src/pages/chart.html +6 -1
  332. package/dist/packages/popup/src/pages/chart.js +136 -10
  333. package/dist/packages/popup/src/pages/spectator.css +45 -0
  334. package/dist/packages/popup/src/pages/spectator.html +1 -0
  335. package/dist/packages/popup/src/pages/spectator.js +69 -0
  336. package/dist/packages/popup/src/protocol.js +4 -0
  337. package/dist/packages/popup/src/server.js +9 -1
  338. package/dist/packages/recorder/src/dev-history.js +7 -3
  339. package/dist/packages/recorder/src/index.js +2 -0
  340. package/dist/packages/recorder/src/recorder.js +63 -6
  341. package/dist/packages/recorder/src/retention.js +100 -0
  342. package/dist/packages/recorder/src/schema.js +34 -1
  343. package/dist/packages/recorder/src/ticks.js +237 -0
  344. package/dist/packages/tui/src/canvas.js +7 -0
  345. package/dist/packages/tui/src/fmt.js +53 -0
  346. package/dist/packages/tui/src/index.js +1 -0
  347. package/dist/packages/tui/src/keymap.js +25 -1
  348. package/dist/packages/tui/src/markers.js +219 -0
  349. package/dist/packages/tui/src/widgets.js +158 -6
  350. package/package.json +2 -1
  351. package/dist/packages/data/src/trade/caps.js +0 -323
  352. package/dist/packages/data/src/trade/executor.js +0 -933
  353. package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
  354. package/dist/packages/data/src/trade/rpc-exec.js +0 -229
  355. package/dist/packages/data/src/trade/signer/client.js +0 -197
  356. package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
  357. package/dist/packages/data/src/trade/signer/host.js +0 -97
  358. package/dist/packages/data/src/trade/signer/main.js +0 -121
  359. package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
  360. package/dist/packages/data/src/trade/signer/protocol.js +0 -92
  361. package/dist/packages/data/src/trade/signer/service.js +0 -400
  362. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
  363. package/dist/packages/data/src/trade/sol-usd.js +0 -86
  364. package/dist/packages/data/src/trade/swap-build.js +0 -108
  365. package/dist/packages/data/src/trade/token-delta.js +0 -201
  366. package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
  367. package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
  368. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
  369. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
  370. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
  371. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
  372. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
  373. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
  374. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
  375. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
  376. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
  377. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
  378. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
  379. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
@@ -0,0 +1,639 @@
1
+ // dexterm autotrade — parallel strategies: N strategies on ONE data feed (task-1529). PAPER today.
2
+ //
3
+ // ┌──────────── one source (the live keyless data layer, or one tape) ────────────┐
4
+ // │ focus = the union of every strategy's candidate + held coins (one fast lane) │
5
+ // └──────────────────────────────┬─────────────────────────────────────────────────┘
6
+ // TickHub.publish(k) (a tape: a barrier, so a replay is deterministic)
7
+ // ┌────────────────────────────────────┼────────────────────────────────────┐
8
+ // member A: runAutotrade member B: runAutotrade member C …
9
+ // own book (bankroll), breaker, own book, breaker, ledger,
10
+ // ledger + run id, entry rules, entry rules, sessions, spend
11
+ // sessions, spend budget budget
12
+ // │ MemberGate ─────────────── FleetGuard (global: model sessions open at once, $/day, sessions/day,
13
+ // │ turns/day, the shared Claude allowance, ONE load check)
14
+ // └── safety: the fleet's RunnerSafety (panic file, global stop/flatten/pause, dead-man) ⊕ the
15
+ // strategy's own MemberSafety (fleet-control.ts)
16
+ //
17
+ // Each member is an ordinary runAutotrade run (runner.ts, unchanged): its own SimBook, so a breaker (daily loss,
18
+ // drawdown) or a stop of one strategy never stops another; only the global switches reach all of them. The shared
19
+ // parts never multiply work: one source fetches for everyone (each strategy screens the same ticks with its own entry
20
+ // rules), one dossier per coin per 30 s whoever asks (sharedDossier), one load sample per 5 s.
21
+ //
22
+ // Paper accounts (task-1538, paper-accounts.ts): members that name an account share that account's cash instead of
23
+ // owning a bankroll; each keeps its own book of positions, breaker and ledger. One strategy can run on several accounts
24
+ // (one member per account). The accounts live for the run: a member started again joins the same account.
25
+ //
26
+ // Execution: every member binds its own executor (bindExecutors). Paper members each get their own simulated book.
27
+ // Live members (refused in this build: fleet-config.ts) would share ONE port per trading wallet, so their orders meet
28
+ // the same signer and its caps together: two live strategies can never exceed the wallet's caps between them.
29
+ //
30
+ // Nothing here holds a key, touches a wallet or the signer.
31
+ import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, rmSync } from 'node:fs';
32
+ import { loadavg } from 'node:os';
33
+ import { Ledger, runFiles, runsDir } from "./ledger.js";
34
+ import { SessionGate } from "./sessions.js";
35
+ import { effectiveCaps } from "./runner-config.js";
36
+ import { runAutotrade, simExecutor } from "./runner.js";
37
+ import { PaperAccount } from "./paper-accounts.js";
38
+ import { FLEET_STATUS_SCHEMA, MISSED_RUNNER_X, MemberSafety, isRugExit, mergeVerdicts, readMemberControl, memberControlPath, } from "./fleet-control.js";
39
+ /**
40
+ * Fan-out of one tick stream. `barrier` (tapes): a tick goes out only once every subscriber has taken the previous
41
+ * one and asked for more, so N strategies over one tape run in a fixed order. Live: no barrier (a slow strategy never
42
+ * holds the others back); a subscriber more than `maxQueue` ticks behind loses its oldest ones (counted).
43
+ */
44
+ export class TickHub {
45
+ subs = new Map();
46
+ ended = false;
47
+ notify = null;
48
+ barrier;
49
+ maxQueue;
50
+ constructor(o) { this.barrier = o.barrier; this.maxQueue = o.maxQueue ?? 5000; }
51
+ changed() { const n = this.notify; this.notify = null; n?.(); }
52
+ /** `id` must be unique per subscription (a run id): a re-started strategy gets a new one. */
53
+ subscribe(id) {
54
+ if (this.subs.has(id))
55
+ throw new Error(`tick hub: ${id} is already subscribed`);
56
+ const sub = { id, queue: [], waiter: null, closed: false, dropped: 0 };
57
+ this.subs.set(id, sub);
58
+ const close = () => { if (!sub.closed) {
59
+ sub.closed = true;
60
+ this.subs.delete(id);
61
+ this.changed();
62
+ } };
63
+ const it = {
64
+ next: () => {
65
+ if (sub.queue.length)
66
+ return Promise.resolve({ value: sub.queue.shift(), done: false });
67
+ if (this.ended || sub.closed) {
68
+ close();
69
+ return Promise.resolve({ value: undefined, done: true });
70
+ }
71
+ return new Promise((resolve) => { sub.waiter = resolve; this.changed(); });
72
+ },
73
+ return: () => { close(); return Promise.resolve({ value: undefined, done: true }); },
74
+ };
75
+ return { [Symbol.asyncIterator]: () => it };
76
+ }
77
+ /** True when every subscriber has drained its queue and waits for more (or has gone). */
78
+ caughtUp() {
79
+ for (const s of this.subs.values())
80
+ if (!s.closed && (s.queue.length || !s.waiter))
81
+ return false;
82
+ return true;
83
+ }
84
+ async publish(k) {
85
+ if (this.barrier)
86
+ while (!this.caughtUp())
87
+ await new Promise((r) => { this.notify = r; });
88
+ for (const s of this.subs.values()) {
89
+ // Each strategy gets its own copy: nothing one run does to a tick can leak into another's.
90
+ const mine = { ...k };
91
+ if (s.waiter) {
92
+ const w = s.waiter;
93
+ s.waiter = null;
94
+ w({ value: mine, done: false });
95
+ continue;
96
+ }
97
+ s.queue.push(mine);
98
+ if (s.queue.length > this.maxQueue) {
99
+ s.queue.shift();
100
+ s.dropped++;
101
+ }
102
+ }
103
+ }
104
+ /** Wait until every subscriber took everything (the end of a tape). */
105
+ async drain() { while (!this.caughtUp())
106
+ await new Promise((r) => { this.notify = r; }); }
107
+ end() {
108
+ this.ended = true;
109
+ for (const s of [...this.subs.values()])
110
+ if (s.waiter && !s.queue.length) {
111
+ const w = s.waiter;
112
+ s.waiter = null;
113
+ s.closed = true;
114
+ this.subs.delete(s.id);
115
+ w({ value: undefined, done: true });
116
+ }
117
+ }
118
+ /** Drop a subscriber that will never ask again (a run that failed before its first tick), so a barrier never waits on it. */
119
+ unsubscribe(id) {
120
+ const s = this.subs.get(id);
121
+ if (!s)
122
+ return;
123
+ s.closed = true;
124
+ this.subs.delete(id);
125
+ if (s.waiter) {
126
+ const w = s.waiter;
127
+ s.waiter = null;
128
+ w({ value: undefined, done: true });
129
+ }
130
+ this.changed();
131
+ }
132
+ get size() { return this.subs.size; }
133
+ droppedBy(id) { return this.subs.get(id)?.dropped ?? 0; }
134
+ }
135
+ export const DEFAULT_FLEET_LOAD = { pauseAboveLoad1m: 25, resumeBelowLoad1m: 20, sustainSec: 60 };
136
+ /** ONE machine-load check for every strategy: sampled at most every 5 s, with hysteresis. */
137
+ export class LoadGuard {
138
+ cfg;
139
+ read;
140
+ clock;
141
+ lastAt = -Infinity;
142
+ aboveSince = null;
143
+ load1m = null;
144
+ paused = false;
145
+ constructor(cfg, o = {}) { this.cfg = cfg; this.read = o.loadavg ?? (() => loadavg()[0]); this.clock = o.clock ?? Date.now; }
146
+ sample() {
147
+ const now = this.clock();
148
+ if (now - this.lastAt < 5000)
149
+ return;
150
+ this.lastAt = now;
151
+ const l = this.read();
152
+ this.load1m = Math.round(l * 100) / 100;
153
+ if (l > this.cfg.pauseAboveLoad1m) {
154
+ this.aboveSince ??= now;
155
+ if (now - this.aboveSince >= this.cfg.sustainSec * 1000)
156
+ this.paused = true;
157
+ }
158
+ else {
159
+ this.aboveSince = null;
160
+ if (l < this.cfg.resumeBelowLoad1m)
161
+ this.paused = false;
162
+ }
163
+ }
164
+ get config() { return this.cfg; }
165
+ }
166
+ /**
167
+ * The caps across every strategy: model sessions open at once (candidate and position sessions of all of them),
168
+ * model $ / turns / sessions per day, the shared Claude allowance (one account: a reading from any session applies to
169
+ * all), a misconfigured account (blocks every strategy), and the load check. The day counters run on the WALL clock.
170
+ */
171
+ export class FleetGuard {
172
+ caps;
173
+ gate;
174
+ load;
175
+ members = new Set();
176
+ live = 0;
177
+ liveBy = new Map();
178
+ constructor(caps, runner, o = {}) {
179
+ // The runner config (machine-local, one per process) caps the whole fleet too: the stricter number wins.
180
+ this.caps = {
181
+ maxConcurrentSessions: caps.maxConcurrentSessions,
182
+ modelUsdPerDay: Math.min(caps.modelUsdPerDay, runner.caps.modelUsdPerDay),
183
+ candidateSessionsPerDay: Math.min(caps.candidateSessionsPerDay, runner.caps.candidateSessionsPerDay),
184
+ positionSessionsPerDay: Math.min(caps.positionSessionsPerDay, runner.caps.positionSessionsPerDay),
185
+ turnsPerDay: Math.min(caps.turnsPerDay, runner.caps.turnsPerDay),
186
+ };
187
+ const eff = { maxSessions: Infinity, budgetUsdPerSession: Infinity, maxTurnsPerSession: Infinity, modelUsdPerDay: this.caps.modelUsdPerDay, candidateSessionsPerDay: this.caps.candidateSessionsPerDay, positionSessionsPerDay: this.caps.positionSessionsPerDay, turnsPerDay: this.caps.turnsPerDay };
188
+ this.gate = new SessionGate(eff, runner.allowanceGuard, { clock: o.clock });
189
+ const lc = o.load ?? (runner.load ? { pauseAboveLoad1m: runner.load.pauseAboveLoad1m, resumeBelowLoad1m: runner.load.resumeBelowLoad1m, sustainSec: runner.load.sustainSec } : DEFAULT_FLEET_LOAD);
190
+ this.load = new LoadGuard(lc, { loadavg: o.loadavg, clock: o.clock });
191
+ }
192
+ register(g) { this.members.add(g); }
193
+ canOpen(kind) {
194
+ this.load.sample();
195
+ if (this.load.paused)
196
+ return { ok: false, reason: 'fleet-load-high' };
197
+ if (this.live >= this.caps.maxConcurrentSessions)
198
+ return { ok: false, reason: 'fleet-max-sessions' };
199
+ const g = this.gate.canOpen(kind, 0);
200
+ return g.ok ? g : { ok: false, reason: `fleet-${g.reason}` };
201
+ }
202
+ /** A session factory that counts its sessions in the fleet-wide open total (closed by the runner, always). */
203
+ track(member, f) {
204
+ return (id, s, ctx) => {
205
+ const x = f(id, s, ctx);
206
+ this.live++;
207
+ this.liveBy.set(member, (this.liveBy.get(member) ?? 0) + 1);
208
+ let closed = false;
209
+ const close = x.close.bind(x);
210
+ x.close = async (reason) => {
211
+ if (!closed) {
212
+ closed = true;
213
+ this.live--;
214
+ this.liveBy.set(member, Math.max(0, (this.liveBy.get(member) ?? 1) - 1));
215
+ }
216
+ return close(reason);
217
+ };
218
+ return x;
219
+ };
220
+ }
221
+ rateLimit(r, from) {
222
+ this.gate.rateLimit(r);
223
+ for (const m of this.members)
224
+ if (m !== from)
225
+ m.shareRateLimit(r);
226
+ }
227
+ block(reason, from) {
228
+ this.gate.block(reason);
229
+ for (const m of this.members)
230
+ if (m !== from)
231
+ m.shareBlock(reason);
232
+ }
233
+ paused() { return this.gate.paused() || this.load.paused; }
234
+ }
235
+ /** The caps one strategy runs under: the stricter of the strategy, its autotrade.json caps and runner.json. */
236
+ export function memberCaps(s, runner, m) {
237
+ const e = effectiveCaps(s, runner);
238
+ const min = (a, b) => (b === undefined ? a : Math.min(a, b));
239
+ return {
240
+ ...e,
241
+ maxSessions: min(e.maxSessions, m.maxSessions),
242
+ modelUsdPerDay: min(e.modelUsdPerDay, m.modelUsdPerDay),
243
+ candidateSessionsPerDay: min(e.candidateSessionsPerDay, m.candidateSessionsPerDay),
244
+ positionSessionsPerDay: min(e.positionSessionsPerDay, m.positionSessionsPerDay),
245
+ turnsPerDay: min(e.turnsPerDay, m.turnsPerDay),
246
+ };
247
+ }
248
+ /**
249
+ * One strategy's session gate: its own caps first (SessionGate), then the fleet's. Every turn and every dollar counts
250
+ * against both, so the global $/day holds across all strategies and each strategy's own budget holds on its own.
251
+ */
252
+ export class MemberGate extends SessionGate {
253
+ member;
254
+ fleet;
255
+ constructor(member, caps, runner, fleet, o = {}) {
256
+ super(caps, runner.allowanceGuard, o);
257
+ this.member = member;
258
+ this.fleet = fleet;
259
+ fleet.register(this);
260
+ }
261
+ canOpen(kind, live) {
262
+ const own = super.canOpen(kind, live);
263
+ return own.ok ? this.fleet.canOpen(kind) : own;
264
+ }
265
+ opened(kind) { super.opened(kind); this.fleet.gate.opened(kind); }
266
+ turn(costDeltaUsd) { super.turn(costDeltaUsd); this.fleet.gate.turn(costDeltaUsd); }
267
+ spend(usd) { super.spend(usd); this.fleet.gate.spend(usd); }
268
+ overDay() { const o = super.overDay(); if (o)
269
+ return o; const g = this.fleet.gate.overDay(); return g ? `fleet-${g}` : null; }
270
+ rateLimit(r) { super.rateLimit(r); this.fleet.rateLimit(r, this); }
271
+ block(reason) { super.block(reason); this.fleet.block(reason, this); }
272
+ paused() { return super.paused() || this.fleet.paused(); }
273
+ /** A reading another strategy's session made (the same Claude account). */
274
+ shareRateLimit(r) { super.rateLimit(r); }
275
+ shareBlock(reason) { super.block(reason); }
276
+ }
277
+ // ───────────────────────────── shared dossier, executors ─────────────────────────────
278
+ /**
279
+ * One dossier record per coin per `ttlMs`, whoever asks: two strategies that pick the same coin at the same time cost
280
+ * one fetch. The record is shared; each strategy renders its own text (its own bounds and bankroll).
281
+ */
282
+ export function sharedDossier(fetch, ttlMs = 30_000, clock = Date.now) {
283
+ const cache = new Map();
284
+ let n = 0;
285
+ return {
286
+ get(mint) {
287
+ const now = clock();
288
+ const hit = cache.get(mint);
289
+ if (hit && now - hit.at < ttlMs)
290
+ return hit.rec;
291
+ n++;
292
+ const rec = fetch(mint);
293
+ cache.set(mint, { at: now, rec });
294
+ rec.catch(() => cache.delete(mint));
295
+ if (cache.size > 500)
296
+ for (const [k, v] of cache)
297
+ if (now - v.at >= ttlMs)
298
+ cache.delete(k);
299
+ return rec;
300
+ },
301
+ fetches: () => n,
302
+ };
303
+ }
304
+ /** Executors by member id. Live members share one port per wallet, so the wallet's signer caps hold across them. */
305
+ export function bindExecutors(members, b) {
306
+ const out = new Map();
307
+ const ports = new Map();
308
+ for (const m of members) {
309
+ if (m.mode === 'paper') {
310
+ out.set(m.id, b.paper(m));
311
+ continue;
312
+ }
313
+ if (!b.live)
314
+ return { ok: false, why: `${m.id}: live execution is not available for parallel strategies in this build` };
315
+ const w = m.wallet ?? 'trading-wallet';
316
+ if (!ports.has(w))
317
+ ports.set(w, b.live(w));
318
+ out.set(m.id, ports.get(w));
319
+ }
320
+ return { ok: true, executors: out, livePorts: ports.size };
321
+ }
322
+ // ───────────────────────────── per-strategy stats (for the comparison table) ─────────────────────────────
323
+ /**
324
+ * Folds one strategy's ledger events into its stats, and watches the shared feed for coins it saw as candidates but
325
+ * did not enter: one that later trades at >= MISSED_RUNNER_X its candidate price is a missed runner.
326
+ */
327
+ export class MemberStatsTracker {
328
+ stats = { candidates: 0, entries: 0, closed: 0, wins: 0, losses: 0, realizedUsd: 0, rugExits: 0, rugCostUsd: 0, enteredUsd: 0, missedRunners: 0, missedSample: [], spendUsd: 0 };
329
+ endedSpend = 0;
330
+ watch = new Map();
331
+ positions = new Map();
332
+ onEvent(e) {
333
+ switch (e.kind) {
334
+ case 'candidate': {
335
+ this.stats.candidates++;
336
+ const price = e.tick?.price ?? null;
337
+ const mint = String(e.mint);
338
+ const w = this.watch.get(mint);
339
+ if (price && price > 0 && !w)
340
+ this.watch.set(mint, { symbol: String(e.symbol ?? ''), price, entered: false, missed: false });
341
+ break;
342
+ }
343
+ case 'fill': {
344
+ const usd = Number(e.usd) || 0;
345
+ if (e.side === 'buy') {
346
+ this.stats.enteredUsd += usd;
347
+ if (e.profile !== undefined)
348
+ this.stats.entries++;
349
+ const w = this.watch.get(String(e.mint));
350
+ if (w)
351
+ w.entered = true;
352
+ this.positions.set(String(e.positionId), (this.positions.get(String(e.positionId)) ?? 0) + usd);
353
+ }
354
+ break;
355
+ }
356
+ case 'trade_closed': {
357
+ const t = e.trade;
358
+ if (!t)
359
+ break;
360
+ this.stats.closed++;
361
+ if (t.pnlUsd > 0)
362
+ this.stats.wins++;
363
+ else
364
+ this.stats.losses++;
365
+ this.stats.realizedUsd = Math.round((this.stats.realizedUsd + t.pnlUsd) * 1e6) / 1e6;
366
+ if (isRugExit(t.exitReason, t.stuck)) {
367
+ this.stats.rugExits++;
368
+ this.stats.rugCostUsd += t.costUsd;
369
+ }
370
+ break;
371
+ }
372
+ case 'session_end':
373
+ this.endedSpend += Number(e.costUsd) || 0;
374
+ break;
375
+ }
376
+ }
377
+ /** A tick from the shared feed (every strategy sees the same ones). */
378
+ onTick(k) {
379
+ const w = this.watch.get(k.mint);
380
+ if (!w || w.entered || w.missed || !k.price)
381
+ return;
382
+ const x = k.price / w.price;
383
+ if (x >= MISSED_RUNNER_X) {
384
+ w.missed = true;
385
+ this.stats.missedRunners++;
386
+ if (this.stats.missedSample.length < 5)
387
+ this.stats.missedSample.push({ symbol: w.symbol, x: Math.round(x * 10) / 10 });
388
+ }
389
+ }
390
+ /** Spend = ended sessions + what the live ones show now; stuck bags still held count as rug exposure. */
391
+ view(st) {
392
+ const liveSpend = st ? st.sessions.reduce((a, s) => a + s.costUsd, 0) : 0;
393
+ const stuck = st ? st.positions.filter((p) => p.next?.startsWith('stuck')) : [];
394
+ return {
395
+ ...this.stats,
396
+ rugExits: this.stats.rugExits + stuck.length,
397
+ rugCostUsd: Math.round((this.stats.rugCostUsd + stuck.reduce((a, p) => a + p.costUsd, 0)) * 100) / 100,
398
+ enteredUsd: Math.round(this.stats.enteredUsd * 100) / 100,
399
+ spendUsd: Math.round((this.endedSpend + liveSpend) * 1e4) / 1e4,
400
+ missedSample: [...this.stats.missedSample],
401
+ };
402
+ }
403
+ }
404
+ export function strategyTaggedHooks(sink, also = {}) {
405
+ return {
406
+ ...also,
407
+ onMemberEvent: (member, runId, e) => { also.onMemberEvent?.(member, runId, e); sink.onEvent({ ...e, strategyId: member, runId }); },
408
+ };
409
+ }
410
+ /** A member ledger appended to runs/<runId>.jsonl as it happens (0600), with a tap after each write. */
411
+ export function memberFileLedger(home, runId, tap) {
412
+ const files = runFiles(home, runId);
413
+ mkdirSync(runsDir(home), { recursive: true, mode: 0o700 });
414
+ if (existsSync(files.ledger))
415
+ throw new Error(`run ledger ${files.ledger} already exists`);
416
+ closeSync(openSync(files.ledger, 'a', 0o600));
417
+ const ledger = new Ledger((e, line) => { appendFileSync(files.ledger, `${line}\n`); try {
418
+ tap(e);
419
+ }
420
+ catch { /* a hook never stops the ledger */ } });
421
+ return { ledger, files };
422
+ }
423
+ export async function runFleet(o) {
424
+ if (!o.tape && !o.source)
425
+ throw new Error('runFleet needs a tape or a live source');
426
+ for (const m of o.members)
427
+ if (m.config.mode !== 'paper' && !o.executors?.live)
428
+ throw new Error(`${m.config.id}: live execution is not available for parallel strategies in this build`);
429
+ const accounts = new Map();
430
+ for (const a of o.accounts ?? []) {
431
+ if (accounts.has(a.id))
432
+ throw new Error(`paper account ${a.id} is listed twice`);
433
+ accounts.set(a.id, new PaperAccount(a.id, a.startUsd, { ...(a.sol !== undefined ? { sol: a.sol, solUsd: a.solUsd } : {}) }));
434
+ }
435
+ for (const m of o.members) {
436
+ if (!m.config.account)
437
+ continue;
438
+ if (!accounts.has(m.config.account))
439
+ throw new Error(`${m.config.id}: paper account ${m.config.account} is not defined`);
440
+ if (m.config.mode !== 'paper')
441
+ throw new Error(`${m.config.id}: a paper account never backs a live strategy`);
442
+ }
443
+ const clock = o.clock ?? Date.now;
444
+ const say = o.onLine ?? (() => { });
445
+ const pacing = o.pacing ?? { mode: 'lockstep' };
446
+ const hub = new TickHub({ barrier: !!o.tape || pacing.mode !== 'live' });
447
+ const fl = o.fleetLedger;
448
+ const startedAt = clock();
449
+ let now = o.tape ? (o.tape.ticks[0]?.t ?? 0) : (o.source.startT ?? startedAt);
450
+ const lives = new Map();
451
+ for (const spec of o.members)
452
+ lives.set(spec.config.id, { spec, state: 'idle', runId: null, gate: null, stats: new MemberStatsTracker(), last: null, ledgerPath: null, done: null, result: null, focus: [], startedAt: null, endedAt: null });
453
+ const execs = bindExecutors(o.members.map((m) => ({ id: m.config.id, mode: m.config.mode, strategy: m.strategy })), o.executors ?? { paper: (m) => simExecutor(m.strategy.execution) });
454
+ if (!execs.ok)
455
+ throw new Error(execs.why);
456
+ fl.add(now, 'fleet_start', {
457
+ fleetId: o.fleetId, mode: 'paper', source: o.tape?.source ?? o.source.name, pacing: pacing.mode,
458
+ members: o.members.map((m) => ({ id: m.config.id, enabled: m.config.enabled, basedOn: m.basedOn, strategyHash: m.strategy.hash, bankroll: m.bankroll, mode: m.config.mode, caps: m.config.caps, ...(m.config.account ? { account: m.config.account } : {}) })),
459
+ global: o.guard.caps, livePorts: execs.livePorts,
460
+ ...(accounts.size ? { accounts: [...accounts.values()].map((a) => ({ id: a.id, startUsd: a.startUsd, ...(a.sol !== undefined ? { sol: a.sol, solUsd: a.solUsd } : {}) })) } : {}),
461
+ });
462
+ const publishFocus = () => {
463
+ if (!o.source?.setFocus)
464
+ return;
465
+ const seen = new Map();
466
+ for (const l of lives.values())
467
+ for (const f of l.focus) {
468
+ const cur = seen.get(f.mint);
469
+ if (!cur || f.role === 'held')
470
+ seen.set(f.mint, f);
471
+ }
472
+ o.source.setFocus([...seen.values()]);
473
+ };
474
+ const status = (state, haltReason) => {
475
+ const g = o.guard;
476
+ const day = g.gate.todayNow();
477
+ return {
478
+ schema: FLEET_STATUS_SCHEMA, fleetId: o.fleetId, pid: process.pid, mode: 'paper', state, haltReason, startedAt, heartbeatAt: clock(),
479
+ source: o.tape ? `replay ${o.tape.source}` : 'live',
480
+ global: {
481
+ caps: g.caps, today: { modelUsd: Math.round(day.modelUsd * 1e4) / 1e4, turns: day.turns, candidateSessions: day.candidateSessions, positionSessions: day.positionSessions },
482
+ liveSessions: g.live, load: { load1m: g.load.load1m, paused: g.load.paused, pauseAbove: g.load.config.pauseAboveLoad1m, resumeBelow: g.load.config.resumeBelowLoad1m },
483
+ allowance: g.gate.allowance ? { fiveHour: g.gate.allowance.fiveHour, sevenDay: g.gate.allowance.sevenDay } : null, blocked: g.gate.blocked,
484
+ },
485
+ members: [...lives.values()].map((l) => ({
486
+ id: l.spec.config.id, runId: l.runId, state: l.state, ...(l.error ? { error: l.error } : {}), haltReason: l.last?.haltReason ?? null, mode: 'paper',
487
+ basedOn: l.spec.basedOn, strategyHash: l.spec.strategy.hash, bankroll: l.spec.bankroll,
488
+ book: l.last ? l.last.book : null, spend: l.last ? { todayUsd: l.last.spend.todayUsd, dayCapUsd: l.last.spend.dayCapUsd } : null,
489
+ open: (l.last?.positions ?? []).map((p) => ({ mint: p.mint, symbol: p.symbol, profile: p.profile, costUsd: p.costUsd, pnlUsd: p.pnlUsd, pnlPct: p.pnlPct, ...(p.freeBag ? { freeBag: true } : {}), ...(p.next?.startsWith('stuck') ? { stuck: true } : {}) })),
490
+ candidates: l.last?.candidates.length ?? 0, sessions: l.last?.sessions.length ?? 0,
491
+ stats: l.stats.view(l.last), ledgerPath: l.ledgerPath, startedAt: l.startedAt, endedAt: l.endedAt,
492
+ ...(l.spec.config.account ? { account: l.spec.config.account, pnlUsd: l.last?.account?.pnlUsd ?? null } : {}),
493
+ })),
494
+ ...(accounts.size ? { accounts: [...accounts.values()].map((a) => a.view()) } : {}),
495
+ ledgerPath: o.fleetLedgerPath ?? '',
496
+ };
497
+ };
498
+ let globalHalt = null;
499
+ const emitStatus = (state = globalHalt ? 'halted' : 'running') => {
500
+ const st = status(state, globalHalt);
501
+ o.onStatus?.(st);
502
+ o.hooks?.onFleetState?.(st);
503
+ };
504
+ const startMember = (l, why) => {
505
+ const spec = l.spec;
506
+ const id = spec.config.id;
507
+ const runId = o.newRunId(id);
508
+ l.runId = runId;
509
+ l.state = 'running';
510
+ l.error = undefined;
511
+ l.last = null;
512
+ l.result = null;
513
+ l.stats = new MemberStatsTracker();
514
+ l.startedAt = clock();
515
+ l.endedAt = null;
516
+ l.focus = [];
517
+ let memberNow = now;
518
+ const tap = (e) => { l.stats.onEvent(e); o.hooks?.onMemberEvent?.(id, runId, e); };
519
+ let ledger;
520
+ try {
521
+ const lf = o.ledgerFor ? o.ledgerFor(id, runId, tap) : memberFileLedger(o.home, runId, tap);
522
+ ledger = lf.ledger;
523
+ l.ledgerPath = lf.files?.ledger ?? null;
524
+ }
525
+ catch (e) {
526
+ l.state = 'error';
527
+ l.error = e.message.slice(0, 200);
528
+ fl.add(now, 'member_error', { member: id, error: l.error });
529
+ return;
530
+ }
531
+ const account = spec.config.account ? accounts.get(spec.config.account) : undefined;
532
+ ledger.add(now, 'fleet_member', { fleetId: o.fleetId, member: id, basedOn: spec.basedOn, strategyHash: spec.strategy.hash, bankroll: spec.bankroll, mode: spec.config.mode, caps: spec.config.caps, why, ...(account ? { account: account.id, accountCashUsd: Math.round(account.cashUsd * 100) / 100 } : {}) });
533
+ const gate = new MemberGate(id, memberCaps(spec.strategy, o.runner, spec.config.caps), o.runner, o.guard, { clock });
534
+ l.gate = gate;
535
+ const log = (kind, data) => { ledger.add(memberNow, kind, data); };
536
+ const sessions = o.sessions ? o.guard.track(id, o.sessions(spec, gate, log, () => memberNow)) : null;
537
+ const msafety = o.memberControl === false ? null : new MemberSafety({ home: o.home, fleetId: o.fleetId, member: id, runId });
538
+ fl.add(now, 'member_start', { member: id, runId, why, strategyHash: spec.strategy.hash, bankroll: spec.bankroll, ...(account ? { account: account.id } : {}) });
539
+ say(`[${id}] started (run ${runId}, ${spec.basedOn.id} v${spec.basedOn.version}, ${account ? `paper account ${account.id} ($${account.cashUsd.toFixed(2)} cash of $${account.startUsd})` : `bankroll $${spec.bankroll.usd}${spec.bankroll.sol !== undefined ? ` = ${spec.bankroll.sol} SOL` : ''}`})`);
540
+ const ticks = hub.subscribe(runId);
541
+ l.done = runAutotrade({
542
+ strategy: spec.strategy, source: { name: o.tape?.source ?? o.source.name, ticks, startT: now }, sessions, gate, ledger, runId,
543
+ dossier: o.dossier?.(spec), vet: o.vet?.(spec), executor: execs.executors.get(id), verbose: o.verbose ?? true, onLine: (s) => say(`[${id}] ${s}`),
544
+ pacing, ...(account ? { account, member: id } : {}),
545
+ safety: () => {
546
+ const g = o.globalSafety();
547
+ return msafety ? mergeVerdicts(g, msafety.step()) : g;
548
+ },
549
+ onState: (st) => {
550
+ memberNow = st.t;
551
+ l.last = st;
552
+ l.state = st.state === 'stopped' ? 'stopped' : st.state;
553
+ o.hooks?.onMemberState?.(id, runId, st);
554
+ },
555
+ onFocus: (marks) => { l.focus = marks; publishFocus(); },
556
+ }).then((res) => {
557
+ hub.unsubscribe(runId);
558
+ l.result = res;
559
+ l.state = 'stopped';
560
+ l.endedAt = clock();
561
+ l.focus = [];
562
+ publishFocus();
563
+ const v = l.stats.view(l.last);
564
+ const own = Math.round(res.book.ownPnlUsd() * 100) / 100;
565
+ fl.add(now, 'member_end', { member: id, runId, trades: res.trades.length, openAtEnd: res.openAtEnd.length, equityUsd: Math.round(res.book.markedEquity() * 100) / 100, startUsd: spec.bankroll.usd, spendUsd: v.spendUsd, head: res.ledger.head(), ...(account ? { account: account.id, pnlUsd: own } : {}) });
566
+ say(account
567
+ ? `[${id}] ended · ${res.trades.length} closed, ${res.openAtEnd.length} open · its P/L ${own >= 0 ? '+' : '-'}$${Math.abs(own).toFixed(2)} on paper account ${account.id} (account equity $${res.book.markedEquity().toFixed(2)} of $${account.startUsd})`
568
+ : `[${id}] ended · ${res.trades.length} closed, ${res.openAtEnd.length} open · equity $${res.book.markedEquity().toFixed(2)} of $${spec.bankroll.usd}`);
569
+ return res;
570
+ }, (e) => {
571
+ hub.unsubscribe(runId);
572
+ l.state = 'error';
573
+ l.error = e.message?.slice(0, 200) ?? String(e);
574
+ l.endedAt = clock();
575
+ l.focus = [];
576
+ publishFocus();
577
+ fl.add(now, 'member_error', { member: id, runId, error: l.error });
578
+ say(`[${id}] failed: ${l.error}`);
579
+ return null;
580
+ });
581
+ };
582
+ // Start requests: a strategy of the config that is not running starts at the next tick.
583
+ const memberControlOn = o.memberControl !== false;
584
+ const checkStarts = () => {
585
+ if (!memberControlOn || globalHalt)
586
+ return;
587
+ for (const l of lives.values()) {
588
+ if (l.state === 'running' || l.state === 'paused' || l.state === 'halted')
589
+ continue;
590
+ const id = l.spec.config.id;
591
+ const c = readMemberControl(o.home, id);
592
+ if (!c || c === 'unreadable' || c.fleetId !== o.fleetId || c.action !== 'start')
593
+ continue;
594
+ try {
595
+ rmSync(memberControlPath(o.home, id), { force: true });
596
+ }
597
+ catch { /* re-read next tick */ }
598
+ fl.add(now, 'member_control', { member: id, action: 'start', by: c.by });
599
+ startMember(l, `start requested (${c.by})`);
600
+ }
601
+ };
602
+ for (const l of lives.values())
603
+ if (l.spec.config.enabled)
604
+ startMember(l, 'enabled in autotrade.json');
605
+ emitStatus();
606
+ const anyRunning = () => [...lives.values()].some((l) => l.done && l.state !== 'stopped' && l.state !== 'error');
607
+ const loop = o.tape ? (async function* () { for (const k of o.tape.ticks)
608
+ yield k; })() : o.source.ticks;
609
+ let lastStatus = 0;
610
+ for await (const k of loop) {
611
+ now = k.t;
612
+ const g = o.globalSafety();
613
+ if (g.halt && !globalHalt) {
614
+ globalHalt = g.halt;
615
+ fl.add(now, 'fleet_halt', { reason: g.halt, ...(g.detail ? { detail: g.detail } : {}), flatten: g.flatten });
616
+ say(`FLEET ${g.halt.toUpperCase()}: every strategy takes no new entries; mechanical exits keep running`);
617
+ }
618
+ checkStarts();
619
+ for (const l of lives.values())
620
+ l.stats.onTick(k);
621
+ if (!anyRunning() && hub.size === 0)
622
+ break;
623
+ await hub.publish(k);
624
+ if (clock() - lastStatus >= 1000) {
625
+ lastStatus = clock();
626
+ emitStatus();
627
+ }
628
+ }
629
+ if (o.tape)
630
+ await hub.drain();
631
+ hub.end();
632
+ const results = await Promise.all([...lives.values()].map((l) => l.done ?? Promise.resolve(null)));
633
+ void results;
634
+ const final = status('stopped', globalHalt);
635
+ fl.add(now, 'fleet_end', { members: final.members.map((m) => ({ id: m.id, runId: m.runId, state: m.state, equityUsd: m.book?.equityUsd ?? null, startUsd: m.bankroll.usd, closed: m.stats.closed, spendUsd: m.stats.spendUsd, ...(m.account ? { account: m.account, pnlUsd: m.pnlUsd ?? null } : {}) })), ...(final.accounts ? { accounts: final.accounts } : {}), head: fl.head() });
636
+ o.onStatus?.(final);
637
+ o.hooks?.onFleetState?.(final);
638
+ return { fleetId: o.fleetId, members: [...lives.values()].map((l) => ({ id: l.spec.config.id, runId: l.runId, result: l.result, ...(l.error ? { error: l.error } : {}) })), status: final };
639
+ }