dexterm-cli 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/dist/packages/agents/src/schema.js +22 -1
  2. package/dist/packages/agents/src/story.js +11 -2
  3. package/dist/packages/agents/src/tiers.js +27 -2
  4. package/dist/packages/agents/src/tools.js +30 -2
  5. package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
  6. package/dist/packages/autotrade/assets/profiles/moonbag.json +46 -0
  7. package/dist/packages/autotrade/assets/profiles/runner.json +88 -0
  8. package/dist/packages/autotrade/assets/profiles/scalp.json +88 -0
  9. package/dist/packages/autotrade/assets/profiles/swing.json +88 -0
  10. package/dist/packages/autotrade/assets/strategies/moonbag.json +101 -0
  11. package/dist/packages/autotrade/assets/strategies/runner.json +101 -0
  12. package/dist/packages/autotrade/assets/strategies/sample-dip-ladder.json +188 -0
  13. package/dist/packages/autotrade/assets/strategies/scalp.json +98 -0
  14. package/dist/packages/autotrade/assets/strategies/swing.json +97 -0
  15. package/dist/packages/autotrade/assets/strategies/yolo.json +157 -0
  16. package/dist/packages/autotrade/assets/tapes/yolo-rehearsal.json +1 -0
  17. package/dist/packages/autotrade/src/account.js +74 -0
  18. package/dist/packages/autotrade/src/backtest.js +357 -0
  19. package/dist/packages/autotrade/src/basis-text.js +32 -0
  20. package/dist/packages/autotrade/src/chat/approvals.js +484 -0
  21. package/dist/packages/autotrade/src/chat/audit.js +140 -0
  22. package/dist/packages/autotrade/src/chat/config.js +93 -0
  23. package/dist/packages/autotrade/src/chat/connector.js +24 -0
  24. package/dist/packages/autotrade/src/chat/control.js +103 -0
  25. package/dist/packages/autotrade/src/chat/fake.js +100 -0
  26. package/dist/packages/autotrade/src/chat/format.js +71 -0
  27. package/dist/packages/autotrade/src/chat/hub.js +187 -0
  28. package/dist/packages/autotrade/src/chat/index.js +18 -0
  29. package/dist/packages/autotrade/src/chat/notify.js +292 -0
  30. package/dist/packages/autotrade/src/chat/pairing.js +46 -0
  31. package/dist/packages/autotrade/src/chat/redact.js +48 -0
  32. package/dist/packages/autotrade/src/chat/secrets.js +99 -0
  33. package/dist/packages/autotrade/src/chat/service.js +248 -0
  34. package/dist/packages/autotrade/src/chat/telegram.js +290 -0
  35. package/dist/packages/autotrade/src/desk-executor.js +188 -0
  36. package/dist/packages/autotrade/src/engine.js +387 -0
  37. package/dist/packages/autotrade/src/execution-port.js +97 -0
  38. package/dist/packages/autotrade/src/fleet-config.js +169 -0
  39. package/dist/packages/autotrade/src/fleet-control.js +276 -0
  40. package/dist/packages/autotrade/src/fleet.js +639 -0
  41. package/dist/packages/autotrade/src/index.js +53 -0
  42. package/dist/packages/autotrade/src/ledger.js +108 -0
  43. package/dist/packages/autotrade/src/live-source.js +182 -0
  44. package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
  45. package/dist/packages/autotrade/src/monitor.js +190 -0
  46. package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
  47. package/dist/packages/autotrade/src/paper-accounts.js +72 -0
  48. package/dist/packages/autotrade/src/replay.js +198 -0
  49. package/dist/packages/autotrade/src/runner-config.js +99 -0
  50. package/dist/packages/autotrade/src/runner.js +872 -0
  51. package/dist/packages/autotrade/src/safety.js +243 -0
  52. package/dist/packages/autotrade/src/session.js +269 -0
  53. package/dist/packages/autotrade/src/sessions.js +503 -0
  54. package/dist/packages/autotrade/src/store.js +250 -0
  55. package/dist/packages/autotrade/src/strategy.js +256 -0
  56. package/dist/packages/autotrade/src/trade-ledger.js +241 -0
  57. package/dist/packages/autotrade/src/yolo.js +140 -0
  58. package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
  59. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
  60. package/dist/packages/cli/src/ai-cmd.js +412 -0
  61. package/dist/packages/cli/src/analyst/backend.js +82 -15
  62. package/dist/packages/cli/src/analyst/codex.js +14 -9
  63. package/dist/packages/cli/src/analyst/ollama.js +263 -0
  64. package/dist/packages/cli/src/analyst/session.js +5 -2
  65. package/dist/packages/cli/src/app-help-gen.js +44 -8
  66. package/dist/packages/cli/src/autotrade-cmd/audit.js +57 -0
  67. package/dist/packages/cli/src/autotrade-cmd/backtest.js +199 -0
  68. package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
  69. package/dist/packages/cli/src/autotrade-cmd/fleet.js +397 -0
  70. package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
  71. package/dist/packages/cli/src/autotrade-cmd/run.js +413 -0
  72. package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
  73. package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
  74. package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
  75. package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
  76. package/dist/packages/cli/src/autotrade-cmd.js +168 -0
  77. package/dist/packages/cli/src/display.js +156 -17
  78. package/dist/packages/cli/src/doctor.js +62 -3
  79. package/dist/packages/cli/src/export.js +10 -2
  80. package/dist/packages/cli/src/index.js +1 -1
  81. package/dist/packages/cli/src/ledger-cmd.js +288 -0
  82. package/dist/packages/cli/src/live/chart-fills.js +46 -0
  83. package/dist/packages/cli/src/live/index.js +23 -89
  84. package/dist/packages/cli/src/live/screens/token-live.js +9 -6
  85. package/dist/packages/cli/src/live/trade.js +12 -457
  86. package/dist/packages/cli/src/live/watchlist.js +1 -1
  87. package/dist/packages/cli/src/main.js +147 -190
  88. package/dist/packages/cli/src/mcp-cmd.js +7 -1
  89. package/dist/packages/cli/src/plan-cmd.js +1 -1
  90. package/dist/packages/cli/src/pnl-app.js +31 -21
  91. package/dist/packages/cli/src/pnl-card.js +158 -21
  92. package/dist/packages/cli/src/popup.js +22 -6
  93. package/dist/packages/cli/src/record-cmd.js +141 -20
  94. package/dist/packages/cli/src/screens/agent-hud.js +34 -2
  95. package/dist/packages/cli/src/screens/analyst-session.js +7 -4
  96. package/dist/packages/cli/src/screens/analyst.js +3 -2
  97. package/dist/packages/cli/src/screens/autotrade-keys.js +169 -0
  98. package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
  99. package/dist/packages/cli/src/screens/autotrade-screen.js +725 -0
  100. package/dist/packages/cli/src/screens/autotrade-ui.js +640 -0
  101. package/dist/packages/cli/src/screens/autotrade-worker.js +89 -0
  102. package/dist/packages/cli/src/screens/brain.js +37 -11
  103. package/dist/packages/cli/src/screens/overlay.js +5 -2
  104. package/dist/packages/cli/src/screens/plans.js +5 -17
  105. package/dist/packages/cli/src/screens/pnl-chip.js +9 -8
  106. package/dist/packages/cli/src/screens/positions.js +6 -28
  107. package/dist/packages/cli/src/screens/ticket.js +38 -205
  108. package/dist/packages/cli/src/screens/token.js +3 -3
  109. package/dist/packages/cli/src/state.js +25 -19
  110. package/dist/packages/cli/src/telegram-cmd.js +451 -0
  111. package/dist/packages/cli/src/trade.js +3 -128
  112. package/dist/packages/cli/src/version.js +1 -1
  113. package/dist/packages/cli/src/wallet-cmd.js +39 -340
  114. package/dist/packages/cli/src/wallet-command.js +8 -11
  115. package/dist/packages/cli/src/widgets.js +0 -12
  116. package/dist/packages/custom/src/config.js +20 -2
  117. package/dist/packages/custom/src/schema.js +35 -10
  118. package/dist/packages/custom/src/workspace.js +3 -3
  119. package/dist/packages/data/src/basis.js +191 -0
  120. package/dist/packages/data/src/detail.js +53 -2
  121. package/dist/packages/data/src/index.js +0 -14
  122. package/dist/packages/data/src/store.js +14 -5
  123. package/dist/packages/data/src/trade/agent/paper-desk.js +228 -16
  124. package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
  125. package/dist/packages/data/src/trade/bridge.js +4 -102
  126. package/dist/packages/data/src/trade/ledger/export.js +101 -0
  127. package/dist/packages/data/src/trade/ledger/file.js +135 -0
  128. package/dist/packages/data/src/trade/ledger/fold.js +191 -0
  129. package/dist/packages/data/src/trade/ledger/index.js +9 -0
  130. package/dist/packages/data/src/trade/ledger/schema.js +83 -0
  131. package/dist/packages/data/src/trade/ledger/sources.js +307 -0
  132. package/dist/packages/data/src/trade/ledger/stats.js +123 -0
  133. package/dist/packages/data/src/trade/ledger/tags.js +116 -0
  134. package/dist/packages/data/src/trade/ledger/view.js +51 -0
  135. package/dist/packages/data/src/trade/plans/index.js +0 -1
  136. package/dist/packages/data/src/trade/plans/service.js +3 -27
  137. package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
  138. package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
  139. package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
  140. package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
  141. package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
  142. package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
  143. package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
  144. package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
  145. package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
  146. package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
  147. package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
  148. package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
  149. package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
  150. package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +217 -0
  151. package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
  152. package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
  153. package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
  154. package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
  155. package/dist/packages/mcp/assets/kb/app-help/screen-token.md +3 -0
  156. package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
  157. package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
  158. package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
  159. package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
  160. package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
  161. package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +4 -1
  162. package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
  163. package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
  164. package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
  165. package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
  166. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
  167. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
  168. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +49 -0
  169. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
  170. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
  171. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
  172. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
  173. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +37 -0
  174. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
  175. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
  176. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
  177. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
  178. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
  179. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
  180. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +35 -0
  181. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
  182. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
  183. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
  184. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
  185. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
  186. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
  187. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
  188. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +43 -0
  189. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
  190. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
  191. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
  192. package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
  193. package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
  194. package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
  195. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
  196. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +1 -1
  197. package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
  198. package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
  199. package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
  200. package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +8 -6
  201. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
  202. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
  203. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
  204. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
  205. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
  206. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
  207. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
  208. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
  209. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
  210. package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
  211. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
  212. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
  213. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
  214. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +33 -0
  215. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
  216. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
  217. package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
  218. package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
  219. package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
  220. package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
  221. package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
  222. package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
  223. package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
  224. package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
  225. package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
  226. package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
  227. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
  228. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
  229. package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
  230. package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
  231. package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
  232. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
  233. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
  234. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +3 -4
  235. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
  236. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +6 -7
  237. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
  238. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
  239. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
  240. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
  241. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
  242. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
  243. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
  244. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
  245. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +2 -1
  246. package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
  247. package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
  248. package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
  249. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +1 -1
  250. package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
  251. package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
  252. package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
  253. package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
  254. package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
  255. package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
  256. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
  257. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
  258. package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
  259. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
  260. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
  261. package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
  262. package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
  263. package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
  264. package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
  265. package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
  266. package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
  267. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
  268. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
  269. package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
  270. package/dist/packages/mcp/assets/kb/entries/autotrade.md +38 -0
  271. package/dist/packages/mcp/src/app-help.js +1 -1
  272. package/dist/packages/mcp/src/autotrade-tools.js +944 -0
  273. package/dist/packages/mcp/src/backtest-requests.js +71 -0
  274. package/dist/packages/mcp/src/engine/config.js +159 -0
  275. package/dist/packages/mcp/src/engine/index.js +10 -0
  276. package/dist/packages/mcp/src/engine/loop.js +101 -0
  277. package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
  278. package/dist/packages/mcp/src/engine/ollama.js +258 -0
  279. package/dist/packages/mcp/src/engine/probe.js +110 -0
  280. package/dist/packages/mcp/src/engine/reason.js +35 -0
  281. package/dist/packages/mcp/src/engine/restrictions.js +111 -0
  282. package/dist/packages/mcp/src/index.js +7 -1
  283. package/dist/packages/mcp/src/ledger-tools.js +346 -0
  284. package/dist/packages/mcp/src/paper-agent.js +2 -0
  285. package/dist/packages/mcp/src/server.js +127 -19
  286. package/dist/packages/packs/src/backtest.js +3 -1
  287. package/dist/packages/popup/src/pages/chart-lib.js +134 -21
  288. package/dist/packages/popup/src/pages/chart.css +9 -0
  289. package/dist/packages/popup/src/pages/chart.html +3 -1
  290. package/dist/packages/popup/src/pages/chart.js +104 -7
  291. package/dist/packages/popup/src/protocol.js +2 -0
  292. package/dist/packages/popup/src/server.js +4 -0
  293. package/dist/packages/recorder/src/index.js +2 -0
  294. package/dist/packages/recorder/src/recorder.js +63 -6
  295. package/dist/packages/recorder/src/retention.js +100 -0
  296. package/dist/packages/recorder/src/schema.js +34 -1
  297. package/dist/packages/recorder/src/ticks.js +237 -0
  298. package/dist/packages/tui/src/index.js +1 -0
  299. package/dist/packages/tui/src/keymap.js +21 -1
  300. package/dist/packages/tui/src/markers.js +219 -0
  301. package/dist/packages/tui/src/widgets.js +149 -2
  302. package/package.json +2 -1
  303. package/dist/packages/data/src/trade/caps.js +0 -323
  304. package/dist/packages/data/src/trade/executor.js +0 -933
  305. package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
  306. package/dist/packages/data/src/trade/rpc-exec.js +0 -229
  307. package/dist/packages/data/src/trade/signer/client.js +0 -197
  308. package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
  309. package/dist/packages/data/src/trade/signer/host.js +0 -97
  310. package/dist/packages/data/src/trade/signer/main.js +0 -121
  311. package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
  312. package/dist/packages/data/src/trade/signer/protocol.js +0 -92
  313. package/dist/packages/data/src/trade/signer/service.js +0 -400
  314. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
  315. package/dist/packages/data/src/trade/sol-usd.js +0 -86
  316. package/dist/packages/data/src/trade/swap-build.js +0 -108
  317. package/dist/packages/data/src/trade/token-delta.js +0 -201
  318. package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
  319. package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
  320. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
  321. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
  322. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
  323. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
  324. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
  325. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
  326. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
  327. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
  328. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
  329. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
  330. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
  331. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
@@ -0,0 +1,250 @@
1
+ // dexterm autotrade — the strategy store (task-1503 AT-1, AUTOTRADE-SPEC §2c).
2
+ //
3
+ // ~/.dexterm/strategies/<id>.json one validated dexterm-strategy/1 document per strategy ($DEXTERM_HOME honoured)
4
+ // ~/.dexterm/strategies/.journal.jsonl append-only audit: {t, actor, id, ops, beforeHash, afterHash, version, …}
5
+ // ~/.dexterm/strategies/.backtests.json the last backtest line per strategy hash (for `list`)
6
+ // ~/.dexterm/strategies/.removed/ a removed strategy is moved here, never deleted
7
+ //
8
+ // Every write validates first (an invalid document is never written), is atomic (tmp + rename, 0600 in a
9
+ // 0700 dir) and appends one journal line naming the actor and the before/after content hashes. The shipped
10
+ // presets are read-only: `get` falls back to them, and editing one means `create` from it under a new id.
11
+ // Ids are the schema's `^[a-z][a-z0-9-]{1,31}$`, checked before any path is built, so an id cannot walk out of
12
+ // the directory. Nothing here decides who may change what: the model deny-list and the keypress live with
13
+ // the proposal flow (AT-5); this module records the actor it is told.
14
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { applyOps, dextermHome } from '../../custom/src/index.js';
18
+ import { fmtIssue, loadStrategyFile, validateStrategy } from "./strategy.js";
19
+ export const STRATEGY_ID = /^[a-z][a-z0-9-]{1,31}$/;
20
+ const ACTOR = /^[a-z][a-z0-9:._-]{0,63}$/;
21
+ /** The most ops one change may carry (the chat tool's limit, §2a). */
22
+ export const MAX_OPS = 20;
23
+ export const strategiesDir = (home) => join(home, 'strategies');
24
+ const fail = (msg, path = '') => ({ ok: false, errors: [{ path, msg }], warnings: [] });
25
+ export class StrategyStore {
26
+ dir;
27
+ journalPath;
28
+ now;
29
+ presets;
30
+ constructor(o = {}) {
31
+ this.dir = strategiesDir(o.home ?? dextermHome(o.env));
32
+ this.journalPath = join(this.dir, '.journal.jsonl');
33
+ this.now = o.now ?? Date.now;
34
+ this.presets = o.presetsDir ?? new URL('../assets/strategies/', import.meta.url);
35
+ }
36
+ pathOf(id) {
37
+ if (!STRATEGY_ID.test(id))
38
+ throw new Error(`bad strategy id ${JSON.stringify(id)} (lowercase letters, digits and dashes, 2-32 chars)`);
39
+ return join(this.dir, `${id}.json`);
40
+ }
41
+ presetFile(id) { return fileURLToPath(new URL(`${id}.json`, this.presets)); }
42
+ presetIds() {
43
+ return readdirSync(this.presets).filter((f) => f.endsWith('.json')).map((f) => f.slice(0, -5)).sort();
44
+ }
45
+ userIds() {
46
+ if (!existsSync(this.dir))
47
+ return [];
48
+ return readdirSync(this.dir).filter((f) => f.endsWith('.json') && !f.startsWith('.') && STRATEGY_ID.test(f.slice(0, -5))).map((f) => f.slice(0, -5)).sort();
49
+ }
50
+ has(id) { return existsSync(this.pathOf(id)); }
51
+ /** The user's file first, else the shipped preset of that id; null when neither exists. */
52
+ get(id) {
53
+ const user = this.pathOf(id);
54
+ if (existsSync(user))
55
+ return { ...loadStrategyFile(user), source: 'user', path: user };
56
+ if (this.presetIds().includes(id)) {
57
+ const p = this.presetFile(id);
58
+ return { ...loadStrategyFile(p), source: 'preset', path: p };
59
+ }
60
+ return null;
61
+ }
62
+ /** The raw document as stored (user file, else preset), for edits. */
63
+ raw(id) {
64
+ const user = this.pathOf(id);
65
+ const path = existsSync(user) ? user : this.presetIds().includes(id) ? this.presetFile(id) : null;
66
+ if (!path)
67
+ return null;
68
+ try {
69
+ return JSON.parse(readFileSync(path, 'utf8'));
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
75
+ list() {
76
+ const last = this.backtests();
77
+ const users = this.userIds();
78
+ const entry = (id, source) => {
79
+ const r = loadStrategyFile(source === 'user' ? this.pathOf(id) : this.presetFile(id));
80
+ if (!r.ok)
81
+ return { id, source, ok: false, errors: r.errors, warnings: r.warnings };
82
+ const s = r.strategy;
83
+ return {
84
+ id, source, ok: true, name: s.name, version: s.version, hash: s.hash, mode: s.mode, ...(s.highRisk ? { highRisk: true } : {}), profiles: Object.keys(s.exits.profiles), warnings: r.warnings,
85
+ ...(last[s.hash] ? { lastBacktest: last[s.hash] } : {}),
86
+ };
87
+ };
88
+ const presets = this.presetIds();
89
+ return [
90
+ ...presets.filter((id) => !users.includes(id)).map((id) => entry(id, 'preset')),
91
+ ...users.map((id) => ({ ...entry(id, 'user'), ...(presets.includes(id) ? { shadowsPreset: true } : {}) })),
92
+ ];
93
+ }
94
+ /** Create a new user strategy from a document (its `id` names the file). Refuses an existing id. */
95
+ create(doc, o) {
96
+ const id = doc?.id;
97
+ if (typeof id !== 'string' || !STRATEGY_ID.test(id))
98
+ return fail('the document needs a valid id (lowercase letters, digits and dashes, 2-32 chars)', '/id');
99
+ if (this.has(id))
100
+ return fail(`a strategy ${id} already exists in ${this.dir} (edit it, or pick another id)`, '/id');
101
+ return this.write(id, doc, null, { ...o, action: 'create', ops: [{ op: 'add', path: '', value: doc }] });
102
+ }
103
+ /** Copy a preset or a stored strategy under a new id (and name). */
104
+ createFrom(fromId, newId, o) {
105
+ const base = this.raw(fromId);
106
+ if (!base)
107
+ return fail(`no strategy or preset ${fromId}`);
108
+ const doc = { ...base, id: newId, version: '1.0.0', ...(o.name ? { name: o.name } : {}) };
109
+ return this.create(doc, { actor: o.actor, why: o.why, from: fromId });
110
+ }
111
+ /** Replace a user strategy with a whole new document (same id). The version must go up. */
112
+ update(id, doc, o) {
113
+ if (!this.has(id))
114
+ return fail(`no user strategy ${id} (presets are read-only: create a copy first)`);
115
+ if (doc?.id !== id)
116
+ return fail(`the document id must stay ${id}`, '/id');
117
+ return this.write(id, doc, this.currentHash(id), { ...o, action: 'update', ops: [{ op: 'replace', path: '', value: doc }] });
118
+ }
119
+ /**
120
+ * Apply JSON-patch ops (≤ MAX_OPS) to a user strategy. The patch version is bumped unless the ops set
121
+ * `/version` themselves; `/id` cannot change. The journal line carries the ops and their inverse.
122
+ */
123
+ patch(id, ops, o) {
124
+ if (!this.has(id))
125
+ return fail(`no user strategy ${id} (presets are read-only: create a copy first)`);
126
+ if (!Array.isArray(ops) || ops.length === 0)
127
+ return fail('no ops');
128
+ if (ops.length > MAX_OPS)
129
+ return fail(`${ops.length} ops is more than ${MAX_OPS} in one change`);
130
+ if (ops.some((x) => x.path === '/id' || x.path === ''))
131
+ return fail('the id and the whole document cannot be patched', '/id');
132
+ const before = this.raw(id);
133
+ if (!before)
134
+ return fail(`${id} is not readable JSON`);
135
+ let next, inverse;
136
+ try {
137
+ ({ doc: next, inverse } = applyOps(before, ops));
138
+ }
139
+ catch (e) {
140
+ return fail(e.message);
141
+ }
142
+ if (!ops.some((x) => x.path === '/version'))
143
+ next = { ...next, version: bumpPatch(before.version) };
144
+ return this.write(id, next, this.currentHash(id), { ...o, action: 'patch', ops, inverse });
145
+ }
146
+ /** Move a user strategy to .removed/ (never deleted) and journal it. */
147
+ remove(id, o) {
148
+ if (!this.has(id))
149
+ return fail(`no user strategy ${id}`);
150
+ const t = this.now();
151
+ const before = this.currentHash(id);
152
+ const dest = join(this.dir, '.removed');
153
+ mkdirSync(dest, { recursive: true, mode: 0o700 });
154
+ const movedTo = join(dest, `${id}.${t}.json`);
155
+ renameSync(this.pathOf(id), movedTo);
156
+ const journal = { t, actor: checkActor(o.actor), id, action: 'remove', ops: [{ op: 'remove', path: '' }], beforeHash: before, afterHash: null, version: null, ...(o.why ? { why: o.why } : {}) };
157
+ this.append(journal);
158
+ return { ok: true, journal, movedTo };
159
+ }
160
+ journal(id) {
161
+ if (!existsSync(this.journalPath))
162
+ return [];
163
+ const out = [];
164
+ for (const l of readFileSync(this.journalPath, 'utf8').split('\n')) {
165
+ if (!l.trim())
166
+ continue;
167
+ try {
168
+ const j = JSON.parse(l);
169
+ if (!id || j.id === id)
170
+ out.push(j);
171
+ }
172
+ catch { /* a torn last line is skipped, never rewritten */ }
173
+ }
174
+ return out;
175
+ }
176
+ backtests() {
177
+ const p = join(this.dir, '.backtests.json');
178
+ if (!existsSync(p))
179
+ return {};
180
+ try {
181
+ return JSON.parse(readFileSync(p, 'utf8'));
182
+ }
183
+ catch {
184
+ return {};
185
+ }
186
+ }
187
+ /** Remember the last backtest of a strategy hash (shown by `list`; a changed strategy has a new hash). */
188
+ recordBacktest(line) {
189
+ const all = this.backtests();
190
+ all[line.hash] = line;
191
+ const keep = Object.values(all).sort((a, b) => b.t - a.t).slice(0, 200);
192
+ this.atomic(join(this.dir, '.backtests.json'), JSON.stringify(Object.fromEntries(keep.map((b) => [b.hash, b])), null, 2) + '\n');
193
+ }
194
+ // ───────────────────────────── internals ─────────────────────────────
195
+ currentHash(id) {
196
+ const r = loadStrategyFile(this.pathOf(id));
197
+ return r.ok ? r.strategy.hash : null;
198
+ }
199
+ write(id, doc, beforeHash, o) {
200
+ let actor;
201
+ try {
202
+ actor = checkActor(o.actor);
203
+ }
204
+ catch (e) {
205
+ return fail(e.message);
206
+ }
207
+ const r = validateStrategy(doc);
208
+ if (!r.ok)
209
+ return r;
210
+ const prev = this.has(id) ? this.raw(id) : null;
211
+ if (prev && o.action !== 'create' && !semverGreater(r.strategy.version, prev.version))
212
+ return { ok: false, errors: [{ path: '/version', msg: `version ${r.strategy.version} must be above the stored ${prev.version}` }], warnings: r.warnings };
213
+ mkdirSync(this.dir, { recursive: true, mode: 0o700 });
214
+ this.atomic(this.pathOf(id), JSON.stringify(doc, null, 2) + '\n');
215
+ const journal = {
216
+ t: this.now(), actor, id, action: o.action, ops: o.ops, ...(o.inverse ? { inverse: o.inverse } : {}),
217
+ beforeHash, afterHash: r.strategy.hash, version: r.strategy.version,
218
+ ...(o.why ? { why: o.why.slice(0, 400) } : {}), ...(o.proposalId ? { proposalId: o.proposalId } : {}), ...(o.from ? { from: o.from } : {}),
219
+ };
220
+ this.append(journal);
221
+ return { ok: true, strategy: r.strategy, warnings: r.warnings, journal };
222
+ }
223
+ append(j) {
224
+ mkdirSync(this.dir, { recursive: true, mode: 0o700 });
225
+ appendFileSync(this.journalPath, `${JSON.stringify(j)}\n`, { mode: 0o600 });
226
+ }
227
+ atomic(path, text) {
228
+ mkdirSync(this.dir, { recursive: true, mode: 0o700 });
229
+ const tmp = `${path}.tmp-${process.pid}`;
230
+ writeFileSync(tmp, text, { mode: 0o600 });
231
+ renameSync(tmp, path);
232
+ }
233
+ }
234
+ function checkActor(a) {
235
+ if (typeof a !== 'string' || !ACTOR.test(a))
236
+ throw new Error(`bad actor ${JSON.stringify(a)}`);
237
+ return a;
238
+ }
239
+ export function bumpPatch(v) {
240
+ const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v);
241
+ return m ? `${m[1]}.${m[2]}.${Number(m[3]) + 1}` : '1.0.1';
242
+ }
243
+ export function semverGreater(a, b) {
244
+ const pa = a.split('.').map(Number), pb = b.split('.').map(Number);
245
+ for (let i = 0; i < 3; i++)
246
+ if ((pa[i] ?? 0) !== (pb[i] ?? 0))
247
+ return (pa[i] ?? 0) > (pb[i] ?? 0);
248
+ return false;
249
+ }
250
+ export const fmtIssues = (xs) => xs.map(fmtIssue).join('; ');
@@ -0,0 +1,256 @@
1
+ // dexterm autotrade — the strategy document (task-1498 spike, AUTOTRADE-SPEC §1).
2
+ //
3
+ // One declarative, versioned JSON document says everything an autotrade run may do: which coins are
4
+ // candidates (the screener filter grammar plus safety gates and triggers), how a position is exited (exit
5
+ // profiles, the challenge-kit model extended with `wake`), how much loss is allowed, how a size is picked,
6
+ // and what the per-candidate agent session may spend. The engine, the clamp, the monitor and the backtester
7
+ // all read this one document, so a backtest and a paper run of the same file make the same decisions.
8
+ //
9
+ // Validation is the zero-dependency subset validator from @dexterm/custom plus the cross-field rules a
10
+ // schema cannot say. Nothing in here can name a key, a wallet or the signer: closed objects make an attempt
11
+ // to add one a precise refusal, and `mode: "live"` validates but is refused by the runner (paper only).
12
+ import { readFileSync, readdirSync } from 'node:fs';
13
+ import { createHash } from 'node:crypto';
14
+ import { validate } from '../../custom/src/index.js';
15
+ import { parseFilter } from "../../cli/src/screener/filter.js";
16
+ export const STRATEGY_SCHEMA = 'dexterm-strategy/1';
17
+ export const EXIT_PROFILE_VERSION = '1.2.0';
18
+ // ───────────────────────────── schema ─────────────────────────────
19
+ const ID = '^[a-z][a-z0-9-]{1,31}$';
20
+ const SEMVER = '^[0-9]+\\.[0-9]+\\.[0-9]+$';
21
+ const num = (min, max, int = false) => ({ type: 'number', min, max, int });
22
+ const str = (max, pattern) => ({ type: 'string', max, pattern });
23
+ const obj = (props, required = Object.keys(props)) => ({ type: 'object', additional: false, required, props });
24
+ const nullable = (n) => ({ oneOf: [{ type: 'null' }, n] });
25
+ const ageRange = obj({ min: num(0, 8760), max: nullable(num(0, 8760)) });
26
+ export const SCHEMAS = {
27
+ exitProfile: obj({
28
+ kind: { type: 'string', enum: ['exit-profile'] },
29
+ id: str(32, ID), name: str(40), version: str(16, SEMVER), description: str(400),
30
+ fit: obj({ summary: str(300), minLiquidityUsd: num(1000), ageHours: ageRange, maxTop10Pct: nullable(num(0, 100)), signals: { type: 'array', items: str(200), max: 8 } }, ['summary', 'minLiquidityUsd', 'ageHours']),
31
+ sizing: obj({ maxSizePctOfBankroll: num(0.1, 25) }),
32
+ tpLadder: { type: 'array', max: 6, items: obj({ atPct: num(0.1, 10000), sellFrac: num(0.01, 1) }) },
33
+ trailingStop: nullable(obj({ armAtPct: num(0), trailPct: num(5, 60) })),
34
+ timeStop: nullable(obj({ minutes: num(1, 10080), ifBelowPct: num(-100, 10000), action: { type: 'string', enum: ['exit', 'review', 'tighten'] } })),
35
+ hardStop: obj({ enabled: { type: 'boolean' }, atPct: nullable(num(-90, 0)), why: str(400) }),
36
+ thesisBreak: obj({
37
+ liquidityDropPct: num(5, 90), liquidityWindowMin: num(1, 60), mcapDivergenceX: nullable(num(1.5)),
38
+ devSell: obj({ exitOnAnyDevSell: { type: 'boolean' }, topHolderSellPctOfBag: nullable(num(1, 100)) }),
39
+ volumeDeath: nullable(obj({ m5TradesBelow: num(1, 10000, true), forMinutes: num(1, 1440) })),
40
+ }),
41
+ reentry: obj({ allowed: { type: 'boolean' }, cooldownMinutes: num(0, 10080), maxEntriesPerCoinPerSession: num(1, 5, true), requires: str(300) }),
42
+ carry: obj({ allowed: { type: 'boolean' }, requiresTpFilled: num(0, 6, true), maxRemainderPctOfBankroll: num(0, 25) }),
43
+ reprofileTo: { type: 'array', max: 8, items: str(32, ID) },
44
+ wake: obj({ onTpFilled: { type: 'boolean' }, onTimeReview: { type: 'boolean' }, drawdownPct: nullable(num(-95, -1)), thesisWarnFrac: nullable(num(0.1, 0.95)), noAnswer: { type: 'string', enum: ['hold', 'exit'] }, onCostRecovered: { type: 'boolean' } }, ['onTpFilled', 'onTimeReview', 'drawdownPct', 'thesisWarnFrac', 'noAnswer']),
45
+ costRecovery: nullable(obj({ atPct: num(10, 10000), maxSellFrac: num(0.1, 0.9) })),
46
+ bagTrail: nullable(obj({ trailPct: num(10, 90), armAtPct: num(0, 100000) }, ['trailPct'])),
47
+ maxHoldMinutes: nullable(num(1, 43200)),
48
+ notes: { type: 'array', max: 8, items: str(300) },
49
+ }, ['kind', 'id', 'name', 'version', 'description', 'fit', 'sizing', 'tpLadder', 'trailingStop', 'timeStop', 'hardStop', 'thesisBreak', 'reentry', 'carry', 'reprofileTo']),
50
+ trigger: {
51
+ oneOf: [
52
+ obj({ kind: { type: 'string', enum: ['screen'] } }),
53
+ obj({ kind: { type: 'string', enum: ['momentum'] }, m5MinPct: num(-100, 10000), buySellMin: num(0, 100) }),
54
+ obj({ kind: { type: 'string', enum: ['narrative'] }, minHeat: num(0, 100) }),
55
+ obj({ kind: { type: 'string', enum: ['wallet'] }, list: { type: 'string', enum: ['followed'] }, minBuyUsd: num(0, 1e7) }),
56
+ ],
57
+ },
58
+ strategy: obj({
59
+ kind: { type: 'string', enum: ['strategy'] },
60
+ schema: { type: 'string', enum: [STRATEGY_SCHEMA] },
61
+ id: str(32, ID), name: str(40), version: str(16, SEMVER), description: str(600),
62
+ mode: { type: 'string', enum: ['paper', 'live'] },
63
+ highRisk: { type: 'boolean' },
64
+ bankroll: obj({ startUsd: num(1, 1e7), source: { type: 'string', enum: ['fixed', 'desk'] } }, ['startUsd']),
65
+ entry: obj({
66
+ screen: str(400),
67
+ ageHours: ageRange,
68
+ safety: obj({
69
+ maxTop10Pct: nullable(num(0, 100)), maxDevPct: nullable(num(0, 100)),
70
+ requireNoMintAuthority: { type: 'boolean' }, requireNoFreezeAuthority: { type: 'boolean' },
71
+ maxMcapSplitX: nullable(num(1.5, 100)),
72
+ youngVertical: nullable(obj({ maxAgeHours: num(0, 168), maxH1Pct: num(0, 100000) })),
73
+ unknownHolders: { type: 'string', enum: ['reject', 'allow'] },
74
+ larp: nullable(obj({ maxScore: num(0, 100), unknown: { type: 'string', enum: ['reject', 'allow'] } })),
75
+ }, ['maxTop10Pct', 'maxDevPct', 'requireNoMintAuthority', 'requireNoFreezeAuthority', 'maxMcapSplitX', 'youngVertical', 'unknownHolders']),
76
+ triggers: { type: 'array', max: 8, items: 'trigger' },
77
+ triggerMode: { type: 'string', enum: ['any', 'all'] },
78
+ decide: { type: 'string', enum: ['agent', 'auto', 'agent+approve'] },
79
+ candidateTtlSec: num(10, 3600),
80
+ }),
81
+ exits: obj({ default: str(32, ID), profiles: { type: 'array', max: 8, items: { oneOf: [str(48, '^builtin:[a-z][a-z0-9-]{1,31}$'), 'exitProfile'] } } }),
82
+ sizing: obj({
83
+ mode: { type: 'string', enum: ['fixed', 'pct', 'risk', 'confidence'] },
84
+ fixedUsd: num(1, 1e6), pct: num(0.1, 25), riskPct: num(0.1, 10),
85
+ confidence: obj({ minPct: num(0.1, 25), maxPct: num(0.1, 25), default: num(0, 1) }),
86
+ ladder: { type: 'array', max: 4, items: obj({ frac: num(0.05, 1), atPct: num(-90, 1000), withinMin: num(1, 1440) }) },
87
+ maxPctOfBankroll: num(0.1, 25), maxUsd: num(1, 1e6), minUsd: num(0.01, 1e6),
88
+ }, ['mode', 'maxPctOfBankroll', 'maxUsd', 'minUsd']),
89
+ risk: obj({
90
+ perTrade: obj({ maxLossPctOfBankroll: num(0.1, 25), assumedLossPctNoStop: num(10, 100) }),
91
+ dailyLossHaltPct: num(1, 100),
92
+ drawdown: obj({ sizeDownAtPct: num(1, 100), sizeDownMult: num(0.05, 1), recoverBelowPct: num(0, 100), haltAtPct: num(1, 100) }),
93
+ maxConcurrent: num(1, 20, true), maxExposurePct: num(1, 100),
94
+ lossStreak: obj({ losses: num(1, 50, true), cooldownMin: num(0, 10080) }),
95
+ maxEntriesPerDay: num(1, 500, true),
96
+ perCoin: obj({ maxEntries: num(1, 20, true), cooldownMin: num(0, 10080) }),
97
+ recoveredBags: obj({ countAsOpen: { type: 'boolean' }, maxBags: num(0, 50, true) }),
98
+ }, ['perTrade', 'dailyLossHaltPct', 'drawdown', 'maxConcurrent', 'maxExposurePct', 'lossStreak', 'maxEntriesPerDay', 'perCoin']),
99
+ agent: obj({
100
+ model: str(64, '^[a-z0-9][a-z0-9.-]{1,63}$'), maxSessions: num(1, 8, true),
101
+ budgetUsdPerSession: num(0.01, 50), budgetUsdPerDay: num(0.01, 500), maxTurns: num(1, 1000, true),
102
+ deltaEverySec: num(1, 600), decideWithinSec: num(5, 3600), sessionMaxMin: num(1, 1440),
103
+ onTimeout: { type: 'string', enum: ['skip'] },
104
+ respawnOnWake: { type: 'boolean' },
105
+ }, ['model', 'maxSessions', 'budgetUsdPerSession', 'budgetUsdPerDay', 'maxTurns', 'deltaEverySec', 'decideWithinSec', 'sessionMaxMin', 'onTimeout']),
106
+ execution: obj({ slippageBps: num(0, 5000), feeBps: num(0, 1000), networkFeeUsd: num(0, 10), latencyMs: num(0, 120000), maxPriceImpactPct: num(0.1, 50), minExitLiquidityUsd: num(0, 1e7) }),
107
+ notes: { type: 'array', max: 8, items: str(300) },
108
+ }, ['kind', 'schema', 'id', 'name', 'version', 'description', 'mode', 'bankroll', 'entry', 'exits', 'sizing', 'risk', 'agent', 'execution']),
109
+ };
110
+ /** Schema + the cross-field rules for one exit profile. */
111
+ export function validateExitProfile(p, at = '') {
112
+ const errs = validate('exitProfile', p, at, [], SCHEMAS);
113
+ if (errs.length)
114
+ return errs;
115
+ const x = p;
116
+ for (let i = 1; i < x.tpLadder.length; i++)
117
+ if (!(x.tpLadder[i].atPct > x.tpLadder[i - 1].atPct))
118
+ errs.push({ path: `${at}/tpLadder/${i}`, msg: 'atPct must be strictly ascending' });
119
+ const sum = x.tpLadder.reduce((s, t) => s + t.sellFrac, 0);
120
+ if (sum > 1 + 1e-9)
121
+ errs.push({ path: `${at}/tpLadder`, msg: `sellFrac adds up to ${round(sum, 4)} (more than the whole position)` });
122
+ if (x.hardStop.enabled && x.hardStop.atPct === null)
123
+ errs.push({ path: `${at}/hardStop/atPct`, msg: 'an enabled hard stop needs atPct' });
124
+ if (x.carry.allowed && x.carry.maxRemainderPctOfBankroll <= 0)
125
+ errs.push({ path: `${at}/carry`, msg: 'carry allowed needs maxRemainderPctOfBankroll > 0' });
126
+ if (x.bagTrail && !x.costRecovery)
127
+ errs.push({ path: `${at}/bagTrail`, msg: 'a bag trail is armed by the cost-recovery sell: set costRecovery' });
128
+ if (x.costRecovery && x.tpLadder.length && x.tpLadder[0].atPct <= x.costRecovery.atPct)
129
+ errs.push({ path: `${at}/tpLadder/0`, msg: 'a TP step at or below the cost-recovery level would sell first: put the ladder above costRecovery.atPct' });
130
+ if (x.reprofileTo.includes(x.id))
131
+ errs.push({ path: `${at}/reprofileTo`, msg: 'a profile cannot re-profile to itself' });
132
+ return errs;
133
+ }
134
+ /** Load the shipped exit-profile library (assets/autotrade/profiles). */
135
+ export function builtinProfiles(dir = new URL('../assets/profiles/', import.meta.url)) {
136
+ const out = {};
137
+ for (const f of readdirSync(dir).filter((x) => x.endsWith('.json')).sort()) {
138
+ const p = JSON.parse(readFileSync(new URL(f, dir), 'utf8'));
139
+ const errs = validateExitProfile(p);
140
+ if (errs.length)
141
+ throw new Error(`builtin profile ${f} is invalid: ${errs.map(fmtIssue).join('; ')}`);
142
+ out[p.id] = p;
143
+ }
144
+ return out;
145
+ }
146
+ /**
147
+ * Validate a strategy document and resolve its profile references. Never throws on bad input: every
148
+ * problem comes back as a path + message so the TUI editor and the assistant's tool can show it inline.
149
+ */
150
+ export function validateStrategy(doc, library = builtinProfiles()) {
151
+ const warnings = [];
152
+ const errors = validate('strategy', doc, '', [], SCHEMAS);
153
+ if (errors.length)
154
+ return { ok: false, errors, warnings };
155
+ const s = doc;
156
+ const f = parseFilter(s.entry.screen);
157
+ if (!f.ok)
158
+ errors.push({ path: '/entry/screen', msg: `${f.error} (column ${f.at})` });
159
+ const profiles = {};
160
+ s.exits.profiles.forEach((ref, i) => {
161
+ const p = typeof ref === 'string' ? library[ref.slice('builtin:'.length)] : ref;
162
+ if (!p) {
163
+ errors.push({ path: `/exits/profiles/${i}`, msg: `unknown builtin profile ${String(ref)}` });
164
+ return;
165
+ }
166
+ errors.push(...validateExitProfile(p, `/exits/profiles/${i}`));
167
+ if (profiles[p.id])
168
+ errors.push({ path: `/exits/profiles/${i}`, msg: `duplicate profile id ${p.id}` });
169
+ profiles[p.id] = p;
170
+ });
171
+ if (!profiles[s.exits.default])
172
+ errors.push({ path: '/exits/default', msg: `default profile ${s.exits.default} is not in exits.profiles` });
173
+ for (const p of Object.values(profiles)) {
174
+ for (const t of p.reprofileTo)
175
+ if (!profiles[t])
176
+ warnings.push({ path: '/exits/profiles', msg: `${p.id} may re-profile to ${t}, which this strategy does not include (that edge is unused)` });
177
+ if (p.sizing.maxSizePctOfBankroll < s.sizing.maxPctOfBankroll)
178
+ warnings.push({ path: '/sizing/maxPctOfBankroll', msg: `profile ${p.id} caps a position at ${p.sizing.maxSizePctOfBankroll}% (the lower cap wins)` });
179
+ }
180
+ const z = s.sizing;
181
+ const need = (k, mode) => { if (z.mode === mode && z[k] === undefined)
182
+ errors.push({ path: `/sizing/${k}`, msg: `sizing mode ${mode} needs ${k}` }); };
183
+ need('fixedUsd', 'fixed');
184
+ need('pct', 'pct');
185
+ need('riskPct', 'risk');
186
+ need('confidence', 'confidence');
187
+ if (z.confidence && z.confidence.minPct > z.confidence.maxPct)
188
+ errors.push({ path: '/sizing/confidence', msg: 'minPct is above maxPct' });
189
+ if (z.minUsd > z.maxUsd)
190
+ errors.push({ path: '/sizing/minUsd', msg: 'minUsd is above maxUsd' });
191
+ if (z.ladder) {
192
+ const sum = z.ladder.reduce((a, t) => a + t.frac, 0);
193
+ if (Math.abs(sum - 1) > 1e-6)
194
+ errors.push({ path: '/sizing/ladder', msg: `tranche fractions add up to ${round(sum, 4)}, they must add up to 1` });
195
+ if (z.ladder[0] && z.ladder[0].atPct !== 0)
196
+ errors.push({ path: '/sizing/ladder/0/atPct', msg: 'the first tranche fills at entry (atPct 0)' });
197
+ }
198
+ const r = s.risk;
199
+ if (r.drawdown.sizeDownAtPct > r.drawdown.haltAtPct)
200
+ errors.push({ path: '/risk/drawdown', msg: 'sizeDownAtPct must not exceed haltAtPct' });
201
+ if (r.drawdown.recoverBelowPct >= r.drawdown.sizeDownAtPct)
202
+ errors.push({ path: '/risk/drawdown/recoverBelowPct', msg: 'recoverBelowPct must be below sizeDownAtPct' });
203
+ if (s.agent.budgetUsdPerSession > s.agent.budgetUsdPerDay)
204
+ errors.push({ path: '/agent/budgetUsdPerSession', msg: 'a session budget above the daily budget' });
205
+ if (s.entry.ageHours.max !== null && s.entry.ageHours.max < s.entry.ageHours.min)
206
+ errors.push({ path: '/entry/ageHours', msg: 'max is below min' });
207
+ if (s.entry.triggers.length === 0)
208
+ errors.push({ path: '/entry/triggers', msg: 'at least one trigger (use {"kind":"screen"} for "every screen match")' });
209
+ for (const [i, t] of s.entry.triggers.entries())
210
+ if (t.kind === 'narrative' || t.kind === 'wallet')
211
+ warnings.push({ path: `/entry/triggers/${i}`, msg: `${t.kind} triggers need the live ${t.kind} feed; replays and backtests treat them as not firing` });
212
+ if (s.bankroll.source === 'desk')
213
+ warnings.push({ path: '/bankroll/source', msg: 'desk reads the paper desk book during a run; backtests use startUsd' });
214
+ if (s.entry.safety.larp)
215
+ warnings.push({ path: '/entry/safety/larp', msg: 'the LARP gate needs the live web (live data runs only); replays and backtests do not evaluate it' });
216
+ if (s.mode === 'live')
217
+ warnings.push({ path: '/mode', msg: 'mode live does not run: dexterm runs strategies on paper only (it holds no key and places no live order); live trading happens in the Telegram trading bot of your choice' });
218
+ if (errors.length)
219
+ return { ok: false, errors, warnings };
220
+ const resolved = { ...s, exits: { default: s.exits.default, profiles } };
221
+ return { ok: true, strategy: { ...resolved, hash: strategyHash(resolved) }, warnings };
222
+ }
223
+ /** Stable content hash (sorted keys) written into every audit line so a decision names its exact rules. */
224
+ export function strategyHash(s) {
225
+ return createHash('sha256').update(canonical(s)).digest('hex').slice(0, 16);
226
+ }
227
+ export function canonical(v) {
228
+ if (v === null || typeof v !== 'object')
229
+ return JSON.stringify(v);
230
+ if (Array.isArray(v))
231
+ return `[${v.map(canonical).join(',')}]`;
232
+ const o = v;
233
+ return `{${Object.keys(o).filter((k) => o[k] !== undefined && k !== 'hash').sort().map((k) => `${JSON.stringify(k)}:${canonical(o[k])}`).join(',')}}`;
234
+ }
235
+ export function loadStrategyFile(path, library) {
236
+ let doc;
237
+ try {
238
+ doc = JSON.parse(readFileSync(path, 'utf8'));
239
+ }
240
+ catch (e) {
241
+ return { ok: false, errors: [{ path: '', msg: `not JSON: ${e.message}` }], warnings: [] };
242
+ }
243
+ return validateStrategy(doc, library);
244
+ }
245
+ /** The shipped presets (assets/autotrade/strategies). */
246
+ export function presetPath(id) {
247
+ return new URL(`../assets/strategies/${id}.json`, import.meta.url);
248
+ }
249
+ export function loadPreset(id) {
250
+ const r = loadStrategyFile(presetPath(id));
251
+ if (!r.ok)
252
+ throw new Error(`preset ${id} is invalid: ${r.errors.map(fmtIssue).join('; ')}`);
253
+ return r.strategy;
254
+ }
255
+ export const fmtIssue = (i) => `${i.path || '/'}: ${i.msg}`;
256
+ export const round = (x, dp = 2) => Math.round(x * 10 ** dp) / 10 ** dp;