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
@@ -15,6 +15,8 @@
15
15
  const chipEl = $('pnlchip');
16
16
  const imgSlot = $('coin-img-slot');
17
17
  const chartEl = $('chart'), drawCv = $('draw'), dctx = drawCv.getContext('2d');
18
+ // task-1483: the BUY / SELL marker hover card (built with textContent only)
19
+ const tipEl = document.createElement('div'); tipEl.className = 'mtip'; tipEl.hidden = true; chartEl.parentNode.appendChild(tipEl);
18
20
  const STALE_MS = 20_000;
19
21
  const DEFAULT_IND = ['volume', 'tape', 'ema9', 'ema21'];
20
22
  /** Exposed for the headless harness (measure/popup-v2-1325): first paint + setData → repaint latency. */
@@ -26,6 +28,8 @@
26
28
  draw: null, tPending: null, hover: null, drawings: [], lastDataAt: 0, firstPainted: false, fitNext: true,
27
29
  banners: { fallback: null, data: null },
28
30
  display: C.displayOf(null), // task-1366: price | mcap (from the VM's priceMode + supply)
31
+ marks: null, // task-1483: { set, on } — the BUY / SELL markers for the bars on screen (chart-lib chartMarkers)
32
+ mouse: null, // task-1483: crosshair point for the marker hover card
29
33
  };
30
34
 
31
35
  const banner = () => {
@@ -88,7 +92,8 @@
88
92
  function levels() {
89
93
  const vm = S.vm || {}, pos = vm.position || null, out = [];
90
94
  const last = S.bars.length ? S.bars[S.bars.length - 1].close : null;
91
- for (const v of [pos && pos.avgEntryUsd, pos && pos.stopUsd, pos && pos.targetUsd, vm.feed === 'LIVE' ? vm.livePrice : null]) {
95
+ const mk = S.marks && S.marks.on ? S.marks.set.markers.map((m) => m.priceUsd) : []; // task-1483: keep the fills in view
96
+ for (const v of [pos && pos.avgEntryUsd, pos && pos.stopUsd, pos && pos.targetUsd, vm.feed === 'LIVE' ? vm.livePrice : null, ...mk]) {
92
97
  if (typeof v === 'number' && v > 0 && last && v / last < 3 && last / v < 3) out.push(v);
93
98
  }
94
99
  return out;
@@ -160,7 +165,7 @@
160
165
  DT.onTheme(applyChartTheme);
161
166
 
162
167
  // ---------------------------------------------------------------- toolbar (buttons built with textContent only)
163
- const btns = { tf: new Map(), axis: new Map(), styles: new Map(), scales: new Map(), inds: new Map(), draws: new Map() };
168
+ const btns = { tf: new Map(), axis: new Map(), marks: new Map(), styles: new Map(), scales: new Map(), inds: new Map(), draws: new Map() };
164
169
  function mkBtn(group, id, label, title, onClick) {
165
170
  const b = document.createElement('button'); b.type = 'button'; b.className = 'tb'; b.textContent = label; b.title = title;
166
171
  b.addEventListener('click', (e) => { e.preventDefault(); onClick(); });
@@ -170,6 +175,8 @@
170
175
  // task-1472: MCAP | PRICE asks the terminal to switch (and save) the chart axis; the next VM re-labels the page
171
176
  mkBtn('axis', 'mcap', 'MCAP', 'Y axis: market cap (A / y toggles; saved by the terminal)', () => setAxis('mcap'));
172
177
  mkBtn('axis', 'price', 'PRICE', 'Y axis: USD price (A / y toggles; saved by the terminal)', () => setAxis('price'));
178
+ // task-1483: MARKERS on/off asks the terminal to switch (and save) display.chartMarkers; the next VM carries it
179
+ mkBtn('marks', 'on', 'MARKERS', 'BUY / SELL markers of your paper + live fills (k toggles; saved by the terminal)', () => setMarkers(!(S.vm && S.vm.marks && S.vm.marks.on)));
173
180
  mkBtn('styles', 'candles', 'candles', 'candlesticks (c cycles)', () => setStyle('candles', true));
174
181
  mkBtn('styles', 'line', 'line', 'line (c cycles)', () => setStyle('line', true));
175
182
  mkBtn('styles', 'area', 'area', 'area (c cycles)', () => setStyle('area', true));
@@ -189,6 +196,8 @@
189
196
  for (const [iv, b] of btns.tf) { b.classList.toggle('on', iv === S.interval); b.classList.toggle('pending', iv === S.pendingInterval && iv !== S.interval); }
190
197
  const ax = S.vm && S.vm.priceMode === 'mcap' ? 'mcap' : 'price';
191
198
  for (const [k, b] of btns.axis) b.classList.toggle('on', k === ax);
199
+ const mOn = !!(S.vm && S.vm.marks && S.vm.marks.on);
200
+ for (const [, b] of btns.marks) { b.classList.toggle('on', mOn); b.hidden = !(S.vm && S.vm.marks); }
192
201
  for (const [k, b] of btns.styles) b.classList.toggle('on', k === S.style);
193
202
  for (const [k, b] of btns.scales) b.classList.toggle('on', k === S.scale);
194
203
  for (const [k, b] of btns.inds) b.classList.toggle('on', S.ind.has(k));
@@ -207,6 +216,11 @@
207
216
  if (axis !== 'mcap' && axis !== 'price') return;
208
217
  DT.send({ type: 'event', name: 'chart.axis', payload: { axis } });
209
218
  }
219
+ /** task-1483: ask the terminal to show / hide the markers; nothing changes here until its next VM says so. */
220
+ function setMarkers(on) {
221
+ if (typeof on !== 'boolean') return;
222
+ DT.send({ type: 'event', name: 'chart.markers', payload: { on } });
223
+ }
210
224
  function setStyle(st, user) {
211
225
  if (!C.STYLES.includes(st) || st === S.style) return;
212
226
  S.style = st;
@@ -271,9 +285,82 @@
271
285
  const times = new Set(S.bars.map((b) => b.time));
272
286
  const list = [];
273
287
  if (S.ind.has('tape')) list.push(...C.tapeMarkers(vm.trades, periodMs(), times, { up: DT.rgba(T.up, 0.85), down: DT.rgba(T.down, 0.85) }));
274
- if (vm.position && vm.position.fills) list.push(...C.fillMarkers(vm.position.fills, periodMs(), times, { buy: T.accent, sell: T.warn }));
275
288
  list.sort((a, b) => a.time - b.time);
276
289
  markers[S.style].setMarkers(list);
290
+ computeMarks();
291
+ }
292
+
293
+ // ---------------------------------------------------------------- task-1483: BUY / SELL markers of the ledgers' fills
294
+ // Bucketed to THIS page's timeframe by the bars' real times (re-bucketed on every timeframe change), drawn on the
295
+ // overlay canvas so paper (hollow) and live (solid) read apart, re-clustered when zooming out, hover card per marker.
296
+ function computeMarks() {
297
+ const mk = S.vm && S.vm.marks;
298
+ if (!mk) { S.marks = null; return; }
299
+ S.marks = { on: !!mk.on, truncated: mk.truncated | 0, set: C.chartMarkers(mk.fills, periodMs(), S.bars.map((b) => b.t)) };
300
+ }
301
+ const MARK_PX = 6;
302
+ let markHits = [];
303
+ function drawMarks(ts, s, paneH) {
304
+ markHits = [];
305
+ const mk = S.marks;
306
+ if (!mk || !mk.on || !S.bars.length) return;
307
+ const x0 = ts.logicalToCoordinate(0), x1 = ts.logicalToCoordinate(1);
308
+ const spacing = x0 != null && x1 != null ? Math.abs(x1 - x0) : 7;
309
+ // closed round trips: a faint wash entry → exit (green when sold above the entry)
310
+ for (const r of mk.set.trips) {
311
+ const a = C.logicalOf(S.bars, C.chartTime(Math.floor(r.openT / periodMs()) * periodMs()), periodMs() / 1000);
312
+ const b = C.logicalOf(S.bars, C.chartTime(Math.floor(r.closeT / periodMs()) * periodMs()), periodMs() / 1000);
313
+ const xa = ts.logicalToCoordinate(a), xb = ts.logicalToCoordinate(b);
314
+ if (xa == null || xb == null) continue;
315
+ const win = r.entryUsd !== null && r.exitUsd !== null ? r.exitUsd >= r.entryUsd : null;
316
+ dctx.fillStyle = DT.rgba(win === null ? T.muted : win ? T.up : T.down, 0.07);
317
+ dctx.fillRect(Math.min(xa, xb) - spacing / 2, 0, Math.abs(xb - xa) + spacing, paneH);
318
+ }
319
+ const shown = C.clusterMarkers(mk.set.markers, spacing, 14);
320
+ const used = new Map();
321
+ dctx.font = 'bold 10px ui-monospace, Menlo, monospace'; dctx.textAlign = 'center'; dctx.textBaseline = 'middle';
322
+ for (const m of shown) {
323
+ const x = ts.logicalToCoordinate(m.index);
324
+ const bar = S.bars[m.index];
325
+ if (x == null || !bar) continue;
326
+ const buy = m.side === 'buy';
327
+ let y = m.priceUsd !== null ? s.priceToCoordinate(m.priceUsd) : s.priceToCoordinate(buy ? bar.low : bar.high);
328
+ if (y == null) continue;
329
+ if (m.priceUsd === null) y += buy ? 4 : -4; // no recorded price: against the bar, never at a made-up price
330
+ // a second marker on the same bar and side steps away from the bar, never across it
331
+ const k = `${m.index}|${m.side}`; const n = used.get(k) || 0; used.set(k, n + 1);
332
+ if (n) y += (buy ? 1 : -1) * n * (MARK_PX * 1.6 + 12);
333
+ if (y < -20 || y > paneH + 20) continue;
334
+ const g = C.markerShape(m.side, x, y, MARK_PX);
335
+ const col = buy ? T.up : T.down;
336
+ dctx.beginPath(); dctx.moveTo(g.tri[0][0], g.tri[0][1]); dctx.lineTo(g.tri[1][0], g.tri[1][1]); dctx.lineTo(g.tri[2][0], g.tri[2][1]); dctx.closePath();
337
+ // a dark halo first, so a green buy still reads on a green candle (and a red sell on a red one)
338
+ dctx.setLineDash([]); dctx.lineJoin = 'round'; dctx.lineWidth = 4; dctx.strokeStyle = DT.rgba(T.bg, 0.92); dctx.stroke();
339
+ if (m.source === 'live') { dctx.fillStyle = col; dctx.fill(); dctx.lineWidth = 1.2; dctx.strokeStyle = DT.rgba(T.text, 0.95); dctx.stroke(); } // solid + a light edge: never mistaken for a hollow paper mark on a same-colour candle
340
+ else { dctx.fillStyle = DT.rgba(T.bg, 0.85); dctx.fill(); dctx.fillStyle = DT.rgba(col, 0.22); dctx.fill(); dctx.lineWidth = 1.6; dctx.strokeStyle = col; dctx.stroke(); }
341
+ const txt = `${C.markerLetter(m.side)}${m.count > 1 ? '×' + m.count : ''}`;
342
+ dctx.lineWidth = 3; dctx.strokeStyle = DT.rgba(T.bg, 0.92); dctx.strokeText(txt, g.labelX, g.labelY);
343
+ dctx.fillStyle = col; dctx.fillText(txt, g.labelX, g.labelY);
344
+ markHits.push({ m, hit: g.hit, x, y });
345
+ }
346
+ hoverMarks();
347
+ }
348
+ /** The hover card for the marker under the crosshair (time, price, size, source). Text only. */
349
+ function hoverMarks() {
350
+ const p = S.mouse;
351
+ const hit = p ? markHits.find((h) => p.x >= h.hit.x0 && p.x <= h.hit.x1 && p.y >= h.hit.y0 && p.y <= h.hit.y1) : null;
352
+ if (!hit) { if (!tipEl.hidden) tipEl.hidden = true; return; }
353
+ const lines = C.markerTooltip(hit.m, S.display, (ms) => fmtTime(C.chartTime(ms)));
354
+ const key = lines.join('|');
355
+ if (tipEl.dataset.k !== key) {
356
+ tipEl.dataset.k = key;
357
+ while (tipEl.firstChild) tipEl.removeChild(tipEl.firstChild);
358
+ lines.forEach((t, i) => tipEl.appendChild(el('div', i === 0 ? `h ${hit.m.side}` : i === lines.length - 1 ? `src ${hit.m.source}` : null, t)));
359
+ }
360
+ tipEl.hidden = false;
361
+ const w = chartEl.clientWidth;
362
+ const left = hit.x + 14 + 190 > w ? hit.x - 14 - tipEl.offsetWidth : hit.x + 14;
363
+ tipEl.style.left = `${Math.max(4, left)}px`; tipEl.style.top = `${Math.max(4, hit.y - 20)}px`;
277
364
  }
278
365
 
279
366
  function renderOverlays() {
@@ -281,7 +368,8 @@
281
368
  const pos = vm.position || null;
282
369
  if (pos && typeof pos.avgEntryUsd === 'number' && pos.avgEntryUsd > 0) {
283
370
  const pct = typeof pos.unrealizedPct === 'number' ? ` ${C.fmtPct(pos.unrealizedPct, 1)}` : '';
284
- setLine('entry', { price: pos.avgEntryUsd, color: T.warn, lineWidth: 1, lineStyle: L.LineStyle.Solid, axisLabelVisible: true, axisLabelColor: T.warn, axisLabelTextColor: T.bg, title: `ENTRY ${pos.sizeLabel}${pct}` });
371
+ // task-1483: the average entry of the open position is a DASHED line (fills are the triangles)
372
+ setLine('entry', { price: pos.avgEntryUsd, color: pos.kind === 'paper' ? T.warn : T.text, lineWidth: 1, lineStyle: L.LineStyle.LargeDashed, axisLabelVisible: true, axisLabelColor: pos.kind === 'paper' ? T.warn : T.text, axisLabelTextColor: T.bg, title: `AVG ENTRY ${pos.sizeLabel}${pct}` });
285
373
  } else setLine('entry', null);
286
374
  setLine('stop', pos && typeof pos.stopUsd === 'number' && pos.stopUsd > 0 ? { price: pos.stopUsd, color: T.down, lineWidth: 1, lineStyle: L.LineStyle.Dashed, axisLabelVisible: true, title: 'STOP' } : null);
287
375
  setLine('target', pos && typeof pos.targetUsd === 'number' && pos.targetUsd > 0 ? { price: pos.targetUsd, color: T.up, lineWidth: 1, lineStyle: L.LineStyle.Dashed, axisLabelVisible: true, title: 'TARGET' } : null);
@@ -342,8 +430,14 @@
342
430
  r2.appendChild(s);
343
431
  }
344
432
  if (r2.firstChild) legendEl.appendChild(r2);
433
+ // task-1483: which markers are on screen, and what the window left off (never moved onto another bar)
434
+ if (S.marks && S.marks.on) {
435
+ const lg = C.markerLegend(S.marks.set.markers);
436
+ const om = C.omittedNote({ ...S.marks.set, before: S.marks.set.before + S.marks.truncated });
437
+ if (lg || om) { const r3 = el('div', 'row3'); if (lg) r3.appendChild(el('span', S.marks.set.markers.some((m) => m.source === 'paper') ? 'paper' : null, lg)); if (om) r3.appendChild(el('span', null, `${lg ? ' · ' : ''}${om}`)); legendEl.appendChild(r3); }
438
+ }
345
439
  }
346
- chart.subscribeCrosshairMove((p) => { S.hover = p && p.time != null ? p.time : null; renderLegend(S.hover); });
440
+ chart.subscribeCrosshairMove((p) => { S.hover = p && p.time != null ? p.time : null; S.mouse = p && p.point ? { x: p.point.x, y: p.point.y } : null; renderLegend(S.hover); });
347
441
 
348
442
  // ---------------------------------------------------------------- header, LIVE badge, banner, position row
349
443
  function renderHeader() {
@@ -437,7 +531,7 @@
437
531
  // task-1366: a mode / supply change re-labels the axis in place (the bars are prices either way)
438
532
  const disp = C.displayOf(vm);
439
533
  if (disp.key !== S.display.key) { S.display = disp; applyPriceFormat(); }
440
- if (tail && (!vm.seriesKey || vm.seriesKey !== S.key)) { S.vm = { ...vm, candles: [] }; renderHeader(); renderPosition(); renderChip(); renderOverlays(); return; }
534
+ if (tail && (!vm.seriesKey || vm.seriesKey !== S.key)) { S.vm = { ...vm, candles: [] }; renderHeader(); renderPosition(); renderChip(); renderOverlays(); computeMarks(); syncToolbar(); return; }
441
535
  const intervalChanged = vm.interval !== S.interval;
442
536
  S.interval = vm.interval;
443
537
  const answered = S.pendingInterval === vm.interval ? S.reqAt : null;
@@ -520,10 +614,12 @@
520
614
  dctx.clearRect(0, 0, w, h);
521
615
  const segs = S.drawings.filter((d) => d.kind === 't').map((d) => [d.a, d.b]);
522
616
  if (S.tPending && mouse) segs.push([S.tPending, mouse]);
523
- if (!segs.length || !S.bars.length) return;
617
+ if (!S.bars.length) return;
524
618
  const ts = chart.timeScale(), s = active(), pS = periodMs() / 1000;
525
619
  const paneH = chart.panes()[0] ? chart.panes()[0].getHeight() : h;
526
620
  dctx.save(); dctx.beginPath(); dctx.rect(0, 0, ts.width(), paneH); dctx.clip();
621
+ drawMarks(ts, s, paneH); // task-1483
622
+ if (!segs.length) { dctx.restore(); return; }
527
623
  dctx.strokeStyle = T.accent; dctx.fillStyle = T.accent; dctx.lineWidth = 1.5;
528
624
  for (const [a, b] of segs) {
529
625
  const xa = ts.logicalToCoordinate(C.logicalOf(S.bars, a.time, pS)), xb = ts.logicalToCoordinate(C.logicalOf(S.bars, b.time, pS));
@@ -552,6 +648,7 @@
552
648
  case 'm': toggleInd('volma'); break;
553
649
  case 'v': toggleInd('volume'); break;
554
650
  case 'b': toggleInd('tape'); break;
651
+ case 'k': setMarkers(!(S.vm && S.vm.marks && S.vm.marks.on)); break; // task-1483
555
652
  case 'h': armDraw('h'); break;
556
653
  case 't': armDraw('t'); break;
557
654
  case 'x': clearDrawings(); break;
@@ -30,4 +30,6 @@ export const CHART_STYLES = ['candles', 'line', 'area'];
30
30
  export const CHART_SCALES = ['auto', 'log', 'percent'];
31
31
  /** task-1472: what the chart's Y axis shows (the popup's MCAP | PRICE control / `A` ask the terminal to switch). */
32
32
  export const CHART_AXES = ['mcap', 'price'];
33
+ /** The most fills one chart VM carries (newest kept). */
34
+ export const CHART_MARKS_MAX = 500;
33
35
  export const PROTOCOL_VERSION = 'dexterm-popup/1';
@@ -50,6 +50,8 @@ export const POPUP_EVENTS = new Set([
50
50
  'event:chart.interval', 'event:chart.indicator', 'event:chart.style', 'event:chart.scale', 'event:chart.painted',
51
51
  // task-1472: the chart's Y axis (market cap ⇄ price): a display preference the terminal saves, nothing else
52
52
  'event:chart.axis',
53
+ // task-1483: show / hide the chart's BUY / SELL markers (display.chartMarkers): a display preference, nothing else
54
+ 'event:chart.markers',
53
55
  // task-1345 agent roster: per-agent pause / kick, by agent name (the only writes this popup can send)
54
56
  'event:roster.pause', 'event:roster.resume', 'event:roster.kick',
55
57
  // task-1355 order plans: cancel a plan by id (the ONLY write the plans popup can send — never arm/cap/sign)
@@ -98,6 +100,8 @@ export function validateEventPayload(name, payload) {
98
100
  return oneOf(CHART_SCALES, p.scale) ? { scale: p.scale } : null;
99
101
  case 'chart.axis':
100
102
  return oneOf(CHART_AXES, p.axis) ? { axis: p.axis } : null;
103
+ case 'chart.markers': // task-1483: show / hide the BUY / SELL markers (a display setting; nothing else)
104
+ return typeof p.on === 'boolean' ? { on: p.on } : null;
101
105
  case 'chart.painted':
102
106
  return (p.kind === 'first' || p.kind === 'data') && finiteIn(p.ms, 0, 600_000) && finiteIn(p.sentAt, 0, 1e15)
103
107
  ? { kind: p.kind, ms: p.ms, sentAt: p.sentAt } : null;
@@ -9,6 +9,8 @@ export * from "./open.js";
9
9
  export * from "./verdicts.js";
10
10
  export * from "./schedule.js";
11
11
  export * from "./dev-history.js";
12
+ export * from "./ticks.js";
13
+ export * from "./retention.js";
12
14
  export { recorderCli, RECORDER_HELP } from "./cli.js";
13
15
  /** `--no-recorder` / `DEXTERM_RECORDER=0` resolution: the recorder is on in live mode unless either says no. */
14
16
  export function recorderEnabled(env, flagOff) {
@@ -7,7 +7,10 @@
7
7
  // day, skipped while the bucket is paused after a 429. The only requests this package makes.
8
8
  // label code-only outcome labels per (t0 anchor, horizon) from the recorded numbers (labels.ts).
9
9
  // prune retention: raw JSON 7 d (focus rows only), one row per mint per minute after 24 h,
10
- // columns 90 d, outcomes/verdicts forever.
10
+ // columns 90 d, outcomes/verdicts forever; autotrade ticks 14 d, sampled candles 90 d.
11
+ // ticks (task-1504, opt-in) position-grade samples every 5–10 s for held / open / candidate / watch coins
12
+ // (ticks.ts), and a DB cap enforced by evicting the oldest history (retention.ts) instead of
13
+ // pausing observation.
11
14
  // Everything is synchronous SQLite on the app's own DB file (node:sqlite, WAL), batched in transactions,
12
15
  // so a refresh never awaits the recorder and a crash loses at most one batch.
13
16
  import { mkdirSync } from 'node:fs';
@@ -18,8 +21,10 @@ import { ALL_HORIZONS_MASK, HORIZONS_SEC, SCHEMA_SQL, SCHEMA_VERSION } from "./s
18
21
  import { COLUMN_NAMES, fromDsView, fromEnrichedRow, isMaterialChange } from "./snapshot.js";
19
22
  import { LABELER_VERSION, labelAtHorizon } from "./labels.js";
20
23
  import { dueBounds, selectDue } from "./sweep.js";
24
+ import { TickLane } from "./ticks.js";
25
+ import { DEFAULT_AGE, enforceCap, pruneTicksByAge } from "./retention.js";
21
26
  export const DEFAULT_SWEEP = { everyMs: 60_000, maxCallsPerSweep: 6, maxCallsPerDay: 1500, batch: 30 };
22
- export const DEFAULT_RETENTION = { rawDays: 7, columnsDays: 90, downsampleAfterHours: 24, everyMs: 3_600_000 };
27
+ export const DEFAULT_RETENTION = { rawDays: 7, columnsDays: 90, downsampleAfterHours: 24, everyMs: 3_600_000, ...DEFAULT_AGE };
23
28
  /** Unchanged rows are re-written this often at most; the horizons are ≥ 1 h, so 30 min keeps every window observed. */
24
29
  export const DEFAULT_HEARTBEAT_MS = 1_800_000;
25
30
  export const DEFAULT_T0_EVERY_MS = 3_600_000;
@@ -43,6 +48,9 @@ export class Recorder {
43
48
  membersEveryMs;
44
49
  sweepPolicy;
45
50
  retention;
51
+ /** task-1504: the tick lane (null when not configured) and the cap policy (null = no cap). */
52
+ tickLane;
53
+ cap;
46
54
  onLog;
47
55
  /**
48
56
  * Per mint: the last WRITTEN row (columns + hash + time) and the newest observation time this process
@@ -92,6 +100,8 @@ export class Recorder {
92
100
  lastLive: this.db.prepare(`SELECT t, hash, sources, ${cols} FROM token_snapshots WHERE mint = ? AND kind = 'live' ORDER BY t DESC LIMIT 1`),
93
101
  insertCreator: this.db.prepare('INSERT OR IGNORE INTO creators (mint, creator, source, first_t, created_at) VALUES (?, ?, ?, ?, ?)'),
94
102
  };
103
+ this.tickLane = opts.ticks ? new TickLane(this.db, { focus: opts.ticks.focus, policy: opts.ticks.policy, clock: this.clock, onLog: this.onLog }) : null;
104
+ this.cap = opts.cap && opts.cap.maxBytes ? opts.cap : null;
95
105
  }
96
106
  /** Attach as the data layer's store listener (and detach any previous one). */
97
107
  attach(store) {
@@ -298,8 +308,38 @@ export class Recorder {
298
308
  catch (err) {
299
309
  this.log(`prune failed: ${msg(err)}`);
300
310
  }
311
+ try {
312
+ this.enforceCap(now);
313
+ }
314
+ catch (err) {
315
+ this.log(`cap failed: ${msg(err)}`);
316
+ }
301
317
  return out;
302
318
  }
319
+ /**
320
+ * task-1504: one tick-lane poll (ticks.ts). The host calls it every `fastEveryMs` (5 s); 'disabled' when the
321
+ * recorder is off or no tick lane was configured. The only other requests this package makes besides sweeps.
322
+ */
323
+ async pollTicks(deps, now = this.clock.now()) {
324
+ if (!this.enabled || this.closed || !this.tickLane)
325
+ return { coins: 0, calls: 0, answered: 0, written: 0, overCap: 0, skipped: 'disabled' };
326
+ return this.tickLane.poll(deps, now);
327
+ }
328
+ /**
329
+ * task-1504: keep the DB under the configured cap by evicting the oldest history (retention.ts). A no-op without
330
+ * a cap or below its high-water mark. Observation never pauses; the collector calls this before each pass.
331
+ */
332
+ enforceCap(now = this.clock.now()) {
333
+ if (!this.cap || this.closed)
334
+ return null;
335
+ const r = enforceCap(this.db, this.cap, now);
336
+ if (r.evicted) {
337
+ this.log(`cap: used ${(r.usedBefore / 1e6).toFixed(1)} → ${(r.usedAfter / 1e6).toFixed(1)} MB (${r.reclaimed} reclaimed, ${r.slices} slice(s) of history), history now from ${r.cutoff === null ? '—' : new Date(r.cutoff).toISOString()}${r.over ? ' (still over: only the newest hour is left)' : ''}`);
338
+ // Forget cached last rows: an evicted mint is re-seeded from the DB (or written fresh) next time.
339
+ this.seen.clear();
340
+ }
341
+ return r;
342
+ }
303
343
  /** Mints whose horizon window is open and unobserved (sweep.ts), soonest-closing first. */
304
344
  due(now = this.clock.now(), max = this.sweepPolicy.maxCallsPerSweep * this.sweepPolicy.batch) {
305
345
  const b = dueBounds(now);
@@ -455,7 +495,7 @@ export class Recorder {
455
495
  * watermark in recorder_meta means each pass only looks at the hours that newly crossed the line.
456
496
  */
457
497
  prune(now = this.clock.now()) {
458
- const res = { rawCleared: 0, downsampled: 0, expired: 0, membersExpired: 0 };
498
+ const res = { rawCleared: 0, downsampled: 0, expired: 0, membersExpired: 0, ticksExpired: 0, candlesExpired: 0 };
459
499
  if (this.closed)
460
500
  return res;
461
501
  const day = 86_400_000;
@@ -480,14 +520,17 @@ export class Recorder {
480
520
  res.membersExpired = Number(this.db.prepare('DELETE FROM feed_members WHERE t < ?').run(colsBefore).changes);
481
521
  this.db.prepare('DELETE FROM mints WHERE last_any_t < ?').run(colsBefore);
482
522
  this.db.prepare('DELETE FROM sweeps WHERE started_at < ?').run(colsBefore);
523
+ const tk = pruneTicksByAge(this.db, { ticksDays: this.retention.ticksDays, candlesDays: this.retention.candlesDays }, now);
524
+ res.ticksExpired = tk.ticks;
525
+ res.candlesExpired = tk.candles;
483
526
  this.db.exec('COMMIT');
484
527
  }
485
528
  catch (err) {
486
529
  this.db.exec('ROLLBACK');
487
530
  throw err;
488
531
  }
489
- if (res.rawCleared || res.downsampled || res.expired)
490
- this.log(`prune: raw ${res.rawCleared}, downsampled ${res.downsampled}, expired ${res.expired} rows`);
532
+ if (res.rawCleared || res.downsampled || res.expired || res.ticksExpired)
533
+ this.log(`prune: raw ${res.rawCleared}, downsampled ${res.downsampled}, expired ${res.expired} rows, ${res.ticksExpired} ticks`);
491
534
  return res;
492
535
  }
493
536
  // ---------------------------------------------------------------- read side
@@ -512,7 +555,7 @@ export class Recorder {
512
555
  const d = one("SELECT SUM(pgsize) AS b FROM dbstat WHERE name IN ('token_snapshots', 'token_snapshots_mint_t', 'token_snapshots_t', 'token_snapshots_t0_pending')");
513
556
  if (d.b !== null && Number(s.n) > 0)
514
557
  bytesPerSnapshot = Number(d.b) / Number(s.n);
515
- const all = one("SELECT SUM(pgsize) AS b FROM dbstat WHERE name IN ('recorder_meta', 'feed_members', 'feed_members_mint_t', 'token_snapshots', 'token_snapshots_mint_t', 'token_snapshots_t', 'token_snapshots_t0_pending', 'mints', 'mints_last_live', 'sweeps', 'chain_events', 'chain_events_mint_t', 'outcomes', 'outcomes_label', 'verdicts', 'verdicts_mint_t')");
558
+ const all = one("SELECT SUM(pgsize) AS b FROM dbstat WHERE name IN ('recorder_meta', 'feed_members', 'feed_members_mint_t', 'token_snapshots', 'token_snapshots_mint_t', 'token_snapshots_t', 'token_snapshots_t0_pending', 'mints', 'mints_last_live', 'sweeps', 'chain_events', 'chain_events_mint_t', 'outcomes', 'outcomes_label', 'verdicts', 'verdicts_mint_t', 'creators', 'creators_creator', 'autotrade_ticks', 'autotrade_ticks_t', 'autotrade_candles_1m', 'autotrade_candles_1m_t')");
516
559
  recorderBytes = all.b === null ? null : Number(all.b);
517
560
  }
518
561
  catch {
@@ -533,6 +576,9 @@ export class Recorder {
533
576
  outcomesByLabel: byLabel,
534
577
  sweeps: Number(one('SELECT COUNT(*) AS n FROM sweeps').n),
535
578
  sweepCallsToday: this.sweepCallsToday(now),
579
+ ticks: Number(one('SELECT COUNT(*) AS n FROM autotrade_ticks').n),
580
+ candles: Number(one('SELECT COUNT(*) AS n FROM autotrade_candles_1m').n),
581
+ tickCallsToday: this.tickLane ? this.tickLane.callsToday(now) : tickCallsOn(this.db, now),
536
582
  dbBytes, recorderBytes, bytesPerSnapshot,
537
583
  oldestT: s.oldest === null ? null : Number(s.oldest),
538
584
  newestT: s.newest === null ? null : Number(s.newest),
@@ -571,6 +617,17 @@ export class Recorder {
571
617
  this.onLog?.(`recorder: ${line}\n`);
572
618
  }
573
619
  }
620
+ /** Tick calls the lane recorded for `now`'s UTC day (recorder_meta), for a reader with no lane of its own. */
621
+ function tickCallsOn(db, now) {
622
+ const r = db.prepare("SELECT value FROM recorder_meta WHERE key = 'tick_calls'").get();
623
+ try {
624
+ const v = r ? JSON.parse(r.value) : null;
625
+ return v && v.day === new Date(now).toISOString().slice(0, 10) ? Number(v.calls) || 0 : 0;
626
+ }
627
+ catch {
628
+ return 0;
629
+ }
630
+ }
574
631
  function msg(err) {
575
632
  return err instanceof Error ? err.message : String(err);
576
633
  }
@@ -0,0 +1,100 @@
1
+ // dexterm history recorder: retention under a DB cap (task-1504, AUTOTRADE-SPEC §3 + §10 Q4).
2
+ //
3
+ // Before this, the collector's only answer to a full disk was to PAUSE observation above `--max-db-mb`, while the
4
+ // outcome sweeps kept writing: the cap stopped exactly the rows autotrade needs and the DB still grew past it.
5
+ // This module keeps the DB under the cap by EVICTING the oldest recorder history instead, so observation never
6
+ // pauses:
7
+ //
8
+ // age ticks after `ticksDays` (14), sampled candles after `candlesDays` (90). Snapshot / feed-member /
9
+ // sweep rows keep the recorder's existing `columnsDays` rule (recorder.ts prune; Q4's "prune raw rows
10
+ // older than 14 days" is `columnsDays: 14`).
11
+ // reclaim (optional) once the cap is reached, the host's cheap space goes first: the collector passes the data
12
+ // layer's own expired-cache prune (`Cache.prune()`, entries a day past their TTL), measured on
13
+ // 2026-10-21 at ~470 MB of a 1,173 MB file that only held ~430 MB of recorder history.
14
+ // cap once the DB's USED bytes (pages minus the freelist) reach `highWater` × `maxBytes`, delete the
15
+ // oldest slice of history (`sliceMs`, all recorder tables: ticks, candles, snapshots incl. sweep rows,
16
+ // feed members; the small sweep ledger keeps its age rule) and repeat until used ≤ `lowWater` × `maxBytes`. Rows newer than
17
+ // `keepMs` are never evicted. Outcomes, verdicts and creators are tiny and stay forever.
18
+ //
19
+ // SQLite does not shrink the file on DELETE: freed pages go to the freelist and new rows reuse them. So the file
20
+ // stops growing at about the high-water mark; a file that is ALREADY above the cap stays that size (only
21
+ // `VACUUM`, with the collector stopped, gives the space back) but no longer grows.
22
+ export const DEFAULT_CAP = { maxBytes: null, highWater: 0.95, lowWater: 0.85, sliceMs: 3_600_000, keepMs: 3_600_000, maxSlices: 500 };
23
+ export const DEFAULT_AGE = { ticksDays: 14, candlesDays: 90 };
24
+ /** The Q4 default cap when retention-by-cap is switched on (3 GB). */
25
+ export const DEFAULT_CAP_BYTES = 3_000_000_000;
26
+ /** Tables the cap may evict from, each with its time column. */
27
+ const EVICTABLE = [
28
+ { table: 'autotrade_ticks', col: 't' },
29
+ { table: 'autotrade_candles_1m', col: 't' },
30
+ { table: 'token_snapshots', col: 't' },
31
+ { table: 'feed_members', col: 't' },
32
+ ];
33
+ export function dbSize(db) {
34
+ const one = (sql) => Number(Object.values(db.prepare(sql).get())[0]);
35
+ const ps = one('PRAGMA page_size');
36
+ const pc = one('PRAGMA page_count');
37
+ const fl = one('PRAGMA freelist_count');
38
+ return { fileBytes: pc * ps, usedBytes: (pc - fl) * ps };
39
+ }
40
+ /**
41
+ * Enforce the cap: a no-op below the high-water mark; above it, delete the oldest `sliceMs` of history across every
42
+ * evictable table, one transaction per slice, until used ≤ the low-water mark. Never deletes rows newer than
43
+ * `now - keepMs`. Outcome / verdict / creator rows are never touched.
44
+ */
45
+ export function enforceCap(db, policy, now) {
46
+ const p = { ...DEFAULT_CAP, ...policy };
47
+ const size0 = dbSize(db);
48
+ const res = { evicted: false, usedBefore: size0.usedBytes, usedAfter: size0.usedBytes, fileBytes: size0.fileBytes, slices: 0, reclaimed: 0, deleted: {}, cutoff: null, over: false };
49
+ if (p.maxBytes === null || p.maxBytes <= 0 || size0.usedBytes < p.highWater * p.maxBytes)
50
+ return res;
51
+ res.evicted = true;
52
+ const target = p.lowWater * p.maxBytes;
53
+ const floor = now - p.keepMs;
54
+ const tables = EVICTABLE.filter((e) => !!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(e.table));
55
+ const oldestQ = tables.map((e) => ({ ...e, q: db.prepare(`SELECT MIN(${e.col}) AS m FROM ${e.table}`), del: db.prepare(`DELETE FROM ${e.table} WHERE ${e.col} < ?`) }));
56
+ const delMints = db.prepare('DELETE FROM mints WHERE last_any_t < ?');
57
+ let used = size0.usedBytes;
58
+ if (p.reclaim) {
59
+ res.reclaimed = p.reclaim();
60
+ used = dbSize(db).usedBytes;
61
+ }
62
+ while (used > target && res.slices < p.maxSlices) {
63
+ let oldest = Infinity;
64
+ for (const e of oldestQ) {
65
+ const m = e.q.get().m;
66
+ if (m !== null && Number(m) < oldest)
67
+ oldest = Number(m);
68
+ }
69
+ if (!Number.isFinite(oldest) || oldest >= floor)
70
+ break;
71
+ const cutoff = Math.min(oldest + p.sliceMs, floor);
72
+ db.exec('BEGIN');
73
+ try {
74
+ for (const e of oldestQ)
75
+ res.deleted[e.table] = (res.deleted[e.table] ?? 0) + Number(e.del.run(cutoff).changes);
76
+ res.deleted.mints = (res.deleted.mints ?? 0) + Number(delMints.run(cutoff).changes);
77
+ db.exec('COMMIT');
78
+ }
79
+ catch (err) {
80
+ db.exec('ROLLBACK');
81
+ throw err;
82
+ }
83
+ res.slices++;
84
+ res.cutoff = cutoff;
85
+ used = dbSize(db).usedBytes;
86
+ }
87
+ const size1 = dbSize(db);
88
+ res.usedAfter = size1.usedBytes;
89
+ res.fileBytes = size1.fileBytes;
90
+ res.over = size1.usedBytes >= p.highWater * p.maxBytes;
91
+ return res;
92
+ }
93
+ /** Age rules for the autotrade tables (the recorder's prune() calls this next to its own snapshot rules). */
94
+ export function pruneTicksByAge(db, policy, now) {
95
+ const p = { ...DEFAULT_AGE, ...policy };
96
+ const day = 86_400_000;
97
+ const ticks = Number(db.prepare('DELETE FROM autotrade_ticks WHERE t < ?').run(now - p.ticksDays * day).changes);
98
+ const candles = Number(db.prepare('DELETE FROM autotrade_candles_1m WHERE t < ?').run(now - p.candlesDays * day).changes);
99
+ return { ticks, candles };
100
+ }
@@ -12,7 +12,11 @@
12
12
  // - `mints` is a per-mint summary the sweep scheduler reads instead of scanning snapshots.
13
13
  // - feed_members is keyed on (t, feed, rank): the refresh tick is the run.
14
14
  // - chain_events and verdicts are created here (one schema owner) but written by R-02 / L-01.
15
- export const SCHEMA_VERSION = 1;
15
+ //
16
+ // v2 (task-1504, AUTOTRADE-SPEC §3): `autotrade_ticks` (position-grade samples every 5–10 s for the few coins
17
+ // that are watched, open, a candidate or held; written by the tick lane in ticks.ts) and `autotrade_candles_1m`
18
+ // (1-minute OHLC built from those samples). Both are additive: a v1 file gains the two tables on open.
19
+ export const SCHEMA_VERSION = 2;
16
20
  export const HORIZONS_SEC = [3600, 7200, 21600, 86400, 259200];
17
21
  /** Every horizon labelled: the bitmask value of `horizons_done` for a finished t0 row. */
18
22
  export const ALL_HORIZONS_MASK = (1 << HORIZONS_SEC.length) - 1;
@@ -143,4 +147,33 @@ CREATE TABLE IF NOT EXISTS creators (
143
147
  created_at INTEGER -- the coin's creation time when the source gave one
144
148
  ) WITHOUT ROWID;
145
149
  CREATE INDEX IF NOT EXISTS creators_creator ON creators (creator, first_t);
150
+
151
+ -- task-1504 (AUTOTRADE-SPEC §3): position-grade ticks. One row per (mint, poll) for focus coins, written
152
+ -- every sample (no change detection), from DexScreener tokens/v1 batches. price_usd NULL = the poll got no
153
+ -- pair back for the mint (pool gone or not indexed): a held coin's rug shows up here as NULL rows.
154
+ CREATE TABLE IF NOT EXISTS autotrade_ticks (
155
+ mint TEXT NOT NULL,
156
+ t INTEGER NOT NULL, -- unix ms the answer was fetched
157
+ role TEXT NOT NULL, -- why it was polled: 'held' | 'open' | 'candidate' | 'watch'
158
+ price_usd REAL, liquidity_usd REAL, mcap REAL, fdv REAL,
159
+ vol_m5 REAL, vol_h1 REAL,
160
+ buys_m5 INTEGER, sells_m5 INTEGER,
161
+ chg_m5 REAL, chg_h1 REAL,
162
+ pair_address TEXT,
163
+ pair_created_at INTEGER,
164
+ PRIMARY KEY (mint, t)
165
+ ) WITHOUT ROWID;
166
+ CREATE INDEX IF NOT EXISTS autotrade_ticks_t ON autotrade_ticks (t);
167
+
168
+ -- 1-minute candles for the same coins, built from the ticks (sampled: high/low are of the 5–10 s samples,
169
+ -- not of every trade). t = the minute's start; n = samples in the minute.
170
+ CREATE TABLE IF NOT EXISTS autotrade_candles_1m (
171
+ mint TEXT NOT NULL,
172
+ t INTEGER NOT NULL,
173
+ o REAL NOT NULL, h REAL NOT NULL, l REAL NOT NULL, c REAL NOT NULL,
174
+ liq_c REAL, mcap_c REAL,
175
+ n INTEGER NOT NULL,
176
+ PRIMARY KEY (mint, t)
177
+ ) WITHOUT ROWID;
178
+ CREATE INDEX IF NOT EXISTS autotrade_candles_1m_t ON autotrade_candles_1m (t);
146
179
  `;