openalgo-script 0.4.0 → 0.6.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 (359) hide show
  1. package/CHANGELOG.md +1050 -0
  2. package/README.md +111 -27
  3. package/dist/adapters/charts/driving.d.ts +50 -0
  4. package/dist/adapters/charts/driving.d.ts.map +1 -0
  5. package/dist/adapters/charts/driving.js +57 -0
  6. package/dist/adapters/charts/driving.js.map +1 -0
  7. package/dist/adapters/charts/run.d.ts +20 -0
  8. package/dist/adapters/charts/run.d.ts.map +1 -1
  9. package/dist/adapters/charts/run.js +89 -16
  10. package/dist/adapters/charts/run.js.map +1 -1
  11. package/dist/adapters/charts/tables.d.ts +6 -19
  12. package/dist/adapters/charts/tables.d.ts.map +1 -1
  13. package/dist/adapters/charts/tables.js.map +1 -1
  14. package/dist/adapters/charts/undrawable.d.ts +4 -0
  15. package/dist/adapters/charts/undrawable.d.ts.map +1 -0
  16. package/dist/adapters/charts/undrawable.js +47 -0
  17. package/dist/adapters/charts/undrawable.js.map +1 -0
  18. package/dist/adapters/charts/venue.d.ts +73 -0
  19. package/dist/adapters/charts/venue.d.ts.map +1 -0
  20. package/dist/adapters/charts/venue.js +104 -0
  21. package/dist/adapters/charts/venue.js.map +1 -0
  22. package/dist/core/accounting/analysis.d.ts +111 -0
  23. package/dist/core/accounting/analysis.d.ts.map +1 -0
  24. package/dist/core/accounting/analysis.js +123 -0
  25. package/dist/core/accounting/analysis.js.map +1 -0
  26. package/dist/core/accounting/equity.d.ts +33 -0
  27. package/dist/core/accounting/equity.d.ts.map +1 -1
  28. package/dist/core/accounting/equity.js +12 -0
  29. package/dist/core/accounting/equity.js.map +1 -1
  30. package/dist/core/accounting/index.d.ts +2 -0
  31. package/dist/core/accounting/index.d.ts.map +1 -1
  32. package/dist/core/accounting/index.js +1 -0
  33. package/dist/core/accounting/index.js.map +1 -1
  34. package/dist/core/accounting/report.d.ts +3 -0
  35. package/dist/core/accounting/report.d.ts.map +1 -1
  36. package/dist/core/accounting/report.js +2 -0
  37. package/dist/core/accounting/report.js.map +1 -1
  38. package/dist/core/accounting/statistics.d.ts +12 -0
  39. package/dist/core/accounting/statistics.d.ts.map +1 -1
  40. package/dist/core/accounting/statistics.js +23 -1
  41. package/dist/core/accounting/statistics.js.map +1 -1
  42. package/dist/core/backtest/case.d.ts +60 -0
  43. package/dist/core/backtest/case.d.ts.map +1 -0
  44. package/dist/core/backtest/case.js +319 -0
  45. package/dist/core/backtest/case.js.map +1 -0
  46. package/dist/core/backtest/compare.d.ts.map +1 -1
  47. package/dist/core/backtest/compare.js +2 -0
  48. package/dist/core/backtest/compare.js.map +1 -1
  49. package/dist/core/backtest/deliver.d.ts +93 -0
  50. package/dist/core/backtest/deliver.d.ts.map +1 -0
  51. package/dist/core/backtest/deliver.js +94 -0
  52. package/dist/core/backtest/deliver.js.map +1 -0
  53. package/dist/core/backtest/drive.d.ts +101 -8
  54. package/dist/core/backtest/drive.d.ts.map +1 -1
  55. package/dist/core/backtest/drive.js +114 -158
  56. package/dist/core/backtest/drive.js.map +1 -1
  57. package/dist/core/backtest/index.d.ts +26 -12
  58. package/dist/core/backtest/index.d.ts.map +1 -1
  59. package/dist/core/backtest/index.js +21 -10
  60. package/dist/core/backtest/index.js.map +1 -1
  61. package/dist/core/backtest/record.d.ts +63 -2
  62. package/dist/core/backtest/record.d.ts.map +1 -1
  63. package/dist/core/backtest/record.js +71 -4
  64. package/dist/core/backtest/record.js.map +1 -1
  65. package/dist/core/backtest/replay.d.ts.map +1 -1
  66. package/dist/core/backtest/replay.js +11 -1
  67. package/dist/core/backtest/replay.js.map +1 -1
  68. package/dist/core/backtest/simulate.d.ts +113 -1
  69. package/dist/core/backtest/simulate.d.ts.map +1 -1
  70. package/dist/core/backtest/simulate.js +123 -5
  71. package/dist/core/backtest/simulate.js.map +1 -1
  72. package/dist/core/backtest/walk.d.ts +39 -0
  73. package/dist/core/backtest/walk.d.ts.map +1 -0
  74. package/dist/core/backtest/walk.js +125 -0
  75. package/dist/core/backtest/walk.js.map +1 -0
  76. package/dist/core/catalogue/catalogue.generated.d.ts +179 -2
  77. package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
  78. package/dist/core/catalogue/catalogue.generated.js +19 -2
  79. package/dist/core/catalogue/catalogue.generated.js.map +1 -1
  80. package/dist/core/catalogue/types.d.ts +8 -2
  81. package/dist/core/catalogue/types.d.ts.map +1 -1
  82. package/dist/core/catalogue/values.generated.d.ts +51 -0
  83. package/dist/core/catalogue/values.generated.d.ts.map +1 -1
  84. package/dist/core/check/assigned.d.ts +19 -0
  85. package/dist/core/check/assigned.d.ts.map +1 -0
  86. package/dist/core/check/assigned.js +14 -0
  87. package/dist/core/check/assigned.js.map +1 -0
  88. package/dist/core/check/call-sites.d.ts.map +1 -1
  89. package/dist/core/check/call-sites.js +9 -4
  90. package/dist/core/check/call-sites.js.map +1 -1
  91. package/dist/core/check/calls.d.ts.map +1 -1
  92. package/dist/core/check/calls.js +12 -4
  93. package/dist/core/check/calls.js.map +1 -1
  94. package/dist/core/check/check.d.ts.map +1 -1
  95. package/dist/core/check/check.js +15 -2
  96. package/dist/core/check/check.js.map +1 -1
  97. package/dist/core/check/checker.d.ts +26 -0
  98. package/dist/core/check/checker.d.ts.map +1 -1
  99. package/dist/core/check/checker.js +38 -2
  100. package/dist/core/check/checker.js.map +1 -1
  101. package/dist/core/check/constant.d.ts +16 -0
  102. package/dist/core/check/constant.d.ts.map +1 -1
  103. package/dist/core/check/constant.js +52 -0
  104. package/dist/core/check/constant.js.map +1 -1
  105. package/dist/core/check/deleted.d.ts +3 -0
  106. package/dist/core/check/deleted.d.ts.map +1 -0
  107. package/dist/core/check/deleted.js +109 -0
  108. package/dist/core/check/deleted.js.map +1 -0
  109. package/dist/core/check/expressions.js +1 -1
  110. package/dist/core/check/expressions.js.map +1 -1
  111. package/dist/core/check/library-orders.js +2 -2
  112. package/dist/core/check/library-orders.js.map +1 -1
  113. package/dist/core/check/library-output.d.ts.map +1 -1
  114. package/dist/core/check/library-output.js +1 -0
  115. package/dist/core/check/library-output.js.map +1 -1
  116. package/dist/core/check/library-prose.generated.js +12 -12
  117. package/dist/core/check/library-prose.generated.js.map +1 -1
  118. package/dist/core/check/library.d.ts +7 -0
  119. package/dist/core/check/library.d.ts.map +1 -1
  120. package/dist/core/check/library.js +1 -0
  121. package/dist/core/check/library.js.map +1 -1
  122. package/dist/core/check/surface.d.ts +9 -0
  123. package/dist/core/check/surface.d.ts.map +1 -1
  124. package/dist/core/check/surface.js +13 -0
  125. package/dist/core/check/surface.js.map +1 -1
  126. package/dist/core/emit/canonical.d.ts +22 -8
  127. package/dist/core/emit/canonical.d.ts.map +1 -1
  128. package/dist/core/emit/canonical.js +67 -6
  129. package/dist/core/emit/canonical.js.map +1 -1
  130. package/dist/core/engine/arithmetic.d.ts +6 -25
  131. package/dist/core/engine/arithmetic.d.ts.map +1 -1
  132. package/dist/core/engine/arithmetic.js +48 -3
  133. package/dist/core/engine/arithmetic.js.map +1 -1
  134. package/dist/core/engine/bar-source.d.ts +65 -0
  135. package/dist/core/engine/bar-source.d.ts.map +1 -0
  136. package/dist/core/engine/bar-source.js +58 -0
  137. package/dist/core/engine/bar-source.js.map +1 -0
  138. package/dist/core/engine/engine.d.ts +9 -11
  139. package/dist/core/engine/engine.d.ts.map +1 -1
  140. package/dist/core/engine/engine.js +24 -24
  141. package/dist/core/engine/engine.js.map +1 -1
  142. package/dist/core/engine/guard.d.ts.map +1 -1
  143. package/dist/core/engine/guard.js +10 -0
  144. package/dist/core/engine/guard.js.map +1 -1
  145. package/dist/core/engine/host.d.ts +4 -2
  146. package/dist/core/engine/host.d.ts.map +1 -1
  147. package/dist/core/engine/host.js.map +1 -1
  148. package/dist/core/engine/index.d.ts +2 -1
  149. package/dist/core/engine/index.d.ts.map +1 -1
  150. package/dist/core/engine/index.js +1 -1
  151. package/dist/core/engine/index.js.map +1 -1
  152. package/dist/core/engine/library/arrays.d.ts.map +1 -1
  153. package/dist/core/engine/library/arrays.js +8 -2
  154. package/dist/core/engine/library/arrays.js.map +1 -1
  155. package/dist/core/engine/library/binding.d.ts +2 -0
  156. package/dist/core/engine/library/binding.d.ts.map +1 -1
  157. package/dist/core/engine/library/binding.js.map +1 -1
  158. package/dist/core/engine/library/code-points.d.ts +40 -0
  159. package/dist/core/engine/library/code-points.d.ts.map +1 -0
  160. package/dist/core/engine/library/code-points.js +74 -0
  161. package/dist/core/engine/library/code-points.js.map +1 -0
  162. package/dist/core/engine/library/index.d.ts +13 -0
  163. package/dist/core/engine/library/index.d.ts.map +1 -1
  164. package/dist/core/engine/library/index.js +15 -0
  165. package/dist/core/engine/library/index.js.map +1 -1
  166. package/dist/core/engine/library/objects.d.ts.map +1 -1
  167. package/dist/core/engine/library/objects.js +1 -1
  168. package/dist/core/engine/library/objects.js.map +1 -1
  169. package/dist/core/engine/library/text.d.ts.map +1 -1
  170. package/dist/core/engine/library/text.js +51 -34
  171. package/dist/core/engine/library/text.js.map +1 -1
  172. package/dist/core/engine/load.d.ts +20 -0
  173. package/dist/core/engine/load.d.ts.map +1 -1
  174. package/dist/core/engine/load.js +62 -0
  175. package/dist/core/engine/load.js.map +1 -1
  176. package/dist/core/engine/request-body.d.ts +3 -1
  177. package/dist/core/engine/request-body.d.ts.map +1 -1
  178. package/dist/core/engine/request-body.js +3 -1
  179. package/dist/core/engine/request-body.js.map +1 -1
  180. package/dist/core/engine/request-plan.d.ts +3 -3
  181. package/dist/core/engine/request-plan.js +3 -3
  182. package/dist/core/engine/requests.d.ts +2 -2
  183. package/dist/core/engine/requests.d.ts.map +1 -1
  184. package/dist/core/engine/requests.js +5 -4
  185. package/dist/core/engine/requests.js.map +1 -1
  186. package/dist/core/engine/series.d.ts +18 -6
  187. package/dist/core/engine/series.d.ts.map +1 -1
  188. package/dist/core/engine/series.js +24 -5
  189. package/dist/core/engine/series.js.map +1 -1
  190. package/dist/core/engine/session/record.d.ts +4 -4
  191. package/dist/core/engine/session/record.js +4 -4
  192. package/dist/core/engine/verify-tables.d.ts.map +1 -1
  193. package/dist/core/engine/verify-tables.js +41 -0
  194. package/dist/core/engine/verify-tables.js.map +1 -1
  195. package/dist/core/importer/calls.d.ts +29 -0
  196. package/dist/core/importer/calls.d.ts.map +1 -0
  197. package/dist/core/importer/calls.js +267 -0
  198. package/dist/core/importer/calls.js.map +1 -0
  199. package/dist/core/importer/context.d.ts +118 -0
  200. package/dist/core/importer/context.d.ts.map +1 -0
  201. package/dist/core/importer/context.js +120 -0
  202. package/dist/core/importer/context.js.map +1 -0
  203. package/dist/core/importer/expressions.d.ts +44 -0
  204. package/dist/core/importer/expressions.d.ts.map +1 -0
  205. package/dist/core/importer/expressions.js +223 -0
  206. package/dist/core/importer/expressions.js.map +1 -0
  207. package/dist/core/importer/importer.d.ts +13 -0
  208. package/dist/core/importer/importer.d.ts.map +1 -0
  209. package/dist/core/importer/importer.js +220 -0
  210. package/dist/core/importer/importer.js.map +1 -0
  211. package/dist/core/importer/index.d.ts +20 -0
  212. package/dist/core/importer/index.d.ts.map +1 -0
  213. package/dist/core/importer/index.js +18 -0
  214. package/dist/core/importer/index.js.map +1 -0
  215. package/dist/core/importer/lexer.d.ts +68 -0
  216. package/dist/core/importer/lexer.d.ts.map +1 -0
  217. package/dist/core/importer/lexer.js +207 -0
  218. package/dist/core/importer/lexer.js.map +1 -0
  219. package/dist/core/importer/names.d.ts +41 -0
  220. package/dist/core/importer/names.d.ts.map +1 -0
  221. package/dist/core/importer/names.js +78 -0
  222. package/dist/core/importer/names.js.map +1 -0
  223. package/dist/core/importer/orders.d.ts +8 -0
  224. package/dist/core/importer/orders.d.ts.map +1 -0
  225. package/dist/core/importer/orders.js +332 -0
  226. package/dist/core/importer/orders.js.map +1 -0
  227. package/dist/core/importer/outputs.d.ts +19 -0
  228. package/dist/core/importer/outputs.d.ts.map +1 -0
  229. package/dist/core/importer/outputs.js +266 -0
  230. package/dist/core/importer/outputs.js.map +1 -0
  231. package/dist/core/importer/parser.d.ts +5 -0
  232. package/dist/core/importer/parser.d.ts.map +1 -0
  233. package/dist/core/importer/parser.js +439 -0
  234. package/dist/core/importer/parser.js.map +1 -0
  235. package/dist/core/importer/presence.d.ts +32 -0
  236. package/dist/core/importer/presence.d.ts.map +1 -0
  237. package/dist/core/importer/presence.js +146 -0
  238. package/dist/core/importer/presence.js.map +1 -0
  239. package/dist/core/importer/settle.d.ts +23 -0
  240. package/dist/core/importer/settle.d.ts.map +1 -0
  241. package/dist/core/importer/settle.js +135 -0
  242. package/dist/core/importer/settle.js.map +1 -0
  243. package/dist/core/importer/statements.d.ts +4 -0
  244. package/dist/core/importer/statements.d.ts.map +1 -0
  245. package/dist/core/importer/statements.js +339 -0
  246. package/dist/core/importer/statements.js.map +1 -0
  247. package/dist/core/importer/syntax.d.ts +182 -0
  248. package/dist/core/importer/syntax.d.ts.map +1 -0
  249. package/dist/core/importer/syntax.js +14 -0
  250. package/dist/core/importer/syntax.js.map +1 -0
  251. package/dist/core/importer/table.d.ts +52 -0
  252. package/dist/core/importer/table.d.ts.map +1 -0
  253. package/dist/core/importer/table.js +156 -0
  254. package/dist/core/importer/table.js.map +1 -0
  255. package/dist/core/importer/values.d.ts +27 -0
  256. package/dist/core/importer/values.d.ts.map +1 -0
  257. package/dist/core/importer/values.js +236 -0
  258. package/dist/core/importer/values.js.map +1 -0
  259. package/dist/core/index.d.ts +22 -5
  260. package/dist/core/index.d.ts.map +1 -1
  261. package/dist/core/index.js +19 -3
  262. package/dist/core/index.js.map +1 -1
  263. package/dist/core/stdlib/calendar/civil.d.ts.map +1 -1
  264. package/dist/core/stdlib/calendar/civil.js +5 -2
  265. package/dist/core/stdlib/calendar/civil.js.map +1 -1
  266. package/dist/core/stdlib/index.d.ts +1 -1
  267. package/dist/core/stdlib/index.d.ts.map +1 -1
  268. package/dist/core/stdlib/index.js +1 -1
  269. package/dist/core/stdlib/index.js.map +1 -1
  270. package/dist/core/stdlib/maths/index.d.ts +1 -1
  271. package/dist/core/stdlib/maths/index.d.ts.map +1 -1
  272. package/dist/core/stdlib/maths/index.js +1 -1
  273. package/dist/core/stdlib/maths/index.js.map +1 -1
  274. package/dist/core/stdlib/maths/rounding.d.ts +5 -0
  275. package/dist/core/stdlib/maths/rounding.d.ts.map +1 -1
  276. package/dist/core/stdlib/maths/rounding.js +19 -1
  277. package/dist/core/stdlib/maths/rounding.js.map +1 -1
  278. package/dist/core/version/version.generated.d.ts +1 -1
  279. package/dist/core/version/version.generated.js +1 -1
  280. package/package.json +17 -2
  281. package/spec/README.md +2 -1
  282. package/spec/errors.json +383 -23
  283. package/src/adapters/charts/driving.ts +109 -0
  284. package/src/adapters/charts/run.ts +126 -28
  285. package/src/adapters/charts/tables.ts +6 -19
  286. package/src/adapters/charts/undrawable.ts +51 -0
  287. package/src/adapters/charts/venue.ts +132 -0
  288. package/src/core/accounting/analysis.ts +188 -0
  289. package/src/core/accounting/equity.ts +38 -0
  290. package/src/core/accounting/index.ts +2 -0
  291. package/src/core/accounting/report.ts +5 -0
  292. package/src/core/accounting/statistics.ts +38 -1
  293. package/src/core/backtest/case.ts +395 -0
  294. package/src/core/backtest/compare.ts +2 -0
  295. package/src/core/backtest/deliver.ts +161 -0
  296. package/src/core/backtest/drive.ts +233 -217
  297. package/src/core/backtest/index.ts +26 -12
  298. package/src/core/backtest/record.ts +134 -5
  299. package/src/core/backtest/replay.ts +11 -1
  300. package/src/core/backtest/simulate.ts +201 -6
  301. package/src/core/backtest/walk.ts +182 -0
  302. package/src/core/catalogue/catalogue.generated.ts +19 -2
  303. package/src/core/catalogue/types.ts +8 -2
  304. package/src/core/catalogue/values.generated.ts +16 -0
  305. package/src/core/check/assigned.ts +31 -0
  306. package/src/core/check/call-sites.ts +11 -4
  307. package/src/core/check/calls.ts +13 -4
  308. package/src/core/check/check.ts +14 -2
  309. package/src/core/check/checker.ts +39 -2
  310. package/src/core/check/constant.ts +57 -0
  311. package/src/core/check/deleted.ts +135 -0
  312. package/src/core/check/expressions.ts +1 -1
  313. package/src/core/check/library-orders.ts +2 -2
  314. package/src/core/check/library-output.ts +1 -0
  315. package/src/core/check/library-prose.generated.ts +12 -12
  316. package/src/core/check/library.ts +8 -0
  317. package/src/core/check/surface.ts +16 -0
  318. package/src/core/emit/canonical.ts +67 -9
  319. package/src/core/engine/arithmetic.ts +23 -3
  320. package/src/core/engine/bar-source.ts +113 -0
  321. package/src/core/engine/engine.ts +26 -25
  322. package/src/core/engine/guard.ts +12 -1
  323. package/src/core/engine/host.ts +4 -2
  324. package/src/core/engine/index.ts +2 -1
  325. package/src/core/engine/library/arrays.ts +8 -2
  326. package/src/core/engine/library/binding.ts +2 -0
  327. package/src/core/engine/library/code-points.ts +73 -0
  328. package/src/core/engine/library/index.ts +17 -0
  329. package/src/core/engine/library/objects.ts +1 -6
  330. package/src/core/engine/library/text.ts +53 -35
  331. package/src/core/engine/load.ts +68 -0
  332. package/src/core/engine/request-body.ts +5 -7
  333. package/src/core/engine/request-plan.ts +3 -3
  334. package/src/core/engine/requests.ts +11 -8
  335. package/src/core/engine/series.ts +30 -6
  336. package/src/core/engine/session/record.ts +4 -4
  337. package/src/core/engine/verify-tables.ts +43 -0
  338. package/src/core/importer/calls.ts +274 -0
  339. package/src/core/importer/context.ts +197 -0
  340. package/src/core/importer/expressions.ts +247 -0
  341. package/src/core/importer/importer.ts +226 -0
  342. package/src/core/importer/index.ts +19 -0
  343. package/src/core/importer/lexer.ts +285 -0
  344. package/src/core/importer/names.ts +104 -0
  345. package/src/core/importer/orders.ts +307 -0
  346. package/src/core/importer/outputs.ts +245 -0
  347. package/src/core/importer/parser.ts +436 -0
  348. package/src/core/importer/presence.ts +164 -0
  349. package/src/core/importer/settle.ts +141 -0
  350. package/src/core/importer/statements.ts +331 -0
  351. package/src/core/importer/syntax.ts +144 -0
  352. package/src/core/importer/table.ts +191 -0
  353. package/src/core/importer/values.ts +241 -0
  354. package/src/core/index.ts +29 -2
  355. package/src/core/stdlib/calendar/civil.ts +5 -2
  356. package/src/core/stdlib/index.ts +1 -0
  357. package/src/core/stdlib/maths/index.ts +1 -0
  358. package/src/core/stdlib/maths/rounding.ts +23 -1
  359. package/src/core/version/version.generated.ts +1 -1
@@ -0,0 +1,135 @@
1
+ /**
2
+ * OS8019: a name, or an array, still holding an object the script deleted.
3
+ *
4
+ * `language.md` 5.4: deleting an object touches neither the name nor the array
5
+ * element that refers to it, so what is left is stale rather than absent, and
6
+ * the next setter that reaches it stops the bar with OS4005, usually many bars
7
+ * after the line that caused it. The warning is about the line that caused it.
8
+ *
9
+ * The rule is read on one path, which is the block the delete is written in.
10
+ * A `draw.delete(name)` is cleared by a later statement of the same block that
11
+ * assigns the name again, `none` or a new object alike. A
12
+ * `draw.delete(element(arr, i))`, or `arr[i]`, is cleared by a later statement
13
+ * of the same block that takes an element out of the array or empties it. Only
14
+ * a name that outlives the bar is followed: a plain name is recomputed on the
15
+ * next bar, and a stale value in it cannot reach one.
16
+ *
17
+ * What this does not see, and does not claim to: a clearing line in a nested
18
+ * block, which may not run, and a deletion reached through a function's
19
+ * parameter, where the name the caller holds is not in view.
20
+ */
21
+ import type { AstNode, Block, Call, Expression, Script } from '../ast/index.js';
22
+ import { childrenOf, withoutGrouping } from '../ast/index.js';
23
+ import type { Binding } from './checked.js';
24
+ import type { Checker } from './checker.js';
25
+ import { elementOf } from './types.js';
26
+
27
+ /** The calls that take an element out of an array or empty it, `language.md` 14.1. */
28
+ const REMOVING = new Set(['shift', 'pop', 'remove', 'clear']);
29
+
30
+ interface Held {
31
+ readonly binding: Binding;
32
+ /** Whether the object sits in an element of the array the binding names. */
33
+ readonly inArray: boolean;
34
+ }
35
+
36
+ export function reportDeletedStillHeld(checker: Checker): void {
37
+ // One walk of the whole tree, paid only by a file that deletes something.
38
+ if (!checker.calls.some((one) => one.name === 'draw.delete')) return;
39
+ visit(checker, checker.script);
40
+ }
41
+
42
+ function visit(checker: Checker, node: AstNode): void {
43
+ if (node.kind === 'script' || node.kind === 'block') {
44
+ reportList(checker, statementsOf(node));
45
+ }
46
+ for (const child of childrenOf(node)) visit(checker, child);
47
+ }
48
+
49
+ function statementsOf(node: Script | Block): readonly AstNode[] {
50
+ return node.kind === 'script' ? node.items : node.statements;
51
+ }
52
+
53
+ function reportList(checker: Checker, statements: readonly AstNode[]): void {
54
+ statements.forEach((statement, index) => {
55
+ const call = deleteCall(checker, statement);
56
+ if (call === undefined) return;
57
+ const argument = call.args[0]?.value;
58
+ if (argument === undefined) return;
59
+ const held = heldBy(checker, argument);
60
+ if (held === undefined) return;
61
+ const kind = objectKindOf(checker, argument);
62
+ if (kind === undefined) return;
63
+ const later = statements.slice(index + 1);
64
+ const cleared = held.inArray
65
+ ? later.some((one) => removesFrom(checker, one, held.binding))
66
+ : later.some((one) => assigns(one, held.binding.name));
67
+ if (cleared) return;
68
+ checker.report('OS8019', argument.span, {
69
+ name: held.binding.name,
70
+ kind,
71
+ line: call.span.line,
72
+ });
73
+ });
74
+ }
75
+
76
+ /** The `draw.delete(...)` a statement is, when it is one. */
77
+ function deleteCall(checker: Checker, statement: AstNode): Call | undefined {
78
+ if (statement.kind !== 'expressionStatement') return undefined;
79
+ const expression = withoutGrouping(statement.expression);
80
+ if (expression.kind !== 'call') return undefined;
81
+ return checker.callSites.get(expression)?.name === 'draw.delete' ? expression : undefined;
82
+ }
83
+
84
+ /** The persistent name the deleted object is still reachable through. */
85
+ function heldBy(checker: Checker, argument: Expression): Held | undefined {
86
+ const written = withoutGrouping(argument);
87
+ if (written.kind === 'nameReference') {
88
+ const binding = persistent(checker, written);
89
+ return binding === undefined ? undefined : { binding, inArray: false };
90
+ }
91
+ const array = arrayOf(checker, written);
92
+ if (array === undefined) return undefined;
93
+ const binding = persistent(checker, array);
94
+ return binding === undefined ? undefined : { binding, inArray: true };
95
+ }
96
+
97
+ /** `element(arr, i)` or `arr[i]`, as the array expression it reads. */
98
+ function arrayOf(checker: Checker, written: Expression): Expression | undefined {
99
+ if (written.kind === 'index') return withoutGrouping(written.target);
100
+ if (written.kind === 'call' && checker.callSites.get(written)?.name === 'element') {
101
+ const first = written.args[0]?.value;
102
+ return first === undefined ? undefined : withoutGrouping(first);
103
+ }
104
+ return undefined;
105
+ }
106
+
107
+ function persistent(checker: Checker, expression: Expression): Binding | undefined {
108
+ if (expression.kind !== 'nameReference') return undefined;
109
+ const binding = checker.references.get(expression);
110
+ return binding !== undefined && binding.persistence !== 'none' ? binding : undefined;
111
+ }
112
+
113
+ function objectKindOf(checker: Checker, argument: Expression): string | undefined {
114
+ const type = checker.types.get(argument);
115
+ if (type === undefined) return undefined;
116
+ const element = elementOf(type);
117
+ return element.kind === 'object' ? element.object : undefined;
118
+ }
119
+
120
+ function assigns(statement: AstNode, name: string): boolean {
121
+ return statement.kind === 'assignment' && statement.target.text === name;
122
+ }
123
+
124
+ /** Whether any call in the statement takes an element out of this array. */
125
+ function removesFrom(checker: Checker, node: AstNode, binding: Binding): boolean {
126
+ if (node.kind === 'call') {
127
+ const name = checker.callSites.get(node)?.name;
128
+ const first = node.args[0]?.value;
129
+ if (name !== undefined && REMOVING.has(name) && first !== undefined) {
130
+ const target = withoutGrouping(first);
131
+ if (target.kind === 'nameReference' && checker.references.get(target) === binding) return true;
132
+ }
133
+ }
134
+ return childrenOf(node).some((child) => removesFrom(checker, child, binding));
135
+ }
@@ -203,7 +203,7 @@ function checkNameReference(checker: Checker, expression: NameReference): Type {
203
203
  }
204
204
  binding.isRead = true;
205
205
  checker.references.set(expression, binding);
206
- return checker.record(expression, binding.type, binding.warmup);
206
+ return checker.record(expression, binding.type, checker.warmupOfRead(binding));
207
207
  }
208
208
 
209
209
  const entries = libraryEntries(expression.name);
@@ -240,7 +240,7 @@ const legs: readonly LibraryEntry[] = [
240
240
  }),
241
241
  entry('leg.stop(name: string, price: number) -> nothing', { ...strategyOnly, planned: true }),
242
242
  entry('leg.target(name: string, price: number) -> nothing', { ...strategyOnly, planned: true }),
243
- entry('leg.trail(name: string, distance: number, arm?: number = none) -> nothing', {
243
+ entry('leg.trail(name: string, distance: number, activateAt?: number = none) -> nothing', {
244
244
  ...strategyOnly,
245
245
  planned: true,
246
246
  }),
@@ -258,7 +258,7 @@ const book: readonly LibraryEntry[] = [
258
258
  entry('book.stop(amount: number) -> nothing', { ...strategyOnly, planned: true }),
259
259
  entry('book.target(amount: number) -> nothing', { ...strategyOnly, planned: true }),
260
260
  entry(
261
- 'book.lockProfit(arm: number, lock: number, step?: number = none, advance?: number = none) -> nothing',
261
+ 'book.lockProfit(activateAt: number, lock: number, step?: number = none, advance?: number = none) -> nothing',
262
262
  { ...strategyOnly, planned: true },
263
263
  ),
264
264
  entry('book.trailStopsToEntry(at: number) -> nothing', { ...strategyOnly, planned: true }),
@@ -77,6 +77,7 @@ const declarations: readonly LibraryEntry[] = [
77
77
  topLevel: true,
78
78
  values: { style: PLOT_STYLES, format: FORMATS, scale: SCALES },
79
79
  constant: ['title', 'width', 'style', 'offset', 'overlay', 'precision', 'format', 'scale'],
80
+ written: ['style'],
80
81
  whole: { precision: wholeRange(0, 10), offset: wholeRange() },
81
82
  },
82
83
  ),
@@ -49,7 +49,7 @@ export const LIBRARY_PROSE: Readonly<Record<string, LibraryProse>> = {
49
49
  "book.exit": {"summary":"Exit the whole book as a unit"},
50
50
  "book.exitAt": {"summary":"Square off every leg at this `\"HHMM\"` in the chart's timezone"},
51
51
  "book.isOpen": {"summary":"Whether any leg holds a position","warmup":"bar 0"},
52
- "book.lockProfit": {"summary":"Arm a floor at a profit, then advance it as profit grows"},
52
+ "book.lockProfit": {"summary":"Activate a floor at a profit, then advance it as profit grows"},
53
53
  "book.profit": {"summary":"The book's profit in money, open and realised since it was last flat","warmup":"bar 0"},
54
54
  "book.squareOffAtExpiry": {"summary":"Square off a leg this many minutes before its contract expires"},
55
55
  "book.stop": {"summary":"Square off every leg when the book's profit falls to `-amount`"},
@@ -118,24 +118,24 @@ export const LIBRARY_PROSE: Readonly<Record<string, LibraryProse>> = {
118
118
  "dpo": {"summary":"Price with its displaced mean removed, to expose a cycle","warmup":"bar `len + floor(len / 2)`"},
119
119
  "draw.box": {"summary":"A zone: supply, demand, an opening range"},
120
120
  "draw.count": {"summary":"How many objects this script currently holds"},
121
- "draw.delete": {"summary":"box"},
121
+ "draw.delete": {"summary":"Remove one object"},
122
122
  "draw.deleteAll": {"summary":"Remove every object this script created"},
123
123
  "draw.label": {"summary":"A plate of text at a point"},
124
124
  "draw.line": {"summary":"A trendline between two points"},
125
125
  "draw.polyline": {"summary":"A path or a closed shape through many points"},
126
126
  "draw.setAt": {"summary":"Move a label"},
127
- "draw.setBounds": {"summary":"nothing"},
128
- "draw.setColor": {"summary":"box"},
127
+ "draw.setBounds": {"summary":"Move both anchors in one call"},
128
+ "draw.setColor": {"summary":"Change the line or border colour"},
129
129
  "draw.setExtend": {"summary":"Continue a line to the pane edge"},
130
- "draw.setFillColor": {"summary":"nothing"},
131
- "draw.setFrom": {"summary":"nothing"},
130
+ "draw.setFillColor": {"summary":"Change a box's or polyline's fill"},
131
+ "draw.setFrom": {"summary":"Move a line's or box's first anchor"},
132
132
  "draw.setPoints": {"summary":"Replace a polyline's path"},
133
133
  "draw.setStyle": {"summary":"`\"solid\"`, `\"dashed\"` or `\"dotted\"`"},
134
- "draw.setText": {"summary":"nothing"},
135
- "draw.setTextColor": {"summary":"nothing"},
136
- "draw.setTo": {"summary":"nothing"},
137
- "draw.setTooltip": {"summary":"nothing"},
138
- "draw.setWidth": {"summary":"polyline, width)`"},
134
+ "draw.setText": {"summary":"Change a label's or box's caption"},
135
+ "draw.setTextColor": {"summary":"Change the text colour"},
136
+ "draw.setTo": {"summary":"Move its second anchor"},
137
+ "draw.setTooltip": {"summary":"Detail shown while the pointer rests on the object"},
138
+ "draw.setWidth": {"summary":"Change the line thickness"},
139
139
  "element": {"summary":"Read element `i`"},
140
140
  "ema": {"summary":"Exponential mean, weight `2 / (len + 1)`, seeded on bar `len - 1` with the simple average of those `len` values","warmup":"bar `len - 1`"},
141
141
  "eom": {"summary":"How far price moved per unit of volume","warmup":"bar `len`"},
@@ -332,7 +332,7 @@ export const LIBRARY_PROSE: Readonly<Record<string, LibraryProse>> = {
332
332
  "str.split": {"summary":"Split into parts"},
333
333
  "str.startsWith": {"summary":"Prefix test"},
334
334
  "str.substring": {"summary":"`from` inclusive, `to` exclusive, to the end when `to` is absent"},
335
- "str.trim": {"summary":"Leading and trailing spaces removed"},
335
+ "str.trim": {"summary":"Leading and trailing whitespace removed: the code points listed below"},
336
336
  "str.upper": {"summary":"Upper case, invariant, not locale dependent"},
337
337
  "sum": {"summary":"Total over the window","warmup":"bar `len - 1`"},
338
338
  "sumSkip": {"summary":"Total over the window, ignoring absent bars","warmup":"bar `len - 1`"},
@@ -156,6 +156,12 @@ export interface LibraryEntry {
156
156
  readonly whole: Readonly<Record<string, WholeRange>>;
157
157
  /** Arguments read once before bar 0, so a bar-dependent one is OS3003. */
158
158
  readonly constant: readonly string[];
159
+ /**
160
+ * Arguments the compiled format carries as a plain value, with no input
161
+ * reference beside it, so a setting written into one is OS3026 rather than
162
+ * folded to its default with nothing said.
163
+ */
164
+ readonly written: readonly string[];
159
165
  /** Pairs that set the same thing two ways, so giving both is OS3010. */
160
166
  readonly conflicts: readonly (readonly [string, string])[];
161
167
  }
@@ -171,6 +177,7 @@ export interface EntryOptions {
171
177
  readonly undeclared?: readonly string[];
172
178
  readonly whole?: Readonly<Record<string, WholeRange>>;
173
179
  readonly constant?: readonly string[];
180
+ readonly written?: readonly string[];
174
181
  readonly conflicts?: readonly (readonly [string, string])[];
175
182
  }
176
183
 
@@ -327,6 +334,7 @@ export function entry(signature: string, options: EntryOptions = {}): LibraryEnt
327
334
  undeclared: options.undeclared ?? [],
328
335
  whole: options.whole ?? {},
329
336
  constant: options.constant ?? [],
337
+ written: options.written ?? [],
330
338
  conflicts: options.conflicts ?? [],
331
339
  };
332
340
  }
@@ -73,6 +73,22 @@ export function libraryNames(): readonly string[] {
73
73
  return [...INDEX.keys()];
74
74
  }
75
75
 
76
+ /**
77
+ * Every name the library lets a script call, for OS2010's suggestion.
78
+ *
79
+ * OS2010 is a call to something that is not a function, so the name it offers
80
+ * has to be one that is: offering `blue` for `volume(20)` would hand the reader
81
+ * a second OS2010. A planned name is left out too, because calling it is OS2020,
82
+ * and so is an order function outside a strategy, because calling it is OS7001.
83
+ */
84
+ export function libraryFunctionNames(inStrategy: boolean): readonly string[] {
85
+ return [...INDEX.entries()]
86
+ .filter(([, entries]) =>
87
+ entries.some((one) => one.callable && !one.planned && (inStrategy || !one.strategyOnly)),
88
+ )
89
+ .map(([name]) => name);
90
+ }
91
+
76
92
  /** The members of one namespace, spelled bare, for OS2009's suggestion. */
77
93
  export function membersOf(namespace: string): readonly string[] {
78
94
  const prefix = `${namespace}.`;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The canonical encoding, `compiled-program.md` 2.14.
2
+ * The canonical encoding, `compiled-program.md` 2.14, and the one number writer.
3
3
  *
4
4
  * It exists so that a hash of a program means something: two compilers, in two
5
5
  * languages, handed the same source, produce the same bytes. So none of the
@@ -9,8 +9,8 @@
9
9
  * - Object keys sorted ascending by Unicode code point. Sorted rather than in
10
10
  * the specification's listed order, because a sort is a rule an emitter in any
11
11
  * language can follow without a table.
12
- * - A number is the shortest decimal string that reads back as the same
13
- * binary64 value, with an exponent written as `e` and an optional `-`.
12
+ * - A number is written by the rule of `language.md` 5.5, which is the same
13
+ * rule for every number that becomes text anywhere in the language.
14
14
  * - A string escapes only the quote, the backslash and the code points below
15
15
  * 0x20, the last as `\u00XX` except for `\n`, `\r` and `\t`.
16
16
  */
@@ -18,16 +18,74 @@ import type { CompiledProgram } from './program.js';
18
18
  import { sha256 } from './sha256.js';
19
19
 
20
20
  /**
21
- * A number, shortest round trip.
21
+ * A number as text: the one writer.
22
22
  *
23
- * The host's own shortest form is already the shortest that reads back
24
- * identically. What it is not is the spelling the specification asks for: a
25
- * positive exponent is written with a `+` that the format does not allow, so
26
- * that one sign is removed and nothing else is touched.
23
+ * Two engines compare numbers as bits, but they compare text in a case file, an
24
+ * expected column, a table cell and `text(x)`, so how a number becomes text has
25
+ * to be one rule that both implement, and in this repository it has to be one
26
+ * function. This is that function. `scripts/check-number-writer.mjs` reads
27
+ * every file under `src` and refuses a conversion outside this module, so a
28
+ * number cannot reach text by a host's default somewhere else and disagree with
29
+ * this one by a plus sign.
30
+ *
31
+ * **The digits are the host's and the layout is not.** The shortest decimal
32
+ * digit string that reads back as the same binary64 is what every host this
33
+ * engine runs on produces, and what a second engine's host produces too; that
34
+ * part is taken from the host's own shortest form. Where the two hosts part
35
+ * company is the layout, when a value is written positionally and when with an
36
+ * exponent, and how the exponent is spelled, so that half is written here from
37
+ * the rule in `language.md` 5.5 and not left to the host. The rule is the one
38
+ * the first host follows natively, minus the `+` it writes on a positive
39
+ * exponent, and `spec/vectors/number-text.json` holds the boundary cases a
40
+ * second engine checks itself against.
27
41
  */
28
42
  export function canonicalNumber(value: number): string {
29
43
  if (!Number.isFinite(value)) throw new Error('a compiled program holds finite numbers only');
30
- return String(value === 0 ? 0 : value).replace('e+', 'e');
44
+ // Zero and negative zero are one value to the language (compiled-program.md
45
+ // 3.1) and one spelling here.
46
+ if (value === 0) return '0';
47
+ const { digits, point } = shortestDigits(Math.abs(value));
48
+ return (value < 0 ? '-' : '') + layout(digits, point);
49
+ }
50
+
51
+ /**
52
+ * The shortest round trip digits of a positive finite magnitude, and where the
53
+ * decimal point falls: the value is `0.d1d2...dk` times ten to the `point`.
54
+ *
55
+ * Read out of the host's own shortest form rather than computed here, because
56
+ * the search for the shortest digit string is the one part of the conversion
57
+ * every host already agrees on. Whatever layout the host chose is undone: the
58
+ * digits and the point are all that is kept.
59
+ */
60
+ function shortestDigits(magnitude: number): { readonly digits: string; readonly point: number } {
61
+ const shown = String(magnitude);
62
+ const e = shown.indexOf('e');
63
+ const mantissa = e < 0 ? shown : shown.slice(0, e);
64
+ const exponent = e < 0 ? 0 : Number(shown.slice(e + 1));
65
+ const dot = mantissa.indexOf('.');
66
+ const whole = dot < 0 ? mantissa : mantissa.slice(0, dot);
67
+ const fraction = dot < 0 ? '' : mantissa.slice(dot + 1);
68
+ let digits = whole + fraction;
69
+ let point = whole.length + exponent;
70
+ // A positional form below one carries leading zeros that are not digits of
71
+ // the value, and a whole number carries trailing zeros that are its layout.
72
+ while (digits.startsWith('0')) {
73
+ digits = digits.slice(1);
74
+ point -= 1;
75
+ }
76
+ while (digits.endsWith('0')) digits = digits.slice(0, -1);
77
+ return { digits, point };
78
+ }
79
+
80
+ /** `language.md` 5.5: positional between the two thresholds, an exponent outside them. */
81
+ function layout(digits: string, point: number): string {
82
+ const count = digits.length;
83
+ if (count <= point && point <= 21) return digits + '0'.repeat(point - count);
84
+ if (0 < point && point <= 21) return `${digits.slice(0, point)}.${digits.slice(point)}`;
85
+ if (-6 < point && point <= 0) return `0.${'0'.repeat(-point)}${digits}`;
86
+ const exponent = point - 1;
87
+ const lead = count === 1 ? digits : `${digits[0]}.${digits.slice(1)}`;
88
+ return `${lead}e${exponent < 0 ? '-' : ''}${String(Math.abs(exponent))}`;
31
89
  }
32
90
 
33
91
  export function canonicalString(value: string): string {
@@ -23,6 +23,7 @@
23
23
  * what a script wants when it wraps an index. Collapsing them would silently
24
24
  * change one of the two.
25
25
  */
26
+ import { compareStrings } from './library/index.js';
26
27
  import type { Value } from './values/index.js';
27
28
  import { ABSENT, numberValue, valuesEqual } from './values/index.js';
28
29
 
@@ -99,12 +100,17 @@ function numeric(
99
100
  * absence is treated two ways, and it is deliberate: an operator that can
100
101
  * itself be absent gives a script no way to ask whether a value is absent at
101
102
  * all, so equality is the exception and every other comparison is not.
103
+ *
104
+ * **Two strings are ordered by code point** (`language.md` 9.3, `stdlib.md`
105
+ * section 10), through the one order `sort` uses, and not by the host's own
106
+ * operator, which orders by sixteen bit unit and puts a symbol outside the
107
+ * basic plane below the last thousands of the plane. Numbers take the host's
108
+ * operator, which is the binary64 order every engine shares.
102
109
  */
103
110
  export function compare(opcode: 'LT' | 'LE' | 'GT' | 'GE', a: Value, b: Value): Value {
104
111
  if (a === null || b === null) return ABSENT;
105
- const bothNumbers = typeof a === 'number' && typeof b === 'number';
106
- const bothStrings = typeof a === 'string' && typeof b === 'string';
107
- if (!bothNumbers && !bothStrings) throw new OperandMismatch(opcode);
112
+ if (typeof a === 'string' && typeof b === 'string') return ordered(opcode, compareStrings(a, b));
113
+ if (typeof a !== 'number' || typeof b !== 'number') throw new OperandMismatch(opcode);
108
114
  switch (opcode) {
109
115
  case 'LT':
110
116
  return a < b;
@@ -117,6 +123,20 @@ export function compare(opcode: 'LT' | 'LE' | 'GT' | 'GE', a: Value, b: Value):
117
123
  }
118
124
  }
119
125
 
126
+ /** An ordering comparison read off a three way order: negative, zero or positive. */
127
+ function ordered(opcode: 'LT' | 'LE' | 'GT' | 'GE', order: number): boolean {
128
+ switch (opcode) {
129
+ case 'LT':
130
+ return order < 0;
131
+ case 'LE':
132
+ return order <= 0;
133
+ case 'GT':
134
+ return order > 0;
135
+ default:
136
+ return order >= 0;
137
+ }
138
+ }
139
+
120
140
  /** `EQ` and `NE` are total: they always produce a boolean. */
121
141
  export function equals(a: Value, b: Value): boolean {
122
142
  return valuesEqual(a, b);
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Where the bars a run reads come from: an array of records, or columns.
3
+ *
4
+ * The bar cycle reads one bar at a time and does not care where its fields come
5
+ * from, so the representation is decided at the door and nowhere else. Two are
6
+ * accepted (issue 0005):
7
+ *
8
+ * - **Records**, one `HostBar` per bar, which is the live path: one bar arrives
9
+ * at a time and an array of one is absurd.
10
+ * - **Columns**, one array per field, owned by the host. A long history held as
11
+ * columns costs about a third of the memory of one object per bar and no
12
+ * allocation per bar at rest, and a store that already holds bars as columns
13
+ * can hand over typed array views with no parse at all. The engine reads
14
+ * `open[i]` when bar `i` executes and builds the one record that execution
15
+ * needs, which is gone by the next bar.
16
+ *
17
+ * **In a column, absence is `null` or `NaN`.** A typed array cannot hold `null`,
18
+ * and a price the host does not have is absent, never zero (`host-interface.md`
19
+ * 3.1), so `NaN` is the typed array's spelling of it. A time that is `NaN` is a
20
+ * bar with no time, which the hand-over refuses with OS6025 exactly as it
21
+ * refuses a record with none.
22
+ *
23
+ * **The number of bars is the length of `time`.** A shorter price, volume or
24
+ * interest column reads as absent past its end, which is the same thing a hole in
25
+ * a record is, and nothing is invented to fill it.
26
+ */
27
+ import type { HostBar } from './bars.js';
28
+
29
+ /** Numbers by bar index, absent written as `null` or `NaN`. */
30
+ export type BarColumn = ArrayLike<number | null>;
31
+
32
+ /** A history as columns, one per field of `host-interface.md` 3.1. */
33
+ export interface BarColumns {
34
+ readonly time: BarColumn;
35
+ readonly open: BarColumn;
36
+ readonly high: BarColumn;
37
+ readonly low: BarColumn;
38
+ readonly close: BarColumn;
39
+ readonly volume?: BarColumn;
40
+ readonly oi?: BarColumn;
41
+ }
42
+
43
+ /** What the bar cycle and the fold read: how many bars, and bar `i`. */
44
+ export interface BarSource {
45
+ readonly length: number;
46
+ at(index: number): HostBar | undefined;
47
+ }
48
+
49
+ export function isColumns(bars: readonly HostBar[] | BarColumns): bars is BarColumns {
50
+ return !Array.isArray(bars);
51
+ }
52
+
53
+ /** Either form, as the bar cycle and the fold read it. */
54
+ export function sourceOf(bars: readonly HostBar[] | BarColumns): BarSource {
55
+ return isColumns(bars) ? columnSource(bars) : recordSource(bars);
56
+ }
57
+
58
+ export function recordSource(bars: readonly HostBar[]): BarSource {
59
+ return { get length() { return bars.length; }, at: (index) => bars[index] };
60
+ }
61
+
62
+ function cell(column: BarColumn | undefined, index: number): number | null {
63
+ const value = column === undefined || index >= column.length ? null : column[index];
64
+ return value === null || value === undefined || Number.isNaN(value) ? null : value;
65
+ }
66
+
67
+ export function columnSource(columns: BarColumns): BarSource {
68
+ const length = columns.time.length;
69
+ return {
70
+ length,
71
+ at(index: number): HostBar | undefined {
72
+ if (index < 0 || index >= length) return undefined;
73
+ return {
74
+ time: cell(columns.time, index),
75
+ open: cell(columns.open, index),
76
+ high: cell(columns.high, index),
77
+ low: cell(columns.low, index),
78
+ close: cell(columns.close, index),
79
+ volume: cell(columns.volume, index),
80
+ oi: cell(columns.oi, index),
81
+ };
82
+ },
83
+ };
84
+ }
85
+
86
+ /**
87
+ * The bars a run has been handed: a whole history, then whatever arrives live.
88
+ *
89
+ * `run` replaces the history; `append` and `update` write one bar over it. A
90
+ * bar written live is held as the record it arrived as, which is the only form
91
+ * a live bar has.
92
+ */
93
+ export class KnownBars implements BarSource {
94
+ private base: BarSource = recordSource([]);
95
+ private readonly live: HostBar[] = [];
96
+
97
+ get length(): number {
98
+ return Math.max(this.base.length, this.live.length);
99
+ }
100
+
101
+ at(index: number): HostBar | undefined {
102
+ return this.live[index] ?? this.base.at(index);
103
+ }
104
+
105
+ set(index: number, bar: HostBar): void {
106
+ this.live[index] = bar;
107
+ }
108
+
109
+ reset(base: BarSource): void {
110
+ this.base = base;
111
+ this.live.length = 0;
112
+ }
113
+ }