dexterm-cli 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/dist/packages/agents/src/schema.js +22 -1
  2. package/dist/packages/agents/src/spectator.js +3 -1
  3. package/dist/packages/agents/src/story.js +11 -2
  4. package/dist/packages/agents/src/tiers.js +36 -2
  5. package/dist/packages/agents/src/tools.js +35 -2
  6. package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
  7. package/dist/packages/autotrade/assets/limits.schema.json +569 -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/examples/executor/README.md +70 -0
  20. package/dist/packages/autotrade/examples/executor/jsonl-tail-executor.mjs +57 -0
  21. package/dist/packages/autotrade/examples/executor/kit.mjs +144 -0
  22. package/dist/packages/autotrade/examples/executor/signal-cmd-executor.mjs +25 -0
  23. package/dist/packages/autotrade/examples/executor/telegram-bot-recipe.md +47 -0
  24. package/dist/packages/autotrade/examples/executor/webhook-executor.mjs +47 -0
  25. package/dist/packages/autotrade/src/account.js +74 -0
  26. package/dist/packages/autotrade/src/backtest.js +358 -0
  27. package/dist/packages/autotrade/src/basis-text.js +32 -0
  28. package/dist/packages/autotrade/src/chat/approvals.js +484 -0
  29. package/dist/packages/autotrade/src/chat/audit.js +140 -0
  30. package/dist/packages/autotrade/src/chat/config.js +95 -0
  31. package/dist/packages/autotrade/src/chat/connector.js +24 -0
  32. package/dist/packages/autotrade/src/chat/control.js +104 -0
  33. package/dist/packages/autotrade/src/chat/fake.js +100 -0
  34. package/dist/packages/autotrade/src/chat/format.js +71 -0
  35. package/dist/packages/autotrade/src/chat/hub.js +187 -0
  36. package/dist/packages/autotrade/src/chat/index.js +18 -0
  37. package/dist/packages/autotrade/src/chat/notify.js +302 -0
  38. package/dist/packages/autotrade/src/chat/pairing.js +46 -0
  39. package/dist/packages/autotrade/src/chat/redact.js +48 -0
  40. package/dist/packages/autotrade/src/chat/secrets.js +106 -0
  41. package/dist/packages/autotrade/src/chat/service.js +251 -0
  42. package/dist/packages/autotrade/src/chat/telegram.js +290 -0
  43. package/dist/packages/autotrade/src/desk-executor.js +188 -0
  44. package/dist/packages/autotrade/src/engine.js +401 -0
  45. package/dist/packages/autotrade/src/execution-port.js +97 -0
  46. package/dist/packages/autotrade/src/executor-health.js +217 -0
  47. package/dist/packages/autotrade/src/fleet-config.js +169 -0
  48. package/dist/packages/autotrade/src/fleet-control.js +276 -0
  49. package/dist/packages/autotrade/src/fleet.js +642 -0
  50. package/dist/packages/autotrade/src/forward.js +355 -0
  51. package/dist/packages/autotrade/src/index.js +72 -0
  52. package/dist/packages/autotrade/src/journey.js +477 -0
  53. package/dist/packages/autotrade/src/ledger.js +108 -0
  54. package/dist/packages/autotrade/src/limits.js +682 -0
  55. package/dist/packages/autotrade/src/live-source.js +182 -0
  56. package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
  57. package/dist/packages/autotrade/src/monitor.js +190 -0
  58. package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
  59. package/dist/packages/autotrade/src/outbox.js +394 -0
  60. package/dist/packages/autotrade/src/paper-accounts.js +72 -0
  61. package/dist/packages/autotrade/src/paper-limits.js +87 -0
  62. package/dist/packages/autotrade/src/real-mode.js +688 -0
  63. package/dist/packages/autotrade/src/receipts.js +330 -0
  64. package/dist/packages/autotrade/src/replay.js +198 -0
  65. package/dist/packages/autotrade/src/report.js +168 -0
  66. package/dist/packages/autotrade/src/runner-config.js +99 -0
  67. package/dist/packages/autotrade/src/runner.js +923 -0
  68. package/dist/packages/autotrade/src/safety.js +243 -0
  69. package/dist/packages/autotrade/src/service-guard.js +125 -0
  70. package/dist/packages/autotrade/src/service-install.js +213 -0
  71. package/dist/packages/autotrade/src/service-recover.js +232 -0
  72. package/dist/packages/autotrade/src/service.js +289 -0
  73. package/dist/packages/autotrade/src/session.js +269 -0
  74. package/dist/packages/autotrade/src/sessions.js +503 -0
  75. package/dist/packages/autotrade/src/signal-channels.js +312 -0
  76. package/dist/packages/autotrade/src/signal.js +196 -0
  77. package/dist/packages/autotrade/src/store.js +250 -0
  78. package/dist/packages/autotrade/src/strategy.js +258 -0
  79. package/dist/packages/autotrade/src/trade-ledger.js +241 -0
  80. package/dist/packages/autotrade/src/yolo.js +140 -0
  81. package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
  82. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
  83. package/dist/packages/cli/src/ai-cmd.js +412 -0
  84. package/dist/packages/cli/src/analyst/backend.js +83 -15
  85. package/dist/packages/cli/src/analyst/codex.js +15 -9
  86. package/dist/packages/cli/src/analyst/ollama.js +263 -0
  87. package/dist/packages/cli/src/analyst/session.js +5 -2
  88. package/dist/packages/cli/src/app-help-gen.js +56 -8
  89. package/dist/packages/cli/src/autotrade-cmd/audit.js +74 -0
  90. package/dist/packages/cli/src/autotrade-cmd/backtest.js +216 -0
  91. package/dist/packages/cli/src/autotrade-cmd/executor.js +91 -0
  92. package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
  93. package/dist/packages/cli/src/autotrade-cmd/fleet.js +401 -0
  94. package/dist/packages/cli/src/autotrade-cmd/forward.js +283 -0
  95. package/dist/packages/cli/src/autotrade-cmd/journey.js +208 -0
  96. package/dist/packages/cli/src/autotrade-cmd/limits.js +393 -0
  97. package/dist/packages/cli/src/autotrade-cmd/receipt.js +63 -0
  98. package/dist/packages/cli/src/autotrade-cmd/report.js +79 -0
  99. package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
  100. package/dist/packages/cli/src/autotrade-cmd/run.js +657 -0
  101. package/dist/packages/cli/src/autotrade-cmd/service.js +873 -0
  102. package/dist/packages/cli/src/autotrade-cmd/signal.js +824 -0
  103. package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
  104. package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
  105. package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
  106. package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
  107. package/dist/packages/cli/src/autotrade-cmd.js +214 -0
  108. package/dist/packages/cli/src/display.js +156 -17
  109. package/dist/packages/cli/src/doctor.js +62 -3
  110. package/dist/packages/cli/src/export.js +10 -2
  111. package/dist/packages/cli/src/index.js +1 -1
  112. package/dist/packages/cli/src/ledger-cmd.js +290 -0
  113. package/dist/packages/cli/src/live/chart-fills.js +46 -0
  114. package/dist/packages/cli/src/live/index.js +23 -89
  115. package/dist/packages/cli/src/live/screens/token-live.js +9 -6
  116. package/dist/packages/cli/src/live/trade.js +12 -457
  117. package/dist/packages/cli/src/live/watchlist.js +1 -1
  118. package/dist/packages/cli/src/main.js +147 -190
  119. package/dist/packages/cli/src/mcp-cmd.js +7 -1
  120. package/dist/packages/cli/src/plan-cmd.js +1 -1
  121. package/dist/packages/cli/src/pnl-app.js +31 -21
  122. package/dist/packages/cli/src/pnl-card.js +162 -21
  123. package/dist/packages/cli/src/popup.js +22 -6
  124. package/dist/packages/cli/src/record-cmd.js +141 -20
  125. package/dist/packages/cli/src/screens/agent-hud.js +34 -2
  126. package/dist/packages/cli/src/screens/analyst-session.js +7 -4
  127. package/dist/packages/cli/src/screens/analyst.js +3 -2
  128. package/dist/packages/cli/src/screens/autotrade-journey.js +134 -0
  129. package/dist/packages/cli/src/screens/autotrade-keys.js +182 -0
  130. package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
  131. package/dist/packages/cli/src/screens/autotrade-screen.js +776 -0
  132. package/dist/packages/cli/src/screens/autotrade-ui.js +650 -0
  133. package/dist/packages/cli/src/screens/autotrade-worker.js +91 -0
  134. package/dist/packages/cli/src/screens/brain.js +37 -11
  135. package/dist/packages/cli/src/screens/overlay.js +5 -2
  136. package/dist/packages/cli/src/screens/plans.js +5 -17
  137. package/dist/packages/cli/src/screens/pnl-chip.js +9 -8
  138. package/dist/packages/cli/src/screens/positions.js +6 -28
  139. package/dist/packages/cli/src/screens/ticket.js +38 -205
  140. package/dist/packages/cli/src/screens/token.js +3 -3
  141. package/dist/packages/cli/src/state.js +25 -19
  142. package/dist/packages/cli/src/telegram-cmd.js +454 -0
  143. package/dist/packages/cli/src/trade.js +3 -128
  144. package/dist/packages/cli/src/version.js +1 -1
  145. package/dist/packages/cli/src/wallet-cmd.js +39 -340
  146. package/dist/packages/cli/src/wallet-command.js +8 -11
  147. package/dist/packages/cli/src/widgets.js +0 -12
  148. package/dist/packages/custom/src/config.js +20 -2
  149. package/dist/packages/custom/src/schema.js +35 -10
  150. package/dist/packages/custom/src/workspace.js +3 -3
  151. package/dist/packages/data/src/basis.js +191 -0
  152. package/dist/packages/data/src/detail.js +53 -2
  153. package/dist/packages/data/src/index.js +0 -14
  154. package/dist/packages/data/src/store.js +14 -5
  155. package/dist/packages/data/src/trade/agent/paper-desk.js +228 -16
  156. package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
  157. package/dist/packages/data/src/trade/bridge.js +4 -102
  158. package/dist/packages/data/src/trade/ledger/export.js +101 -0
  159. package/dist/packages/data/src/trade/ledger/file.js +135 -0
  160. package/dist/packages/data/src/trade/ledger/fold.js +191 -0
  161. package/dist/packages/data/src/trade/ledger/index.js +9 -0
  162. package/dist/packages/data/src/trade/ledger/schema.js +83 -0
  163. package/dist/packages/data/src/trade/ledger/sources.js +307 -0
  164. package/dist/packages/data/src/trade/ledger/stats.js +123 -0
  165. package/dist/packages/data/src/trade/ledger/tags.js +116 -0
  166. package/dist/packages/data/src/trade/ledger/view.js +51 -0
  167. package/dist/packages/data/src/trade/plans/index.js +0 -1
  168. package/dist/packages/data/src/trade/plans/service.js +3 -27
  169. package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
  170. package/dist/packages/mcp/assets/kb/app-help/autotrade-executor-kit.md +44 -0
  171. package/dist/packages/mcp/assets/kb/app-help/autotrade-journey.md +46 -0
  172. package/dist/packages/mcp/assets/kb/app-help/autotrade-limits.md +48 -0
  173. package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
  174. package/dist/packages/mcp/assets/kb/app-help/autotrade-real-signal.md +45 -0
  175. package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
  176. package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
  177. package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
  178. package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
  179. package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
  180. package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
  181. package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
  182. package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
  183. package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
  184. package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
  185. package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
  186. package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +229 -0
  187. package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
  188. package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
  189. package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
  190. package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
  191. package/dist/packages/mcp/assets/kb/app-help/screen-token.md +3 -0
  192. package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
  193. package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
  194. package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
  195. package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
  196. package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
  197. package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +4 -1
  198. package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
  199. package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
  200. package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
  201. package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
  202. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
  203. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
  204. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +51 -0
  205. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
  206. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
  207. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
  208. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
  209. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +38 -0
  210. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
  211. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
  212. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
  213. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
  214. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
  215. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
  216. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +37 -0
  217. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
  218. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-always-on-service.md +57 -0
  219. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
  220. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
  221. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
  222. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
  223. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
  224. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
  225. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +50 -0
  226. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
  227. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
  228. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
  229. package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
  230. package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
  231. package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
  232. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-1-ask-for-a-strategy.md +27 -0
  233. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-2-backtest-it-held-out-split.md +31 -0
  234. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-3-forward-test-it-on-live-data.md +44 -0
  235. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-4-promote-it-to-always-on-paper.md +36 -0
  236. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-5-real-only-if-you-choose.md +36 -0
  237. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-ladder.md +39 -0
  238. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-report.md +31 -0
  239. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-what-the-analyst-knows.md +30 -0
  240. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat.md +24 -0
  241. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
  242. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +1 -1
  243. package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
  244. package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
  245. package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
  246. package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +8 -6
  247. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
  248. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
  249. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
  250. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
  251. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
  252. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
  253. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
  254. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
  255. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
  256. package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
  257. package/dist/packages/mcp/assets/kb/docs/docs-going-real-executor-health.md +38 -0
  258. package/dist/packages/mcp/assets/kb/docs/docs-going-real-paper-vs-real.md +33 -0
  259. package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-digest.md +36 -0
  260. package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-executor-kit.md +48 -0
  261. package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-whole-path.md +37 -0
  262. package/dist/packages/mcp/assets/kb/docs/docs-going-real-what-dexterm-does-and-never-does.md +44 -0
  263. package/dist/packages/mcp/assets/kb/docs/docs-going-real-your-limits.md +77 -0
  264. package/dist/packages/mcp/assets/kb/docs/docs-going-real.md +24 -0
  265. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
  266. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-real-mode-your-strategy-s-trades-to-your.md +31 -0
  267. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
  268. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
  269. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +31 -0
  270. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
  271. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
  272. package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
  273. package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
  274. package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
  275. package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
  276. package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
  277. package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
  278. package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
  279. package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
  280. package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
  281. package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
  282. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
  283. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
  284. package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
  285. package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
  286. package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
  287. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
  288. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
  289. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +3 -4
  290. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
  291. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +6 -7
  292. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
  293. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
  294. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
  295. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
  296. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
  297. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
  298. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
  299. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
  300. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +2 -1
  301. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-receipts-what-your-executor-did.md +42 -0
  302. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-safety.md +34 -0
  303. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-set-it-up.md +47 -0
  304. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-channels.md +42 -0
  305. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-signal.md +48 -0
  306. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-is.md +35 -0
  307. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-will-not-do.md +34 -0
  308. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-where-you-see-it.md +34 -0
  309. package/dist/packages/mcp/assets/kb/docs/docs-real-mode.md +27 -0
  310. package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
  311. package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
  312. package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
  313. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +1 -1
  314. package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
  315. package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
  316. package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
  317. package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
  318. package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
  319. package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
  320. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
  321. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
  322. package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
  323. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
  324. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
  325. package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
  326. package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
  327. package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
  328. package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
  329. package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
  330. package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
  331. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
  332. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
  333. package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
  334. package/dist/packages/mcp/assets/kb/entries/autotrade.md +40 -0
  335. package/dist/packages/mcp/src/app-help.js +1 -1
  336. package/dist/packages/mcp/src/autotrade-tools.js +1107 -0
  337. package/dist/packages/mcp/src/backtest-requests.js +71 -0
  338. package/dist/packages/mcp/src/engine/config.js +159 -0
  339. package/dist/packages/mcp/src/engine/index.js +10 -0
  340. package/dist/packages/mcp/src/engine/loop.js +101 -0
  341. package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
  342. package/dist/packages/mcp/src/engine/ollama.js +258 -0
  343. package/dist/packages/mcp/src/engine/probe.js +110 -0
  344. package/dist/packages/mcp/src/engine/reason.js +35 -0
  345. package/dist/packages/mcp/src/engine/restrictions.js +111 -0
  346. package/dist/packages/mcp/src/index.js +7 -1
  347. package/dist/packages/mcp/src/ledger-tools.js +494 -0
  348. package/dist/packages/mcp/src/paper-agent.js +2 -0
  349. package/dist/packages/mcp/src/server.js +138 -19
  350. package/dist/packages/packs/src/backtest.js +3 -1
  351. package/dist/packages/popup/src/pages/chart-lib.js +134 -21
  352. package/dist/packages/popup/src/pages/chart.css +9 -0
  353. package/dist/packages/popup/src/pages/chart.html +3 -1
  354. package/dist/packages/popup/src/pages/chart.js +104 -7
  355. package/dist/packages/popup/src/protocol.js +2 -0
  356. package/dist/packages/popup/src/server.js +4 -0
  357. package/dist/packages/recorder/src/index.js +2 -0
  358. package/dist/packages/recorder/src/recorder.js +63 -6
  359. package/dist/packages/recorder/src/retention.js +100 -0
  360. package/dist/packages/recorder/src/schema.js +34 -1
  361. package/dist/packages/recorder/src/ticks.js +237 -0
  362. package/dist/packages/tui/src/index.js +1 -0
  363. package/dist/packages/tui/src/keymap.js +28 -1
  364. package/dist/packages/tui/src/markers.js +219 -0
  365. package/dist/packages/tui/src/widgets.js +149 -2
  366. package/package.json +2 -1
  367. package/dist/packages/data/src/trade/caps.js +0 -323
  368. package/dist/packages/data/src/trade/executor.js +0 -933
  369. package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
  370. package/dist/packages/data/src/trade/rpc-exec.js +0 -229
  371. package/dist/packages/data/src/trade/signer/client.js +0 -197
  372. package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
  373. package/dist/packages/data/src/trade/signer/host.js +0 -97
  374. package/dist/packages/data/src/trade/signer/main.js +0 -121
  375. package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
  376. package/dist/packages/data/src/trade/signer/protocol.js +0 -92
  377. package/dist/packages/data/src/trade/signer/service.js +0 -400
  378. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
  379. package/dist/packages/data/src/trade/sol-usd.js +0 -86
  380. package/dist/packages/data/src/trade/swap-build.js +0 -108
  381. package/dist/packages/data/src/trade/token-delta.js +0 -201
  382. package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
  383. package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
  384. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
  385. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
  386. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
  387. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
  388. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
  389. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
  390. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
  391. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
  392. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
  393. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
  394. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
  395. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
@@ -0,0 +1,657 @@
1
+ // `dexterm autotrade run <id|file> --paper` (task-1505 AT-3, AUTOTRADE-SPEC §4). PAPER ONLY.
2
+ //
3
+ // Headless, like `dexterm watch` and `dexterm record run`, one per machine (run.lock). Live: the keyless data layer's
4
+ // Store feeds MarketTicks (the screener pool every 10 s, candidate and held coins every 5 s). `--replay FILE` paces a
5
+ // recorded tape instead (no network for market data). Each candidate gets one session on the user's own `claude`
6
+ // (runner.json picks the account), clamped by the strategy; orders fill on the simulated paper book (the paper desk
7
+ // executor arrives with the monitor card). Every step goes to $DEXTERM_HOME/autotrade/runs/<runId>.jsonl, and the
8
+ // rendezvous $DEXTERM_HOME/autotrade/runner-status.json is rewritten every 10 s for the TUI's runner view.
9
+ //
10
+ // Nothing here arms a mandate, signs, holds a key or touches a wallet. task-1571: there is no `--live`: dexterm places
11
+ // no live order. Live trading happens in the Telegram trading bot of the user's choice.
12
+ //
13
+ // task-1645: `--real` runs a strategy armed for real mode (`dexterm autotrade signal arm`, on the user's terminal) on
14
+ // LIVE data only: the same engine as paper, and every fill first goes out as a LIVE TRADE FIRE signal (outbox +
15
+ // the channels of connectors.json `signals`, the Telegram copy through the user's bot). Never on a replay (no stale
16
+ // price may ever reach an executor), never with chat approvals (a tap never authorises a real signal).
17
+ //
18
+ // Telegram (task-1527): when `dexterm telegram setup` stored a token and a chat is paired, the user's own bot starts
19
+ // with the run (telegram-host.ts): notify, control through the same control file, and approve-to-trade on paper (a
20
+ // strategy with `entry.decide: "agent+approve"`, or approvals on). --no-telegram keeps it off; an agent+approve
21
+ // strategy then refuses to start, because no entry could ever be approved.
22
+ //
23
+ // Safety (task-1509, @dexterm/autotrade safety.ts): every step re-reads the panic file and the control file
24
+ // (`dexterm autotrade stop|flatten`, the Q screen's `!` / `f` / `p`); a run refuses to start while the panic file is
25
+ // engaged (resume is `dexterm wallet resume`, a keypress on a real terminal); entries pause while the heartbeat cannot
26
+ // be written. After a stop or flatten the runner keeps running the mechanical exits and ends once nothing is held.
27
+ import { appendFileSync } from 'node:fs';
28
+ import { join } from 'node:path';
29
+ import { dextermHome, loadConfig } from '../../../custom/src/index.js';
30
+ import { pickBasis } from '../../../data/src/basis.js';
31
+ import { KillSwitch, createDataLayer, defaultDbPath, resolveRpcUrl } from '../../../data/src/index.js';
32
+ import { HEARTBEAT_MS, LiveSource, badgeOf, RUNNER_RENDEZVOUS_SCHEMA, RunnerSafety, SessionGate, acquireRunLock, clearControl, clearStaleControl, controlPath, claudeSessionFactory, effectiveCaps, fileLedger, loadRunnerConfig, loadTape, newRunId, prepareAccount, releaseRunLock, renderDossierRecord, runAutotrade, runnerRendezvousPath, runnerSystemPrompt, simExecutor, stubSession, writeJsonAtomic, writeRendezvous, RUN_SUMMARY_SCHEMA, builtinTapePath, } from '../../../autotrade/src/index.js';
33
+ import { assertAutotradeAllowed, ollamaSessionFactory } from '../../../autotrade/src/ollama-sessions.js';
34
+ import { Outbox, RealSignalExecutor, channelsFromConfig, enabledChannels, loadSignalsConfig, openRealRun, realModeCheck } from '../../../autotrade/src/index.js';
35
+ import { PaperLimitsExecutor, effectiveLimits, loadLimits } from '../../../autotrade/src/index.js';
36
+ import { digestDue, digestLines, hmacStore, limitsSummary, loadDigestState, saveDigestState, startExecutorListener } from '../../../autotrade/src/index.js';
37
+ import { digestFor } from "./report.js";
38
+ import { parseArgs, resolveStrategyArg } from "./strategies.js";
39
+ import { startTelegramHost } from "./telegram-host.js";
40
+ const BOOL = ['paper', 'real', 'live', 'stub', 'verbose', 'quiet', 'raw', 'no-telegram'];
41
+ export const RUN_USAGE = 'dexterm autotrade run <id|file> --paper|--real [--replay TAPE [--ms-per-tick 2000]] [--engine claude|ollama] [--model M] [--max-minutes N] [--sol-usd N] [--stub] [--raw] [--quiet] [--no-telegram]';
42
+ /** task-1571: what `--live` says. dexterm holds no key and places no live order. */
43
+ export const NO_LIVE = 'dexterm autotrade run: there is no --live: dexterm runs strategies on paper only and never holds a key or signs; live trading happens in the Telegram trading bot of your choice (your own bot gets the alerts: dexterm telegram setup)';
44
+ function num(v, dflt) {
45
+ const n = typeof v === 'string' ? Number(v) : NaN;
46
+ return Number.isFinite(n) && n > 0 ? n : dflt;
47
+ }
48
+ export async function runCmd(argv, io, over = {}) {
49
+ // task-1529: `--all` runs every strategy of autotrade.json at once (fleet.ts).
50
+ if (argv.includes('--all'))
51
+ return (await import("./fleet.js")).fleetRunCmd(argv, io, { killSwitch: over.killSwitch });
52
+ const { pos, opts } = parseArgs(argv, BOOL);
53
+ if (opts.live || opts['devnet-rehearsal'] !== undefined || opts['signer-size'] !== undefined) {
54
+ io.stderr.write(`${NO_LIVE}\n`);
55
+ return 2;
56
+ }
57
+ if (!opts.paper && !opts.real) {
58
+ io.stderr.write(`dexterm autotrade run: pass --paper (or --real for a strategy armed for real mode)\nusage: ${RUN_USAGE}\n`);
59
+ return 2;
60
+ }
61
+ if (opts.paper && opts.real) {
62
+ io.stderr.write('dexterm autotrade run: --paper or --real, not both\n');
63
+ return 2;
64
+ }
65
+ const arg = typeof opts.strategy === 'string' ? opts.strategy : pos[0];
66
+ const r = resolveStrategyArg(arg, io);
67
+ if (!r) {
68
+ io.stderr.write(`usage: ${RUN_USAGE}\n`);
69
+ return 2;
70
+ }
71
+ for (const w of r.warnings)
72
+ io.stderr.write(`warning ${w.path} ${w.msg}\n`);
73
+ if (!r.ok) {
74
+ for (const e of r.errors)
75
+ io.stderr.write(`error ${e.path} ${e.msg}\n`);
76
+ return 1;
77
+ }
78
+ // task-1645: --paper on a real strategy runs its paper twin (a forward test: no signal goes out).
79
+ const strategy = opts.paper && r.strategy.mode === 'real' ? { ...r.strategy, mode: 'paper' } : r.strategy;
80
+ if (opts.real && strategy.mode !== 'real') {
81
+ io.stderr.write(`dexterm autotrade run: ${strategy.id} is mode "${strategy.mode}": --real needs a strategy armed for real mode (dexterm autotrade signal arm ${strategy.id}, on your own terminal)\n`);
82
+ return 1;
83
+ }
84
+ if (strategy.mode !== 'paper' && strategy.mode !== 'real') {
85
+ io.stderr.write(`dexterm autotrade run: ${strategy.id} is mode "${strategy.mode}": the runner runs paper only (dexterm holds no key and places no live order); real mode, which sends signals to your own executor, is armed with dexterm autotrade signal arm\n`);
86
+ return 1;
87
+ }
88
+ const realMode = strategy.mode === 'real';
89
+ const home = dextermHome(io.env);
90
+ // task-1537: the run's lines and the agents' messages speak the user's basis (--price/--mcap/env, config, market cap)
91
+ const basis = pickBasis(loadConfig(home).config.display?.priceMode, io.env);
92
+ const kill = over.killSwitch ?? new KillSwitch();
93
+ const panic = kill.engaged();
94
+ if (panic) {
95
+ io.stderr.write(`dexterm autotrade run: the panic switch is engaged (${panic.reason}); nothing starts until you run \`dexterm wallet resume\` in a terminal\n`);
96
+ return 1;
97
+ }
98
+ const cfg = loadRunnerConfig(home);
99
+ if (!cfg.ok) {
100
+ io.stderr.write(`dexterm autotrade run: ${cfg.path} is invalid:\n${cfg.errors.map((e) => ` ${e}`).join('\n')}\n`);
101
+ return 1;
102
+ }
103
+ const config = cfg.config;
104
+ const replay = typeof opts.replay === 'string' ? opts.replay : null;
105
+ // task-1645: real mode's preconditions, before anything starts (armed at this hash, caps, kill off, a channel).
106
+ let real = null;
107
+ if (realMode) {
108
+ if (replay) {
109
+ io.stderr.write('dexterm autotrade run: --real runs on live data only (a replay would send old prices to your executor); replay its paper twin with --paper\n');
110
+ return 2;
111
+ }
112
+ real = await prepareReal(home, strategy, opts, io);
113
+ if ('error' in real) {
114
+ io.stderr.write(`dexterm autotrade run: ${real.error}\n`);
115
+ return 1;
116
+ }
117
+ }
118
+ // task-1543: the engine for the sessions: --engine, else ai.json (surfaces.autotrade / provider); auto = claude.
119
+ let local = null;
120
+ if (!opts.stub) {
121
+ const r = await localEngine(opts, io);
122
+ if ('error' in r) {
123
+ io.stderr.write(`dexterm autotrade run: ${r.error}\n`);
124
+ return r.code;
125
+ }
126
+ local = r.engine === 'ollama' ? r : null;
127
+ }
128
+ const quiet = !!opts.quiet;
129
+ const say = (s) => { if (!quiet)
130
+ io.stdout.write(`${s}\n`); };
131
+ const runId = newRunId(strategy.id);
132
+ const lock = acquireRunLock(home, runId);
133
+ if (!lock.ok) {
134
+ io.stderr.write(`dexterm autotrade run: another runner is active (pid ${lock.holder.pid}, run ${lock.holder.runId}); stop it first\n`);
135
+ if (real && !('error' in real))
136
+ real.stop();
137
+ return 1;
138
+ }
139
+ if (lock.reclaimed)
140
+ say(`reclaimed a stale runner lock (pid ${lock.reclaimed.pid} is gone)`);
141
+ if (!clearStaleControl(home, runId)) {
142
+ io.stderr.write(`dexterm autotrade run: ${controlPath(home)} cannot be read; look at it and remove it, then start again\n`);
143
+ releaseRunLock(home, runId);
144
+ return 1;
145
+ }
146
+ const badge = realMode ? 'REAL (LIVE TRADE FIRE signals to your executor)' : 'PAPER';
147
+ // Telegram: the user's own bot, when it is set up and paired (or the host a /use switch carried over).
148
+ let host = over.telegramHost ?? null;
149
+ const ownHost = !over.telegramHost;
150
+ let liveData = null;
151
+ // The card's SOL/USD: the coin's own SOL-quoted pair on DexScreener at the close (live data only; a replay sends text).
152
+ const solUsdFor = async (mint) => {
153
+ const v = liveData ? (await liveData.tokens([mint])).get(mint) : undefined;
154
+ const p = v?.pair;
155
+ return p && p.quoteMint === 'So11111111111111111111111111111111111111112' && p.priceUsd && p.priceNative ? p.priceUsd / p.priceNative : null;
156
+ };
157
+ if (!host && !opts['no-telegram']) {
158
+ const t = await startTelegramHost({ home, exec: simExecutor(strategy.execution), say, ...over.telegram, solUsdFor });
159
+ if (t.ok)
160
+ host = t.host;
161
+ else if (!t.quiet)
162
+ say(`telegram: off (${t.why})`);
163
+ }
164
+ if (realMode && host && (strategy.entry.decide === 'agent+approve' || host.service.broker.required(strategy))) {
165
+ io.stderr.write(`dexterm autotrade run: approve-to-trade over chat is paper only, so ${strategy.id} cannot take an entry in real mode (entry.decide "agent+approve" or \`dexterm telegram approvals on\`); your executor is the last gate in real mode\n`);
166
+ if (ownHost)
167
+ await host.stop();
168
+ releaseRunLock(home, runId);
169
+ return 1;
170
+ }
171
+ if (strategy.entry.decide === 'agent+approve' && !host) {
172
+ io.stderr.write(`dexterm autotrade run: ${strategy.id} needs a one-tap approval for every entry (entry.decide "agent+approve"), and no paired Telegram chat is connected; set it up with \`dexterm telegram setup\`\n`);
173
+ releaseRunLock(home, runId);
174
+ return 1;
175
+ }
176
+ // task-1654: a paper run obeys the limits only when limits.json has a `paper` scope (a forward test that shows what
177
+ // real mode would have refused or clamped). Its state is the run's own; real mode's files are never touched.
178
+ let paperLimits = null;
179
+ if (!realMode) {
180
+ paperLimits = await preparePaperLimits(home, strategy, opts, io, !!replay);
181
+ if (paperLimits && 'error' in paperLimits) {
182
+ io.stderr.write(`dexterm autotrade run: ${paperLimits.error}\n`);
183
+ if (host && ownHost)
184
+ await host.stop();
185
+ releaseRunLock(home, runId);
186
+ return 1;
187
+ }
188
+ }
189
+ const baseExec = simExecutor(strategy.execution);
190
+ if (host)
191
+ host.attach(runId, strategy, baseExec, solUsdFor);
192
+ // task-1645: the signal executor wraps the paper path (the host's port when the bot is on), so every fill is first a signal.
193
+ let realExec = null;
194
+ let outbox = null;
195
+ let pumpTimer = null;
196
+ let digestTimer = null;
197
+ let listener = null;
198
+ let bookUsd = strategy.bankroll.startUsd;
199
+ let paperExec = null;
200
+ if (paperLimits && !('error' in paperLimits)) {
201
+ paperExec = new PaperLimitsExecutor({ inner: host ? host.port : baseExec, limits: paperLimits.limits, solUsd: paperLimits.solUsd, bookUsd: () => bookUsd, onTrip: say });
202
+ }
203
+ if (real && !('error' in real)) {
204
+ const sc = real.signals;
205
+ const tg = host ? async (lines) => {
206
+ const before = host.service.audit.events.length;
207
+ await host.service.hub.broadcast({ kind: 'text', badge: 'LIVE', lines }, 'trade_fire');
208
+ if (host.service.audit.events.slice(before).some((e) => e.kind === 'out_failed'))
209
+ throw new Error('telegram send failed');
210
+ } : undefined;
211
+ const { channels, defer } = channelsFromConfig(home, sc, { telegram: tg, env: io.env });
212
+ try {
213
+ outbox = Outbox.open(home, { channels, defer });
214
+ }
215
+ catch (e) {
216
+ io.stderr.write(`dexterm autotrade run: ${e.message}\n`);
217
+ if (host && ownHost)
218
+ await host.stop();
219
+ releaseRunLock(home, runId);
220
+ return 1;
221
+ }
222
+ const swept = openRealRun(outbox, runId, strategy, real.check.arming.accountId).swept;
223
+ if (swept.length)
224
+ say(`real mode: sent "halted" (runner-lost) for ${swept.length} earlier run(s) that ended without one`);
225
+ // task-1654: every limit (the effective set for this strategy + account), and a line to the run log and the
226
+ // paired chat when the executor goes silent / comes back or a risk limit trips.
227
+ const notify = (line) => { say(line); if (host)
228
+ void host.service.hub.broadcast({ kind: 'text', badge: 'LIVE', lines: [line] }, 'real_limits').catch(() => { }); };
229
+ realExec = new RealSignalExecutor({ home, inner: host ? host.port : baseExec, outbox, strategy, arming: real.check.arming, caps: real.check.caps, limits: real.check.limits.values, notify, runId, ttlSec: sc.ttlSec, solUsd: real.solUsd, bookUsd: () => bookUsd });
230
+ pumpTimer = setInterval(() => { void outbox.pump().catch(() => { }); }, 1000);
231
+ if (sc.executor.listenPort) {
232
+ const store = hmacStore(home, sc, io.env);
233
+ try {
234
+ listener = await startExecutorListener({ home, port: sc.executor.listenPort, secret: () => { try {
235
+ return store.get();
236
+ }
237
+ catch {
238
+ return null;
239
+ } } });
240
+ say(`real mode: executor listener on 127.0.0.1:${listener.port} (POST /heartbeat, /receipt)`);
241
+ }
242
+ catch (e) {
243
+ say(`real mode: executor listener NOT started (${e.message.slice(0, 80)}); executors can still append to autotrade/real/executor.jsonl`);
244
+ }
245
+ }
246
+ }
247
+ // task-1654: the scheduled digest (opt-in: dexterm autotrade report --schedule daily|weekly), sent by a run with the bot on.
248
+ if (host) {
249
+ const tick = () => {
250
+ const st = loadDigestState(home);
251
+ const due = digestDue(st, Date.now());
252
+ if (!due)
253
+ return;
254
+ saveDigestState(home, { ...st, lastSentDay: localDayNow(), lastSentAt: Date.now() });
255
+ const d = digestFor(home, { period: due, now: Date.now() });
256
+ void host.service.hub.broadcast({ kind: 'text', badge: realMode ? 'LIVE' : 'PAPER', lines: digestLines(d) }, 'report').catch(() => { });
257
+ };
258
+ digestTimer = setInterval(tick, 10 * 60_000);
259
+ digestTimer.unref?.();
260
+ }
261
+ const { ledger, files } = fileLedger(home, runId, host?.onLedger);
262
+ const startedAt = Date.now();
263
+ const gate = new SessionGate(effectiveCaps(strategy, config), config.allowanceGuard);
264
+ let runNow = startedAt;
265
+ const log = (kind, data) => { ledger.add(runNow, kind, data); };
266
+ // The run ledger anchors the chat audit (day file, head, count) at start and end: `dexterm telegram audit --verify`
267
+ // cross-checks these, so a chat audit cut or re-chained after an anchor no longer verifies.
268
+ const anchorChat = (when) => { const h = host?.service.audit.head(); if (h?.file && h.day && h.events > 0)
269
+ log('chat_audit', { when, day: h.day, file: h.file, head: h.head, events: h.events }); };
270
+ anchorChat('start');
271
+ const rawPath = opts.raw ? join(home, 'autotrade', 'runs', `${runId}.stream.jsonl`) : null;
272
+ let sessions;
273
+ if (opts.stub)
274
+ sessions = stubSession();
275
+ else if (local && local.engine === 'ollama') {
276
+ sessions = ollamaSessionFactory({
277
+ client: local.client, model: local.model, numCtx: local.numCtx, restriction: local.level, live: false,
278
+ systemPrompt: (s) => runnerSystemPrompt(s, { live: !replay, basis }),
279
+ gate, log,
280
+ onRaw: rawPath ? (id, line) => appendFileSync(rawPath, `${JSON.stringify({ sessionId: id, line })}\n`, { mode: 0o600 }) : undefined,
281
+ });
282
+ }
283
+ else {
284
+ sessions = claudeSessionFactory({
285
+ bin: typeof opts['claude-bin'] === 'string' ? opts['claude-bin'] : (io.env.DEXTERM_CLAUDE_BIN || 'claude'),
286
+ account: () => prepareAccount(config.account, io.env),
287
+ model: typeof opts.model === 'string' ? opts.model : undefined,
288
+ systemPrompt: (s) => runnerSystemPrompt(s, { live: !replay, basis }),
289
+ gate, log,
290
+ onRaw: rawPath ? (id, line) => appendFileSync(rawPath, `${JSON.stringify({ sessionId: id, line })}\n`, { mode: 0o600 }) : undefined,
291
+ now: () => runNow,
292
+ });
293
+ }
294
+ // The market: a paced tape, or the live keyless data layer.
295
+ let source;
296
+ let tape;
297
+ let live = null;
298
+ let dossier;
299
+ let vet;
300
+ let closeData = () => { };
301
+ if (replay) {
302
+ try {
303
+ tape = loadTape(builtinTapePath(replay));
304
+ }
305
+ catch (e) {
306
+ io.stderr.write(`dexterm autotrade run: ${e.message}\n`);
307
+ releaseRunLock(home, runId);
308
+ return 1;
309
+ }
310
+ }
311
+ else {
312
+ const rpc = resolveRpcUrl({ env: io.env });
313
+ const data = createDataLayer({ dbPath: io.env.DEXTERM_DB_PATH || defaultDbPath(io.env), rpcUrl: rpc.url, rpcFallbackUrl: rpc.fallbackUrl });
314
+ closeData = () => data.close();
315
+ liveData = data.dexscreener;
316
+ live = new LiveSource({ store: data.store, dexscreener: data.dexscreener }, { onLog: (l) => log('source_error', { error: l.slice(0, 300) }) });
317
+ source = { name: 'live keyless data layer (Store + DexScreener tokens/v1 fast lane)', ticks: live.ticks(), startT: startedAt };
318
+ // v1 dossier: the MCP get_dossier assembly (market, holders, security, candles, tape, creator) for the mint.
319
+ const { McpTools } = await import('../../../mcp/src/index.js');
320
+ const mcp = new McpTools(data, () => ({ screen: 'autotrade-runner', actions: { mints: [], feeds: [], sortKeys: [] } }));
321
+ dossier = async (k, h, matched, b) => {
322
+ let rec;
323
+ try {
324
+ rec = await mcp.call('get_dossier', { mint: k.mint, interval: '5m' });
325
+ }
326
+ catch (e) {
327
+ rec = { availability: { present: [], missing: [`dossier: ${e.message}`], knownUnavailable: [] } };
328
+ }
329
+ return renderDossierRecord(rec, { mint: k.mint, symbol: k.symbol, matched }, k, h, b, basis);
330
+ };
331
+ // task-1532: the strategy's LARP gate (entry.safety.larp) on live data, once per coin, before it becomes a candidate.
332
+ const larp = strategy.entry.safety.larp;
333
+ if (larp)
334
+ vet = larpVet(larp, (mint) => mcp.call('larp_check', { mint }));
335
+ }
336
+ // Heartbeat + rendezvous: on every state change (throttled to 1 s) and every 10 s regardless.
337
+ let last = null;
338
+ let lastWrite = 0;
339
+ // Dead-man: the time of the last heartbeat that was written OK; entries pause when it goes stale (a full disk,
340
+ // a removed directory), because the TUI and the monitor would see this runner as dead.
341
+ let lastBeatOk = startedAt;
342
+ let beatFailing = false;
343
+ const writeState = (force = false) => {
344
+ if (!last)
345
+ return;
346
+ const now = Date.now();
347
+ if (!force && now - lastWrite < 1000)
348
+ return;
349
+ lastWrite = now;
350
+ const { t: _t, ...body } = last;
351
+ try {
352
+ writeRendezvous(home, { schema: RUNNER_RENDEZVOUS_SCHEMA, pid: process.pid, mode: realMode ? 'real' : 'paper', runId, startedAt, heartbeatAt: now, ledgerPath: files.ledger, ...body });
353
+ writeJsonAtomic(files.summary, { schema: RUN_SUMMARY_SCHEMA, runId, strategy: body.strategy, startedAt, updatedAt: now, state: body.state, book: body.book, spend: body.spend, events: body.events, ledgerHead: body.ledgerHead, source: replay ? `replay ${replay}` : 'live' });
354
+ lastBeatOk = now;
355
+ if (beatFailing) {
356
+ beatFailing = false;
357
+ log('heartbeat_ok', {});
358
+ }
359
+ }
360
+ catch (e) {
361
+ if (!beatFailing) {
362
+ beatFailing = true;
363
+ log('heartbeat_failed', { error: e.message.slice(0, 200) });
364
+ }
365
+ }
366
+ };
367
+ const beat = setInterval(() => writeState(true), HEARTBEAT_MS);
368
+ const maxMin = num(opts['max-minutes'], 0);
369
+ const stopAt = maxMin ? startedAt + maxMin * 60_000 : Infinity;
370
+ let stopping = false;
371
+ const stop = (why) => { if (stopping)
372
+ return; stopping = true; log('stop_requested', { why }); say(`stopping (${why}): open sessions close, the paper book is kept in the ledger`); live?.stop(); };
373
+ const onSig = () => stop('signal');
374
+ process.once('SIGINT', onSig);
375
+ process.once('SIGTERM', onSig);
376
+ // The heartbeat goes stale only while writes fail (a slow first fetch before any state is not a failure).
377
+ const safety = new RunnerSafety({ home, runId, killSwitch: kill, heartbeatAgeMs: () => (beatFailing ? Date.now() - lastBeatOk : 0) });
378
+ if (strategy.highRisk)
379
+ say(`⚠ HIGH RISK strategy (${strategy.id}): one position at a time, early entries, most of them lose. Paper only; not financial advice.`);
380
+ say(`dexterm autotrade run · ${badge} · strategy ${strategy.id} v${strategy.version} (${strategy.hash}) · ${replay ? `replay ${replay}` : 'live keyless data'} · sessions ${opts.stub ? 'stub' : local ? `ollama (local ${local.model}, profile ${local.level}, $0)` : `claude (${config.account.kind}${typeof opts.model === 'string' ? `, ${opts.model}` : ''})`}`);
381
+ say(`run ${runId} · ledger ${files.ledger} · rendezvous ${runnerRendezvousPath(home)}`);
382
+ if (realExec && real && !('error' in real)) {
383
+ say(`REAL MODE · account ${real.check.arming.accountId} · limits ${limitsSummary(real.check.limits.values)} · channels ${enabledChannels(real.signals).join(', ')} · ttl ${Math.min(real.signals.ttlSec, real.check.limits.values.signalTtlSec ?? Infinity)} s · kill: dexterm autotrade signal kill · limits: dexterm autotrade limits show`);
384
+ const carried = realExec.inherited().length;
385
+ const elsewhere = realExec.heldElsewhere();
386
+ if (carried || elsewhere.length)
387
+ say(`REAL HOLDINGS · ${carried} carried from an earlier run of ${strategy.id} (its exits keep running)${elsewhere.length ? ` · ${elsewhere.length} held by other strategies (${elsewhere.map((h) => `${h.symbol} ${h.strategyId}`).join(', ')}): counted against the open cap, not traded here` : ''}${realExec.holdingsSource === 'rebuilt' ? ' · rebuilt from the outbox' : ''} · dexterm autotrade signal held`);
388
+ }
389
+ if (paperExec && paperLimits && !('error' in paperLimits))
390
+ say(`PAPER LIMITS · the paper scope of limits.json applies · ${limitsSummary(paperLimits.limits)} · dexterm autotrade limits show --mode paper`);
391
+ if (host)
392
+ say(`telegram: on (your own bot, ${realMode ? 'LIVE copy of each signal' : badgeOf(host.port.network)}) · approvals ${strategy.entry.decide === 'agent+approve' ? 'required by the strategy' : 'when turned on'} · /status /pause /kill from your paired chat`);
393
+ let switchTo = null;
394
+ let code = 0;
395
+ try {
396
+ const res = await runAutotrade({
397
+ strategy, basis, tape, source, sessions, gate, dossier, vet, verbose: true, onLine: say, ledger, runId,
398
+ ...(realExec ? { executor: realExec, real: realExec, ...(host ? { approvals: host.service.broker } : {}) } : paperExec ? { executor: paperExec, maxHoldMinutes: () => paperExec.maxHoldMinutes(), ...(host ? { approvals: host.service.broker } : {}) } : host ? { executor: host.port, approvals: host.service.broker } : { executor: baseExec }),
399
+ pacing: replay ? { mode: 'paced', msPerTick: num(opts['ms-per-tick'], 2000) } : { mode: 'live' },
400
+ // The panic file (`dexterm wallet panic`), a stop / flatten request and a local stop (signal, --max-minutes) all
401
+ // halt: no new entries, sessions close. A signal or --max-minutes also ends the tick stream at once.
402
+ safety: () => {
403
+ if (Date.now() >= stopAt)
404
+ stop('max-minutes');
405
+ const v = safety.step();
406
+ return stopping && !v.halt ? { ...v, halt: 'runner-stopped', detail: 'a signal or --max-minutes' } : v;
407
+ },
408
+ onState: (st) => { runNow = st.t; last = st; bookUsd = st.book.equityUsd; writeState(st.state === 'stopped'); host?.onState(st); },
409
+ onFocus: (marks) => { live?.setFocus(marks); writeFocus(home, marks); },
410
+ });
411
+ const next = host?.takeSwitch() ?? null;
412
+ if (next && !stopping)
413
+ switchTo = next.id;
414
+ say(`done · ${res.candidates} candidates · ${res.sessions} sessions · $${res.sessionCostUsd.toFixed(4)} model spend · ${res.trades.length} closed, ${res.openAtEnd.length} open · ledger head ${ledger.head()}`);
415
+ }
416
+ catch (e) {
417
+ io.stderr.write(`dexterm autotrade run: ${e.message}\n`);
418
+ code = 1;
419
+ }
420
+ finally {
421
+ clearInterval(beat);
422
+ if (pumpTimer)
423
+ clearInterval(pumpTimer);
424
+ if (digestTimer)
425
+ clearInterval(digestTimer);
426
+ if (listener)
427
+ await listener.close().catch(() => { });
428
+ // The runner always says goodbye: idempotent after a normal end, and the only `halted` when the run threw.
429
+ if (realExec) {
430
+ realExec.sendHalted(code ? 'runner-error' : 'runner-stopped');
431
+ // task-1679 (0.2.0 decision): what is still open when a real run ends (stop, kill, time limit, error) is impossible to miss.
432
+ const open = realExec.openOnAccount();
433
+ if (open.length) {
434
+ const line = `REAL POSITIONS STILL OPEN (${open.length}): ${open.map((h) => `${h.symbol} ${h.mint}${h.strategyId === strategy.id ? '' : ` (${h.strategyId})`}`).join(', ')}`;
435
+ say(line);
436
+ say(' these positions are still open: manage them in your executor. While no real run of the strategy that holds one is running, dexterm sends NO signal for it, sells included. The next real run of that strategy on this account carries it and its exits fire again (the "halted" signal lists them; dexterm autotrade signal held).');
437
+ if (host)
438
+ void host.service.hub.broadcast({ kind: 'text', badge: 'LIVE', lines: [line, 'these positions are still open: manage them in your executor'] }, 'real_limits').catch(() => { });
439
+ }
440
+ }
441
+ if (outbox) {
442
+ try {
443
+ await outbox.pump();
444
+ }
445
+ catch { /* pending signals stay in the outbox; they expire by TTL */ }
446
+ outbox.close();
447
+ }
448
+ real?.stop?.();
449
+ if (paperLimits && !('error' in paperLimits))
450
+ paperLimits.stop();
451
+ process.removeListener('SIGINT', onSig);
452
+ process.removeListener('SIGTERM', onSig);
453
+ anchorChat('end');
454
+ writeState(true);
455
+ writeFocus(home, []);
456
+ live?.stop();
457
+ closeData();
458
+ clearControl(home, runId);
459
+ releaseRunLock(home, runId);
460
+ if (host && !(switchTo && code === 0) && ownHost)
461
+ await host.stop();
462
+ }
463
+ if (host && switchTo && code === 0) {
464
+ // A confirmed /use: the same bot, the next run on the new strategy (it was checked equal-or-tighter).
465
+ say(`telegram: starting the next run on ${switchTo}`);
466
+ const rest = [];
467
+ let dropped = false;
468
+ for (let i = 0; i < argv.length; i++) {
469
+ if (argv[i] === '--strategy') {
470
+ i++;
471
+ continue;
472
+ }
473
+ if (!dropped && argv[i] === arg) {
474
+ dropped = true;
475
+ continue;
476
+ }
477
+ rest.push(argv[i]);
478
+ }
479
+ const c2 = await runCmd([switchTo, ...rest], io, { ...over, telegramHost: host });
480
+ if (ownHost)
481
+ await host.stop();
482
+ return c2;
483
+ }
484
+ return code;
485
+ }
486
+ /** Candidate + held coins for the recorder's tick lane (task-1504 focus file); best effort, never fails the run. */
487
+ function writeFocus(home, marks) {
488
+ void import('../../../recorder/src/index.js').then((m) => {
489
+ try {
490
+ m.writeFocusFile(m.focusFilePath(home), marks.map((x) => ({ mint: x.mint, role: x.role, until: Date.now() + 30 * 60_000 })), Date.now());
491
+ }
492
+ catch { /* the recorder is optional */ }
493
+ }).catch(() => { });
494
+ }
495
+ /**
496
+ * task-1543: resolve the session engine. claude = the original path (null here). ollama = the user's local model,
497
+ * allowed only when the local profile allows autotrade (paper) — checked before the run lock.
498
+ */
499
+ export async function localEngine(opts, io) {
500
+ const eng = await import('../../../mcp/src/engine/index.js');
501
+ const loaded = eng.loadAiConfig(io.env);
502
+ const flag = typeof opts.engine === 'string' ? eng.parseEngineChoice(opts.engine) : null;
503
+ if (typeof opts.engine === 'string' && !flag)
504
+ return { error: `unknown engine ${String(opts.engine).slice(0, 16)} (claude|ollama)`, code: 2 };
505
+ const choice = flag ?? eng.engineChoiceFor('autotrade', loaded.config, io.env);
506
+ if (choice === 'auto' || choice === 'claude')
507
+ return { engine: 'claude' };
508
+ if (choice === 'codex')
509
+ return { error: 'autotrade sessions run on claude or a local ollama model (codex has no session mode); pass --engine claude or --engine ollama', code: 2 };
510
+ const set = eng.ollamaSettings(loaded.config, io.env);
511
+ const client = new eng.OllamaClient({ host: set.host });
512
+ let pulled;
513
+ try {
514
+ pulled = await client.tags();
515
+ }
516
+ catch (e) {
517
+ return { error: `Ollama is not reachable at ${set.host.url} (${e.message.slice(0, 120)}); start it, or run on claude (--engine claude)`, code: 1 };
518
+ }
519
+ const asked = typeof opts.model === 'string' && eng.MODEL_NAME.test(opts.model) && !/^claude/i.test(opts.model) ? opts.model : null;
520
+ const model = asked ?? set.model ?? eng.pickModel(pulled);
521
+ if (!model)
522
+ return { error: `no local model pulled: ollama pull ${eng.SUGGESTED_MODELS[0].name} && dexterm ai model ${eng.SUGGESTED_MODELS[0].name}`, code: 1 };
523
+ if (!pulled.includes(model) && !pulled.includes(`${model}:latest`))
524
+ return { error: `${model} is not pulled: ollama pull ${model}`, code: 1 };
525
+ const { level, note } = eng.effectiveRestriction(loaded.config, model, io.env);
526
+ if (note)
527
+ io.stderr.write(`note: ${note}\n`);
528
+ try {
529
+ assertAutotradeAllowed(level, false);
530
+ }
531
+ catch (e) {
532
+ return { error: e.message, code: 1 };
533
+ }
534
+ const w = eng.remoteHostWarning(set.host);
535
+ if (w)
536
+ io.stderr.write(`warning: ${w}\n`);
537
+ return { engine: 'ollama', client, model, numCtx: Math.min(set.numCtx, 16384), level };
538
+ }
539
+ /**
540
+ * task-1532: the LARP gate. `larp_check` (the MCP tool: links, website, domain age, repo, X account) scores the coin
541
+ * 0-100; above maxScore is refused. A check that fails, times out (20 s) or comes back without a score is "unknown":
542
+ * refused when the strategy says reject (the default for yolo). Evidence, not a verdict: a low score is not a safety check.
543
+ */
544
+ export function larpVet(g, check, timeoutMs = 20_000) {
545
+ return async (k) => {
546
+ let rec = null;
547
+ let why = '';
548
+ // task-1574: the timeout keeps the event loop alive until the race settles (an unref'd timer let a check that never
549
+ // answers leave the vet pending forever when nothing else held the loop; Node 22's test runner cancels on that).
550
+ let timer;
551
+ try {
552
+ rec = await Promise.race([check(k.mint), new Promise((_, rej) => { timer = setTimeout(() => rej(new Error(`no answer in ${timeoutMs / 1000} s`)), timeoutMs); })]);
553
+ if (rec && typeof rec.error === 'string') {
554
+ why = rec.error.slice(0, 120);
555
+ rec = null;
556
+ }
557
+ }
558
+ catch (e) {
559
+ why = e.message.slice(0, 120);
560
+ }
561
+ finally {
562
+ clearTimeout(timer);
563
+ }
564
+ const score = rec && typeof rec.score === 'number' && Number.isFinite(rec.score) ? rec.score : null;
565
+ if (score === null) {
566
+ const detail = `the LARP check could not run${why ? ` (${why})` : ''}`;
567
+ return g.unknown === 'allow' ? { ok: true, note: `${detail}; allowed by the strategy` } : { ok: false, rule: 'larp-unknown', detail };
568
+ }
569
+ const band = typeof rec.band === 'string' ? rec.band : '';
570
+ if (score > g.maxScore)
571
+ return { ok: false, rule: 'larp-score', detail: `LARP score ${score}${band ? ` (${band})` : ''} is above ${g.maxScore}` };
572
+ return { ok: true, note: `LARP score ${score}${band ? ` (${band})` : ''} ≤ ${g.maxScore}` };
573
+ };
574
+ }
575
+ const SOL_MINT = 'So11111111111111111111111111111111111111112';
576
+ /**
577
+ * task-1645: real mode's preconditions (realModeCheck: armed at this hash, kill off, caps, an executor channel) and
578
+ * SOL/USD for sizes in SOL: `--sol-usd N`, else the SOL pair on DexScreener (keyless), refreshed every minute.
579
+ * No SOL price, no run: a signal must state its size in SOL.
580
+ */
581
+ async function prepareReal(home, s, opts, io) {
582
+ const sc = loadSignalsConfig(home);
583
+ if (sc.problems.length)
584
+ io.stderr.write(`warning: signal channels turned off: ${sc.problems.join('; ')}\n`);
585
+ const check = realModeCheck(home, s, enabledChannels(sc.config));
586
+ if (!check.ok)
587
+ return { error: check.why };
588
+ const fixed = typeof opts['sol-usd'] === 'string' ? Number(opts['sol-usd']) : null;
589
+ if (fixed !== null && !(fixed > 1 && fixed < 100_000))
590
+ return { error: '--sol-usd needs the SOL price in USD' };
591
+ if (fixed)
592
+ return { check, signals: sc.config, solUsd: () => fixed, stop: () => { } };
593
+ const { createDataLayer, defaultDbPath } = await import('../../../data/src/index.js');
594
+ const data = createDataLayer({ dbPath: io.env.DEXTERM_DB_PATH || defaultDbPath(io.env) });
595
+ let px = null;
596
+ const refresh = async () => { try {
597
+ const p = (await data.dexscreener.tokens([SOL_MINT])).get(SOL_MINT)?.pair.priceUsd ?? null;
598
+ if (p && p > 1 && p < 100_000)
599
+ px = p;
600
+ }
601
+ catch { /* keep the last price */ } };
602
+ await refresh();
603
+ if (!px) {
604
+ data.close();
605
+ return { error: 'no SOL/USD price (DexScreener did not answer); pass --sol-usd N' };
606
+ }
607
+ const t = setInterval(() => { void refresh(); }, 60_000);
608
+ t.unref?.();
609
+ return { check, signals: sc.config, solUsd: () => px, stop: () => { clearInterval(t); data.close(); } };
610
+ }
611
+ /**
612
+ * task-1654: the `paper` scope of limits.json for a paper run, or null when there is none (paper ignores the limits
613
+ * then, exactly as before). Sizes are in SOL, so it needs SOL/USD: `--sol-usd N`, else DexScreener on live data; a
614
+ * replay needs --sol-usd (today's price is not the tape's). An unreadable or invalid file is only a warning: a
615
+ * paper run moves no money, and the line says the paper scope was NOT applied.
616
+ */
617
+ async function preparePaperLimits(home, s, opts, io, replay) {
618
+ const l = loadLimits(home);
619
+ if (!l.ok) {
620
+ if (!('missing' in l && l.missing))
621
+ io.stderr.write(`warning: paper limits NOT applied: ${l.why}\n`);
622
+ return null;
623
+ }
624
+ const accountId = typeof opts.account === 'string' ? opts.account : '-';
625
+ const e = effectiveLimits(l.doc, { strategyId: s.id, accountId, mode: 'paper' });
626
+ if (!e.applies)
627
+ return null;
628
+ const fixed = typeof opts['sol-usd'] === 'string' ? Number(opts['sol-usd']) : null;
629
+ if (fixed !== null && !(fixed > 1 && fixed < 100_000))
630
+ return { error: '--sol-usd needs the SOL price in USD' };
631
+ if (fixed)
632
+ return { limits: e.values, solUsd: () => fixed, stop: () => { } };
633
+ if (replay)
634
+ return { error: 'limits.json has a paper scope (sizes in SOL): a replay needs the SOL price of its day, --sol-usd N' };
635
+ const { createDataLayer, defaultDbPath } = await import('../../../data/src/index.js');
636
+ const data = createDataLayer({ dbPath: io.env.DEXTERM_DB_PATH || defaultDbPath(io.env) });
637
+ let px = null;
638
+ const refresh = async () => { try {
639
+ const p = (await data.dexscreener.tokens([SOL_MINT])).get(SOL_MINT)?.pair.priceUsd ?? null;
640
+ if (p && p > 1 && p < 100_000)
641
+ px = p;
642
+ }
643
+ catch { /* keep the last price */ } };
644
+ await refresh();
645
+ if (!px) {
646
+ data.close();
647
+ return { error: 'limits.json has a paper scope (sizes in SOL) and there is no SOL/USD price (DexScreener did not answer); pass --sol-usd N' };
648
+ }
649
+ const t = setInterval(() => { void refresh(); }, 60_000);
650
+ t.unref?.();
651
+ return { limits: e.values, solUsd: () => px, stop: () => { clearInterval(t); data.close(); } };
652
+ }
653
+ /** The local calendar day (the digest schedule's key). */
654
+ function localDayNow(t = Date.now()) {
655
+ const d = new Date(t);
656
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
657
+ }