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
package/CHANGELOG.md CHANGED
@@ -7,6 +7,1056 @@ nothing, fails the build before it can become permanent.
7
7
 
8
8
  ---
9
9
 
10
+ ## 0.6.0
11
+
12
+ **An engine that implemented nothing passed the conformance suite, and now does
13
+ not.** Every case in the tree declared the `strategy` profile, so an engine
14
+ claiming `core` was handed none of them: every case was skipped, the runner
15
+ printed "Suite passed" and exited zero. The repository's own test asserted that
16
+ outcome against an adapter written to answer no case at all. If you have been
17
+ using a passing suite run as evidence about a `core` engine, it was not one.
18
+
19
+ `core` cases now exist, twenty three at first across `lexical`, `syntax`,
20
+ `static`, `runtime` and `limits`, and fifty two by the end of this entry.
21
+ Nothing in the runner changed to make this work: its rules were already right,
22
+ and what was missing was cases for them to apply to.
23
+
24
+ **An engine reporting `engineOnly` is no longer handed compiler-diagnostic
25
+ cases.** Section 8 of `conformance.md` always said it should not be, and the
26
+ runner did not implement it, which nothing noticed while no such case existed.
27
+ Which categories need a compiler is now a column of section 7's table, read by
28
+ the runner, so a category added without an answer in that column stops the
29
+ suite rather than being handed to an engine with no compiler.
30
+
31
+ **Behaviour that changed under you, first engine.** Read these before upgrading
32
+ a deployment whose numbers you have already checked.
33
+
34
+ - **A `"lookahead"` read in a backtest now reads a higher timeframe bar's final
35
+ value from its first bar**, as `stdlib.md` 15.3 always said. The backtest
36
+ appended bars one at a time, so a lookahead read answered the bucket so far,
37
+ which is the developing reading. Confirmed and developing reads are
38
+ unchanged. A lookahead backtest now looks as smooth as the mode says it will.
39
+ - **Dates before about 1 BCE were one day early.** The day count applied the
40
+ negative era adjustment twice. No modern date moves.
41
+ - **Some input that was accepted is now refused, each with a code and a fix.**
42
+ An `input()` in a field fixed before bar 0 that is not the whole of the value
43
+ is OS3025 (arithmetic, a ternary or a colour call over a setting used to
44
+ compile and then fail inside the emitter). A plot's `style` written from an
45
+ input is OS3026 (it used to be folded silently to its default). A bar handed
46
+ over with no time is OS6025. The chart adapter refuses a second declared grid
47
+ and a band coloured per bar with OS6024 before any bar runs, where it used to
48
+ drop them in silence.
49
+ - **A cell outside its grid is OS4008**, naming the row, the column and the
50
+ grid's shape, where it was the array code OS4004 with a flattened count. A
51
+ name or an array still holding an object deleted earlier warns OS8019.
52
+ - **The editor's hover text for six drawing calls was wrong**: `draw.setFrom`
53
+ said "nothing" and `draw.delete` said "box", because the generator split
54
+ `stdlib.md`'s rows on the pipe inside `line | box`. Fixed at the generator and
55
+ in the pages.
56
+
57
+ **New.**
58
+
59
+ - **`engine.run` takes a history as columns**, one array per field, typed arrays
60
+ included, beside the record form. Over 900,000 one minute bars that held 43 MB
61
+ against 104 MB and peaked at 346 MB of heap against 877 MB.
62
+ `docs/integrating/running-the-engine.md` has the shape.
63
+ - **`importScript`**, from the main entry point, imports a script written in the
64
+ version-annotated chart dialect, versions 5 and 6. Each statement is translated
65
+ with its original meaning, translated with a warning stating the difference,
66
+ or kept as a comment with an error; the output is compiled before it is
67
+ returned. Twelve codes in a new range, OS9001 to OS9012, and a new catalogue
68
+ stage, `import`. `docs/writing/importing-a-script.md` lists what is translated
69
+ and what is refused.
70
+ - **A documentation site.** `npm run site` builds every page of `docs/` and
71
+ `spec/`, and a page per catalogue code, with every address relative.
72
+ `scripts/check-site.mjs` fails the build on a link or anchor that leads
73
+ nowhere.
74
+ - **A conformance badge.** `npm run badge` makes one from a passing result
75
+ document, and refuses anything that is not a pass of the profile it claims.
76
+ The suite revision it names is now the package version and a digest of every
77
+ case file (`conformance.md` section 11), so a result names the exact cases it
78
+ was run against.
79
+
80
+ **If you read the catalogue programmatically**: `spec/errors.json` has 173
81
+ entries, a ninth range, OS9xxx, whose severity is "error or warning", and a
82
+ sixth stage, `import`. OS4008, OS8019, OS3025, OS3026, OS6024 and OS6025 are
83
+ raised; OS6012 is now raised only where a fact cannot default, and a bare read
84
+ of an instrument fact the host did not state is absent.
85
+
86
+ **The suite now measures what it exists for.** Four channels that no engine
87
+ answered now have a definition in `conformance.md` section 4 and an answer from
88
+ both engines: `values`, one value per bar per plot; `log`; `drawings`; and
89
+ `table`. There are 106 cases, 52 `core`, 46 `chart` and 8 `strategy`, and the
90
+ `semantics`, `numerics`, `time`, `surface` and `external` categories hold real
91
+ cases for the first time, each with expected values computed independently of
92
+ both engines. A read of another instrument is served from the case's own
93
+ `bars.<SYMBOL>.csv`. `npm run suite:agree` passes 87 cases between the two
94
+ engines exactly, the other 19 being compiler-diagnostic cases an engine-only
95
+ implementation skips.
96
+
97
+ **The second engine now holds every library entry the first does**, 251 of
98
+ 251, checked on every build by `scripts/check-manifests.mjs`: arrays, `print`,
99
+ the calendar, the session calls, drawing objects, grids, and higher timeframe
100
+ and other instrument reads in all three modes. Two disagreements between the
101
+ engines were found by the new cases and settled in the specification: the
102
+ `and` and `or` tables are total (decision 71), and the drawing and grid channels
103
+ have a written shape (decision 72).
104
+
105
+ **The installed Python engine can say what it is.** `python -m openscript
106
+ --describe` read the engine's name and version from `pyproject.toml` beside the
107
+ package, and an install never carries that file, so on every installed copy,
108
+ 0.5.0 from the index included, it refused, and no host could run the conformance
109
+ suite against the engine it had actually installed. It now reads the record the
110
+ installer wrote beside the package, and still reads the file in a checkout. A
111
+ `pyproject.toml` above an installed package that names some other project is
112
+ ignored rather than reported as this one, and two installed records beside one
113
+ package, which a broken upgrade leaves, are refused rather than one picked.
114
+
115
+ **The Python engine is published by a workflow.** 0.6.0 is the first version
116
+ uploaded by `release-pypi.yml` rather than by hand. Before anything is uploaded
117
+ it builds the wheel, checks every source module is in it and nothing else is,
118
+ installs it into a fresh interpreter, and loads a program compiled by this
119
+ release's compiler and checks every value. It publishes through trusted
120
+ publishing, with no stored token, and each file on the index carries a signed
121
+ attestation of the workflow run that built it.
122
+
123
+ **What this still does not prove.** Both engines were written in this
124
+ repository, by the same hands, from the same pages. Their agreement is evidence
125
+ about this repository and not yet about the specification: that needs an engine
126
+ written by somebody who has read only the pages, which is the gate of Phase 7
127
+ and cannot be met from inside.
128
+
129
+ ---
130
+
131
+ ## 0.5.0
132
+
133
+ **`pow` has no library vector, and the reason is the same one.** A vector is a
134
+ bit pattern a second engine is written to match, and `pow` returns different
135
+ bits on two runtimes of the same virtual machine: publishing one hands that
136
+ second engine a test it cannot pass and this engine cannot keep. The vectors
137
+ were added after the last release, so this is the first build to check them
138
+ anywhere but where they were made.
139
+
140
+ `compiled-program.md` 8.3 already requires that the transcendental functions not
141
+ use the platform's maths library, and the source records that they do until the
142
+ portable algorithm it names exists. Everything in that group is provisional in
143
+ the last bit; only `pow` has been measured to differ, so only `pow` is held out,
144
+ and the index says why. The rest keep their vectors and the exposure is written
145
+ down where the next one goes when it is measured.
146
+
147
+
148
+ **A figure in the specification was one machine's reading.** Section 20.7 said
149
+ how many powers of ten a host's `pow` gets wrong and which one, and a test
150
+ measured it again on every build, which is the rule this project has for a
151
+ printed figure. Both numbers turned out to belong to the host rather than to the
152
+ language: the same engine misses a single count on one runtime of its virtual
153
+ machine and thirty six on another, and the two sets do not overlap. The claim
154
+ that matters, that a floating point power is not the nearest binary64 and that
155
+ the difference reaches `round`, is true on both and is what is now printed and
156
+ measured. The witnesses the two rounding tests use are found on the host running
157
+ them rather than written down, because a literal witness demonstrates the claim
158
+ on the machine it was written on and nothing on the next one.
159
+
160
+
161
+ **A chart can draw a strategy, not just a study.** `descriptorFor` takes
162
+ `simulateOrders`, and with it a program that places orders runs in the chart
163
+ tier against the venue a backtest uses: its plots draw, its legend row and its
164
+ settings dialog follow, and its position is right because the venue's frames
165
+ reach the engine between bars. Until now a host had two choices, refuse the
166
+ strategy or hand it a destination that answered nothing, and the second draws a
167
+ strategy that never learns it holds anything: every close closes nothing, every
168
+ entry is allowed again on the next signal, and a stop and reverse script
169
+ measured five buys and no sells while looking entirely normal.
170
+
171
+ It is the backtest's own `Simulator` rather than a second one written for
172
+ charts, so the marks a trader sees on the price and the trades in the report of
173
+ the same script are one answer. A test holds the two together: the position the
174
+ chart ends on and the open size the report states are compared directly.
175
+
176
+ **Off unless asked for.** Without `simulateOrders` a strategy with nowhere to
177
+ send an order is still refused at load with OS6006, which is what a host that
178
+ meant to wire a destination and forgot needs to be told. Supplying `orders`
179
+ still wins over it: somewhere real to send an order is a better destination than
180
+ a simulated one. Nothing is placed anywhere by this.
181
+
182
+
183
+ **The Python engine is on the index.** `pip install openscript` gets the engine
184
+ that runs a compiled program, Apache-2.0, zero dependencies, Python 3.12 or
185
+ newer. Until now the only way to have it was to clone this repository and point
186
+ an environment variable at a directory, which meant a platform built from a
187
+ container image could not run a strategy at all, and anyone attempting the
188
+ second half of Phase 6 had nothing to install. `RELEASING.md` carries the
189
+ release, beside the npm one, and the two versions ship together because
190
+ `check-python.mjs` holds them equal.
191
+
192
+ `engine/README.md` is the page the index shows: what the package is, that it
193
+ holds no compiler and is handed a compiled program, that nothing in it builds
194
+ code out of text, and how a host drives it bar by bar. It was written because
195
+ the first build had no long description at all and the page would have been
196
+ blank, which is permanent for a version once it is up.
197
+
198
+ **The Python distribution shipped one package out of six.** `[tool.setuptools]
199
+ packages` named `openscript` alone, so an install carried the machine and none
200
+ of the halves it calls: `import openscript` worked and
201
+ `from openscript.adapter.serving import Serving` did not. A host that followed
202
+ `docs/integrating/the-python-engine.md` and installed the directory got an
203
+ engine that could not run anything, and the failure appeared at their first
204
+ import rather than anywhere in this build.
205
+
206
+ It was found by doing it: installing the engine into a platform and watching the
207
+ adapter go missing. Nothing here could have caught it, because every test in this
208
+ repository runs the package from the tree where all six directories are present
209
+ whether or not the distribution would have carried them.
210
+
211
+ `scripts/check-python.mjs` now compares the package list against the packages
212
+ that exist, in both directions: a directory holding an `__init__.py` that the
213
+ list omits is refused, and a name in the list that is not a package in the tree
214
+ is refused too. A new subpackage is shipped because it exists, not because
215
+ somebody remembered a line.
216
+
217
+ **The phase the language was designed for is now scoped.** A strategy trades one
218
+ instrument today, chosen by the host before the run starts. The surface for more
219
+ than one has been designed and marked planned since `stdlib.md` was written:
220
+ `leg.fixed` and `leg.relative` declare what each leg trades, the `leg.*` rules
221
+ manage one, and the `book.*` rules reason across all of them. None of it
222
+ executes, and a script calling any of it is refused at the call with OS2020.
223
+ `ROADMAP.md` now carries Phase 8, which says what building it involves, in the
224
+ order it has to be built, and the four questions that have to be answered before
225
+ any of it is written. It is placed after Phase 7 deliberately, and the reason is
226
+ in the phase: every rule in it is behaviour a third engine has to reproduce
227
+ exactly, so designing it before the format is fixed means discovering the
228
+ disagreements one at a time in somebody else's engine.
229
+
230
+ The risk rules that a combination of contracts needs are the point of it. A
231
+ position made of two or more derivative contracts has a risk profile belonging to
232
+ the combination and not to any leg, so a stop placed per leg both fires on moves
233
+ the combination absorbed and misses the ones it did not. Two independent
234
+ single-instrument strategies are not a substitute for one multi-leg strategy;
235
+ they are two strategies running at the same time.
236
+
237
+ **Six capabilities that were missing rather than planned now have rows.** The
238
+ feature matrix said nothing at all about an account-level drawdown halt, a
239
+ position size cap, a cap on orders per session, a halt after consecutive losing
240
+ sessions, indexed access to past trades, or a script stating whether it is
241
+ evaluated on every update or only on a closed bar. Every one of them is ordinary
242
+ in the prior art this language is measured against, and a gap nothing records is
243
+ a gap nobody plans. They are `planned` with no section, which is what that status
244
+ is for.
245
+
246
+ **The second engine's host surface is documented, and proved.** `engine/openscript/run.py`
247
+ has held everything a live runner needs since the engine was written, and no
248
+ document mentioned it once: a host reading `docs/integrating/the-python-engine.md`
249
+ concluded that the only way to use this engine was to hand it a case directory,
250
+ because the one entry point that page named was the conformance adapter. Phase 6
251
+ of the roadmap is a server-side engine running the same compiled program, and the
252
+ surface that makes it possible was invisible to the people it exists for.
253
+
254
+ `docs/integrating/running-a-strategy.md` is the page a platform engineer reads
255
+ instead. It states plainly that the engine is handed a compiled program and never
256
+ a script, and what that means for a server that cannot run the compiler: the
257
+ program is compiled where the compiler runs and stored as data. Then `load` and
258
+ `load_text` and what a refusal carries, `execute_bar` argument by argument, what a
259
+ `BarResult` holds, the rollback a still-moving bar rests on and the single
260
+ condition it needs, the order boundary with `adapter/ordering.py` as the worked
261
+ reference, a worked example that runs, and a list of what the surface does not
262
+ give a host: no scheduling, no process isolation, no persistence, no data feed, no
263
+ destination, no compiler and no chart.
264
+
265
+ `engine/tests/test_host_surface.py` is the other half. It drives the engine the way
266
+ a live host does, with no conformance adapter in the loop: a program loaded from
267
+ canonical text, bars pushed one at a time, the order calls read back and placed on
268
+ a ledger, a frame folded in at a bar boundary, and a moving bar executed three
269
+ times that accumulates once. Every existing test drove this engine in batch, so
270
+ nothing held the surface a live host actually uses.
271
+
272
+ **And the record says what is true.** The registry page claimed there was no second
273
+ engine and no case files; both have existed for some time, and it now says the one
274
+ thing that is still true, which is that no engine anybody else wrote has run the
275
+ suite. The trading-mode sense of "paper" is gone from the documentation, the
276
+ specification and the error catalogue, and `docs/running/paper-and-live.md` is now
277
+ `docs/running/sandbox-and-live.md`: this platform maintains sandbox mode and
278
+ analyzer mode and now says so everywhere. The trading sense of "arm" is gone from the language itself, not
279
+ only from the prose: `leg.trail`'s and `book.lockProfit`'s `arm` parameter is
280
+ now `activateAt`, and the events `trailArmed` and `lockProfitArmed` are now
281
+ `trailActivated` and `lockProfitActivated`, which is the word the event table
282
+ already used in `trailToEntryActivated`. Both names are marked planned, so no
283
+ script running today is affected. A `switch` arm and a ternary arm are language
284
+ vocabulary and are untouched.
285
+
286
+ **Three cases where the destination behaves badly.** Every one of the 204 frames
287
+ the suite held was an order working and then filling whole: no partial fill, no
288
+ refusal, no cancellation, no expiry, and no frame arriving later than the bar
289
+ that placed its order. Two engines agreeing over that agreed about the half of a
290
+ day that costs nobody anything. Three cases are harvested from the crossing
291
+ example against a destination told to behave badly on a schedule stated before
292
+ the run, and each one reaches a shape `stdlib.md` 17.7 and 17.8 provide for and
293
+ no case carried.
294
+
295
+ - `order/partial-fill`. The first entry is acknowledged with nothing filled,
296
+ reports 400 of its 1084 units two boundaries later and the rest five
297
+ boundaries after it was taken, and the close that ends the trade arrives in
298
+ two pieces as well. The first trade has two entries and two exits where every
299
+ other trade in the suite has one of each, its entry price is the average over
300
+ two pieces filled at two prices, and the run is charged for fourteen fills
301
+ where the same run against a destination that fills whole is charged for
302
+ twelve. An engine that reads a cumulative quantity as a delta folds 1484 units
303
+ onto a 1084 unit order.
304
+ - `order/ended-unfilled`. The first three entries are refused carrying the
305
+ destination's own text, expire, and are cancelled, each after being
306
+ acknowledged and each with nothing filled. Because nothing fills, no position
307
+ opens on any of the three, the crossing back finds the strategy flat and sends
308
+ nothing, and the run reaches three trades where the plain run reaches six. It
309
+ is the only case in the suite carrying a `rejection`.
310
+ - `order/fold-after-terminal`. The first entry is cancelled a boundary after it
311
+ is taken and reported filled whole the boundary after that. The row ends
312
+ `cancelled` carrying 1084 filled and an average price, which is what 17.8
313
+ describes and why: a cancellation can race a fill at any destination, and an
314
+ engine that refuses the late frame leaves the account holding a position the
315
+ strategy cannot see.
316
+
317
+ The suite is eight cases now, 273 frames, 140 ledger rows and 66 trades. Across
318
+ it a frame says `working` 137 times, `filled` 132, `cancelled` twice, `rejected`
319
+ once and `expired` once, and a ledger row ends `filled` 131 times, `placed` five
320
+ times, `cancelled` twice, `rejected` once and `expired` once. Two of those
321
+ `working` frames carry part of an order rather than none of it, and five of the
322
+ `placed` rows are the order each run was holding when its bars ran out.
323
+
324
+ **A schedule that stops producing its status is refused rather than harvested.**
325
+ An act names an order by its ordinal and the run decides how many orders there
326
+ are, so a schedule can stop reaching its order without anything failing: the
327
+ case is still harvested, still passes, and tests whatever the plain run tests.
328
+ `scripts/lib/venue-schedule.mjs` holds each act to the frames the run recorded,
329
+ by the facts the act itself fixes and by the row it reached, with the status
330
+ vocabulary read out of `stdlib.md` 17.7 rather than copied into it. Twenty four
331
+ wrong schedules and wrong pages were put to it and all twenty four were refused:
332
+ every ordinal moved past the orders the run places, one act moved past the end
333
+ of the run, the plain run asked about a schedule it never ran under, the acts
334
+ written in reverse, a verb no word on the page is the stem of, a page with no
335
+ status table, a rejection text no frame carries, and a piece of 401 where the
336
+ run reported 400. The first version passed one of them, a fill of a whole order
337
+ answered by whichever later order happened to fill, which is why an act is now
338
+ held to the row its own order was answered about.
339
+
340
+ **The second engine now carries a frame's instant, and the suite is eight of
341
+ eight between the engines.** It did not when the three cases landed: three places
342
+ built a frame and left the column off, so every frame carried the instant of the
343
+ bar that placed the order and the three new cases failed on the second engine at
344
+ `orders[].updatedAt` and at nothing else, with the partial quantities, the
345
+ cumulative averages, the refusal text, the trades with two entries and two exits
346
+ and the whole summary agreeing exactly. The three were
347
+ `openscript/adapter/ordering.py`, where the driver builds the frame it delivers;
348
+ `tests/recorded.py`, whose `DeliveredFrame` had no field for the column and read
349
+ seven of the file's eight; and `tests/replaying.py`, where the replay that holds
350
+ the ledger to a case builds its own. Each now sets it, and an absent column still
351
+ leaves the ledger whatever instant the placement carried.
352
+
353
+ And the language's own `cancel(...)` is still exercised by nothing, because no
354
+ shipped example calls it: the cancellation in `order/fold-after-terminal` is the
355
+ destination's own and not the strategy's.
356
+
357
+ **A report says how far the run climbed, and which side made the money.** The
358
+ summary answered twenty six figures and could not answer three questions a
359
+ reader decides on. A run that made ten and gave back nine reports the same net
360
+ profit as one that made one and kept it, and no figure separated them. A run
361
+ whose long trades paid for its short ones reported a healthy net, because every
362
+ figure in the summary is folded over both sides at once. And how many times in a
363
+ row a strategy was wrong, which is the number that actually stops somebody, was
364
+ not derivable from the win rate, the drawdown or the trade count.
365
+
366
+ `maxRunUp`, `maxRunUpPercent` and `maxRunUpAt` join the summary, and `runUp` and
367
+ `runUpPercent` join every point of the equity curve. Run-up is measured from a
368
+ running trough anchored at the run's capital, which is the running peak's rule
369
+ rather than its mirror image: a trough anchored at the first reported point
370
+ would report the gain a run arrived with as having come from nowhere. The
371
+ fraction is **zero wherever that trough is not above zero**, and that asymmetry
372
+ with `maxDrawdownPercent` is deliberate. A peak only rises and stays above zero
373
+ throughout any funded run; a trough only falls, and an open position can lose
374
+ more than the account holds, so it reaches zero and passes it. Against a
375
+ negative basis a positive climb divides to a negative fraction, which is the
376
+ shape that once reported a profit factor of minus a half. `maxRunUp` itself is
377
+ unaffected and is the figure to read on such a run. The height and the depth
378
+ name different bars, and the earliest bar reaching either wins the tie.
379
+
380
+ `analysisOf` and the report's new `analysis` are the trades taken apart: the
381
+ closed trades split long against short with each side's own net and win rate,
382
+ the largest win and the largest loss as nets after charges, and the longest run
383
+ of wins and of losses. The sides partition the closed trades, so their counts
384
+ sum to `tradeCount` and their nets to `netProfit`. A streak is counted **in the
385
+ order the trades closed**, which differs from the order they opened whenever a
386
+ trade is held across another one's whole life, which is every strategy that
387
+ scales in; two trades closing on one bar are ordered by the order they opened,
388
+ so the answer does not depend on the order the list arrived in. A trade whose
389
+ net is exactly zero breaks a streak and extends neither half.
390
+
391
+ **What is proved, and what is not.** Run-up is in the `performance` channel, so
392
+ all five conformance cases compare it between the two engines exactly; that was
393
+ checked by removing it from one engine and watching every case fail. The trade
394
+ analysis is **not** in that channel, because the channel holds one flat object
395
+ and the side split is nested, so it is proved by unit tests on each engine and
396
+ by nothing that compares them. Whether it is flattened into `performance` or
397
+ becomes a channel of its own is left open rather than answered in passing.
398
+
399
+ **And the gap that was invisible is now closed.** Until this release the
400
+ summary's figures had no formula stated in any specification document: the two
401
+ engines agreed on them because one was translated from the other, not because a
402
+ sentence said what they are, so a third engine had nothing to be written
403
+ against. `conformance.md` section 4 now defines every field of the summary: the
404
+ words the formulas are written in, which trades each figure is counted over, the
405
+ equity basis the curve figures are folded from, which figures are absent rather
406
+ than zero and why, and the tie rules. The equity curve, drawdown and win rate
407
+ rows of `feature-matrix.md` section 30 move from `planned` to `specified` on the
408
+ strength of it.
409
+
410
+ The trade list does **not** move, and the section says why in its own text: how a
411
+ run folds the `trades` channel out of its fills is still unspecified, so that
412
+ channel is the boundary of what these formulas promise. An engine checking itself
413
+ against a case is handed every value they need; an engine folding a report out of
414
+ fills alone still has that earlier fold to agree on. `spec/decisions.md` 64 is the
415
+ minute.
416
+
417
+ **A case supplies the frames, and this engine folds them.** `conformance.md`
418
+ section 3 has said since it was written that `frames.csv` supplies order frames
419
+ the way `bars.csv` supplies bars, so that a case asserts the fold against input
420
+ the engine did not choose. This engine's adapter did something else: it re-ran
421
+ the case on its own simulated destination and held the frames that run answered
422
+ to the case's file byte for byte, reporting the case `unsupported` when the two
423
+ differed. So the suite could hold only cases whose frames this engine would have
424
+ produced anyway, which is why all 204 frames in it are an order working and then
425
+ filling whole, and why a partial fill, a rejection, a cancellation, an expiry
426
+ and a fill after a terminal status were all shapes the page provides for and no
427
+ case could carry. Two engines agreeing on that suite agreed about the half of a
428
+ destination's day that costs nobody anything.
429
+
430
+ `backtest` now has a second driver beside it, `backtestSupplied`, which delivers
431
+ the rows a case supplies and answers none of its own: no fill priced off a bar,
432
+ no order resting and no schedule read, so an order the frames say nothing about
433
+ stays where its placement left it. A case with no `frames.csv` still runs
434
+ against the simulated destination, because section 3 ends that such a case is
435
+ handed no frames at all. The two are one function underneath, so the window, the
436
+ refusals before the first bar, the boundary a frame is folded at and the record
437
+ are the same for both. A row naming an order the run never placed is delivered
438
+ and refused by the fold rather than dropped on the way in, which is what section
439
+ 3 hands an engine such a row for, and a row no boundary of the run delivers, one
440
+ after the last bar or one naming no bar, makes the case `unsupported` naming the
441
+ row rather than run with part of its own input passed over. `spec/decisions.md`
442
+ 63 is the minute, and section 3 now says where a frame naming no row is
443
+ answered.
444
+
445
+ The backtest module's door also exports `VenueAct`, `VenueDoes` and
446
+ `VenuePolicy` beside `Simulator` and `SimulatorOptions`. A host writing a
447
+ schedule for the simulated destination had to reach them structurally, through
448
+ `SimulatorOptions['fill']`, and a module's index is its only door.
449
+
450
+ **What this does not close.** The second engine reads a frame's instant out of
451
+ the file and does not put it on the frame it hands its ledger, so a case whose
452
+ destination answered later than the bar that placed the order is answered
453
+ differently by the two engines, by `updatedAt` and by nothing else: every other
454
+ field of the ledger, the trades and the whole performance summary agree exactly
455
+ on a case harvested and measured here. That line belongs to the file that owns
456
+ that delivery. `BacktestSettings` still states no schedule of its own, because
457
+ the field needs a row in a projection this stage does not own, and
458
+ `docs/integrating/running-the-suite.md` still describes the reading this change
459
+ replaces. None of this is a change to what an engine computes for a case that
460
+ supplies the frames its own destination would have answered: the five cases in
461
+ the suite pass in both modes, exactly, as they did.
462
+
463
+ **A frame carries the instant it arrived at.** `frames.csv` gains a `time`
464
+ column: the destination's own instant for that frame, UTC milliseconds, absent
465
+ as `none`, last in the row and optional in the way `orderRef` and `text` are.
466
+ `host-interface.md` 7.2 gives a frame that instant and `stdlib.md` 17.7 folds a
467
+ ledger row's `updatedAt` from it, so while no column carried one an engine
468
+ driven from a case file had nothing to move that field to and left it at
469
+ `placedAt`, while an engine answering its own frames carried the instant it
470
+ spoke. The two readings differ by one field, on exactly the rows whose
471
+ destination answered later than the bar that placed the order, and every frame
472
+ in the suite today arrives at the boundary that placed its own order, which is
473
+ why a suite made of them passed both. The column makes the instant input, like
474
+ every other byte of a case.
475
+
476
+ A record carries it too, because a projection can only write what the run kept:
477
+ `RECORD_VERSION` is 4, a record written before it reads with no instant on any
478
+ frame, and a later revision is still refused rather than read under this one's
479
+ rules. Both readers read the column, this engine's and the second engine's, and
480
+ `conformance.md` section 3 now says what a `time` means, that a case is not
481
+ required to state one, and why. `spec/decisions.md` 62 is the minute.
482
+
483
+ **What this does not close.** The five cases in the suite were harvested before
484
+ the column existed and carry the seven columns of the day, so they are stale
485
+ rather than wrong, and until they are harvested again `npm test` stops on them
486
+ at `scripts/harvest-cases.mjs --check`, which projects each run again and finds
487
+ eight columns where the file on disk has seven. `frames.csv` is the only file it
488
+ names. Measured with the five harvested again and nothing else changed: 1843 of
489
+ 1843 unit tests, the harvest check passes, and the two engines agree on 5 of 5,
490
+ exactly. The second engine reads the instant and does not yet carry it onto the
491
+ frame it hands its ledger, which is one line in the file that owns that
492
+ boundary. None of this is a change to what an engine computes: the ledgers, the
493
+ trades and the money of every case are what they were.
494
+
495
+ **The destination can be told to behave badly, on a schedule stated before the
496
+ run.** `SimulatorOptions.fill` now carries one: a list of acts, each naming the
497
+ nth order this destination took, how many boundaries after the one that took it
498
+ the act falls, and what it does. Four verbs, and between them they reach every
499
+ status of `stdlib.md` 17.7 a host may send. A fill carries a cumulative
500
+ quantity, so one verb covers the acknowledgement before anything has traded, a
501
+ fill of part of the order and a fill of the whole of it, and the other three are
502
+ the three ways an order ends carrying less than it asked for: refused, cancelled
503
+ and expired. An order the schedule names is answered by the schedule and by
504
+ nothing else, and a run that states no schedule is answered exactly as before,
505
+ which is what keeps every case already harvested the bytes it was harvested as.
506
+
507
+ There is no random number in it and there will not be one. `stdlib.md` 8.2 keeps
508
+ a script that answers differently on a second run out of a conformance suite,
509
+ and a venue that rolled a die would make every case harvested from it
510
+ unreproducible in the same breath. The venue works out the average over the
511
+ cumulative quantity itself, because that is the figure `stdlib.md` 17.8 step 3
512
+ says the row takes whole, and a destination reporting the last piece's price as
513
+ an average hands the engine a number that is not one, which the engine may not
514
+ correct because it is forbidden to average two averages of its own.
515
+
516
+ **What the suite had never been handed, measured rather than guessed.** Across
517
+ the five cases in this suite there are 204 frames: 102 that say `working` with
518
+ nothing filled and 102 that say `filled` with the whole order, and nothing else.
519
+ No frame reports part of an order, none arrives at a boundary later than the one
520
+ that took the order, and the words a host may send for a refusal, a cancellation
521
+ and an expiry appear no times at all. So the two engines are held to each other
522
+ over a destination that behaves perfectly, which is not the half of a day that
523
+ costs a trader money. The venue above is the half of the fix that could ship
524
+ here; the cases it can now produce cannot be added to the suite yet, for two
525
+ reasons that this release records rather than hides.
526
+
527
+ The first is this engine's own adapter. `conformance.md` section 3 says
528
+ `frames.csv` supplies frames the way `bars.csv` supplies bars, so a case can
529
+ assert the fold against input the engine did not choose, and a backtest here
530
+ answers its own frames from its own destination and can be handed none. The
531
+ adapter says so honestly, comparing the frames its run answered with the file
532
+ and reporting `unsupported` when they differ, so every case in the suite today
533
+ is one whose frames this engine's destination would have produced anyway. A case
534
+ about a destination that behaves badly is by definition not one of those.
535
+
536
+ The second is in the file. A frame carries a `time` under `host-interface.md`
537
+ 7.2 and `stdlib.md` 17.7 folds `updatedAt` from it, and `frames.csv` has no
538
+ column for one, so an engine folding a case's frames leaves that field at
539
+ `placedAt` where an engine answering its own frames carries the instant it
540
+ spoke. Every ledger row in the suite today has the two equal, because every
541
+ frame in it arrives after the bar that placed its own order, so no case can tell
542
+ the two readings apart. Section 3 now says this, and says what would close it.
543
+ The two engines were driven over three harvested cases carrying a partial fill,
544
+ an order filled over more than one bar, a refusal with the destination's own
545
+ text, an expiry, a cancellation and a fill arriving after a terminal status, and
546
+ they agreed on every figure of all three except that one field.
547
+
548
+ **The second engine has a home, and the gate covers both engines.** `engine/`
549
+ holds a Python distribution: the package `openscript`, importable as one name,
550
+ its tests beside it, and the tools that run them. It requires an interpreter and
551
+ nothing else, so a clone runs the tests as it stands, with no install and
552
+ nothing fetched from an index. The entry point the suite's adapter starts is
553
+ `python -m openscript`, which works from an installed distribution and from the
554
+ directory unchanged. `__init__.py` is the door, and it says what is where: the
555
+ machine, the two halves of the library, the orders, the money and the adapter,
556
+ each of which is an entry of its own below.
557
+
558
+ `npm test` now runs `scripts/check-python.mjs`, which finds an interpreter,
559
+ refuses one older than the distribution requires, reads every import in the tree
560
+ against the module names that interpreter says are its own, and then runs the
561
+ engine's tests under it. A missing interpreter fails the gate rather than
562
+ skipping half of it, because a suite that quietly checks one engine is how two
563
+ engines drift apart. The empty dependency list is therefore a measured fact
564
+ rather than a claim about a file, and inside the package the network, threads,
565
+ randomness and the locale are refused as well, each with the sentence from the
566
+ specification that refuses it.
567
+
568
+ **The no-eval check reads Python.** A `.py` file used to land in the check's
569
+ `unknown` pile and stop the build, deliberately, because code that nothing scans
570
+ is the one thing that check will not allow. It now has an arm of its own: a
571
+ masker that removes comments, marks string literals, reads a formatted string's
572
+ substitutions as code and normalises every identifier the way an interpreter
573
+ does, and rules that refuse the string evaluator, the statement executor, the
574
+ compiler underneath them, the import machinery driven by hand, objects loaded
575
+ out of bytes, function and code objects built at run time, the namespace of the
576
+ built-in names, a namespace taken as a dictionary, a process, and the modules
577
+ whose purpose is running text handed to them. `ast.literal_eval` is safe and is
578
+ allowed, and the rules are written so that it and an ordinary pattern builder
579
+ both pass untouched.
580
+
581
+ The identifier normalisation is the one with no counterpart on the JavaScript
582
+ side. An interpreter normalises a name before resolving it, so a call written in
583
+ mathematical or fullwidth letters is the same call to it and invisible to any
584
+ pattern written against plain letters. Three such spellings are in the attack
585
+ corpus, each one run under an interpreter before it was written down, and every
586
+ form in that corpus goes through the rules before the check opens a file.
587
+
588
+ **A Python test run leaves nothing behind and refuses to prove nothing.**
589
+ Bytecode caching is off before the first test module is imported, so no cache
590
+ directory appears in the tree, and the test count is a result rather than a line
591
+ of output: a discovery that found no tests, which the standard runner reports as
592
+ a pass, fails instead.
593
+
594
+ **A run record becomes a conformance case.** `caseFilesFrom(record, identity)`
595
+ returns the files of one case, keyed by the names `conformance.md` section 2
596
+ gives them: `case.json`, `script.os`, `bars.csv`, `expected.json` and
597
+ `instrument.json`, with `frames.csv` and `settings.json` where the run had
598
+ frames or inputs. It returns text and writes nothing, because core does no I/O,
599
+ so the caller decides where a case lives and the same call works in a browser.
600
+
601
+ This is what the suite the second engine will be measured against is built from.
602
+ A case written by hand asserts what somebody believed a run does; a harvested one
603
+ asserts what an engine actually produced, over bars that existed, under settings
604
+ somebody chose.
605
+
606
+ **Record version 2 carries the script's text**, in a new `sourceText` channel.
607
+ A record identified its script by hash, and a hash settles whether two files are
608
+ the same without yielding either of them, so a case could never be given the
609
+ `script.os` it is required to hold. The text is checked against that hash when
610
+ the record is written: text from a different revision than the one compiled is
611
+ refused, rather than written into a case that could not reproduce its own
612
+ expected output.
613
+
614
+ The text is on the record and not on the compiled program. A program is
615
+ executable data that no engine needs the source to run, and it is the versioned
616
+ artefact other implementations depend on; putting the text there would send a
617
+ script everywhere a program travels and widen the format every engine reads. The
618
+ compiled format is unchanged.
619
+
620
+ **Record version 3 carries the instrument record**, in a new `instrument`
621
+ channel: the twelve facts of `host-interface.md` 4.1 as the engine read them at
622
+ load. A record carried the money layer's contract, which holds six of them, and
623
+ the interval, the timezone, the session and the volume flag were handed to the
624
+ engine and written down nowhere, so `instrument.json`, which section 2 says is
625
+ that record, was the contract instead: a shape with a rounding digit count no
626
+ instrument record has and without the one fact that page requires of every
627
+ host. `backtest` takes `instrument` in its options, the six facts beside the
628
+ contract; the six the contract holds cannot be stated there, so the two cannot
629
+ disagree. A run whose host states no `hasVolume` still runs and still records,
630
+ and is refused a case rather than handed a value, because no derivation
631
+ recovers that flag and a case stating it would give the engine under test a
632
+ study the expected output did not come from.
633
+
634
+ **A record written before this still reads**, with the channels it never
635
+ carried absent: `sourceText` before version 2, `instrument` before version 3. An
636
+ earlier revision only ever has fewer channels, and every one it carries means
637
+ here what it meant when it was written. A later revision is still refused, which
638
+ is the asymmetry that matters: a later one may mean something new by a field this
639
+ version thinks it knows. Such a record replays and reruns as before; the one
640
+ thing it cannot do is become a case.
641
+
642
+ **A harvested case's `expected.json` is the shape section 4 fixes.**
643
+ `performance` is a list of one flat object, the summary statistics. The equity
644
+ curve, the monthly table and the trade markers are no longer written inside it:
645
+ the first two are not conformance channels, and a marker belongs to the
646
+ `markers` channel, which a case about money does not assert. A case now writes
647
+ exactly the channels it declares in `case.json` and no others.
648
+
649
+ **Specification decisions for the second engine.** Three, all in
650
+ `conformance.md` and `stdlib.md`, and each one was a place two implementations
651
+ could not have been written against the same page.
652
+
653
+ *The adapter is invoked once per case*, and the runner assembles the result
654
+ document. The page allowed both readings. Per-case is forced by the `error`
655
+ outcome, which covers a crash, a hang and a timeout: none of the three can be
656
+ reported by the program that suffered it, so only a caller holding a clock and a
657
+ child process can turn them into an outcome.
658
+
659
+ *An adapter also answers `--actual`*, writing what it computed with no
660
+ comparison. Section 10 requires comparing two engines channel by channel, and a
661
+ case result carries an outcome and a first difference rather than the values, so
662
+ two adapters both reporting `pass` proved only that each matched an expected
663
+ file, which is the thing that section says is not enough.
664
+
665
+ *The equity curve is not a conformance channel.* It is one value per bar derived
666
+ from fills and closes that the case already asserts, so it can only fail with the
667
+ channels it comes from or alone, and alone means the engines disagree about
668
+ arithmetic section 6 compares directly. It was also most of the bytes in a case.
669
+ Trade markers move to the `markers` channel, which already existed, and
670
+ `performance` is a list of one flat object.
671
+
672
+ **The transcendental gap is scoped out of conformance rather than solved.**
673
+ `exp`, `log`, `pow`, the trigonometric family and the three indicators built on
674
+ them have no portable reference algorithm, and `compiled-program.md` 8.3 forbids
675
+ answering them from the platform's maths library. No conformance case may assert
676
+ a value reaching them until one is written. They still compute what they always
677
+ did; what they do not carry is a cross-engine guarantee. The deciding fact was
678
+ deployment rather than theory: the first host installs across two processor
679
+ architectures and most common operating systems, so that one library is several
680
+ in practice, and the disagreement is two traders reading two numbers.
681
+
682
+ **A harvested case carries the frames the run was handed.** Without them the
683
+ case was unpassable on every engine, including the one that wrote it:
684
+ `conformance.md` section 3 ends "a case with no `frames.csv` is handed no frames
685
+ at all", and what `expected.json` asserts through its orders channel is what came
686
+ of those frames. An engine handed none folds nothing, disagrees with every row,
687
+ and takes the blame for a hole in the case. A frame names its intent by ordinal
688
+ rather than by an engine's own id, because a case cannot know the id another
689
+ engine minted.
690
+
691
+ `caseFilesFrom` and its types are exported from the package root, so an install
692
+ can reach the one function that turns a run into a case. `DriveOptions` and
693
+ `InstrumentFacts` are exported beside them, so a host can name what `backtest`
694
+ takes.
695
+
696
+ `backtest` takes `sourceText` and `instrument` in its options. A caller that
697
+ has only a compiled program leaves the first out, one that states nothing about
698
+ the instrument beside the contract leaves the second out, and either loses
699
+ nothing but the ability to harvest.
700
+
701
+ **The first conformance cases are in the tree, harvested rather than written.**
702
+ `scripts/harvest-cases.mjs` runs the shipped strategy examples over the Phase 5
703
+ gate's fixture, the placeholder contract and four hundred formula bars, under
704
+ the instrument facts `conformance.md` section 3 assumes of a case that states
705
+ none, read from that page, and writes each run into `cases/<id>/` through
706
+ `caseFilesFrom`, with a `notes.md` saying why the case exists and what it
707
+ defends against. The first two: `order/buy`, from the crossing strategy, and
708
+ `order/sell`, from the opening range strategy. The rows of
709
+ `feature-matrix.md` that name them are the first marked `implemented`, and the
710
+ gate's contract and bars now live in one module the reproducibility check and
711
+ the harvest share, so what the gate reproduces is what the suite holds.
712
+
713
+ **The harvest is a check as well as a writer.** Run with `--check`, which
714
+ `npm test` does, it writes nothing and fails the build when a case on disk is
715
+ not what this engine produces, byte for byte; when a case it wrote names a row
716
+ that does not say `implemented`; when an `implemented` row names a case
717
+ directory that is not there; or when a case directory exists that no row names.
718
+ Every script is harvested twice and the two compared before anything is
719
+ written. A script that reaches a gap of `stdlib.md` section 20.11 is refused by
720
+ name with the call that reached it, by the gate's own reading of that table,
721
+ which now lives in one module the gate's test and the harvest share; and a
722
+ strategy this driver cannot run is named and counted rather than passed over.
723
+ The short premium example reads a second instrument, which a backtest over one
724
+ series of bars cannot supply, so nobody has chosen a case identity for it, and
725
+ that is what the report names it for.
726
+
727
+ **The feature matrix is checked against the tests and the pages it cites.**
728
+ `scripts/check-matrix.mjs` enforces the preamble of `spec/feature-matrix.md`,
729
+ which described a checker nobody had written, and `npm test` runs it. Every
730
+ feature row has five cells and a status the page lists; every citation resolves
731
+ to a Markdown document under `spec/` and, where it carries a locator, to a
732
+ heading matching the shape the preamble gives; every test identifier is well
733
+ formed under a listed area and no two rows share one; an `implemented` row
734
+ names a test that exists, a `unit:` identifier written by a file under `tests/`
735
+ or a case directory holding its `case.json`; and a case directory no row names
736
+ fails the build. Existence is what it proves, and the unit runner and the suite
737
+ prove passing. A `specified` or `planned` row naming a case that is not written
738
+ is not a failure, which is the direction the preamble's paragraph on
739
+ `conformance.md` settles: such rows reserve an identifier, and they are counted
740
+ and printed rather than failed. The status words, the column names, the heading
741
+ shapes and the areas are read out of the page rather than retyped, every rule
742
+ is attacked with a row it must refuse and one it must accept over a fabricated
743
+ document and tree before a row is read, and a run that reads no row refuses.
744
+ It prints the row count, the count per status and the implemented ratio, which
745
+ is the number the page says nobody types. The preamble now names the checker,
746
+ says `none` is written in backticks, says what makes a unit test or a case
747
+ exist, and no longer says every row is unimplemented.
748
+
749
+ **This engine has a conformance adapter, and the suite has a runner.**
750
+ `scripts/adapter.mjs` is the program `conformance.md` section 9 says an
751
+ implementation ships, answering the three invocations that page gives:
752
+ `--describe` for the engine's identity, a case directory for one case result,
753
+ and `--actual` for what the engine computed with no comparison made. It loads
754
+ the built package by its door and nothing behind it, reads a case directory by
755
+ the file names section 2's table gives and refuses a file the table does not
756
+ name, compiles `script.os`, runs it over `bars.csv` under `instrument.json` and
757
+ `settings.json`, and answers the channels the case asserts in the encoding
758
+ section 4 gives them. The channels are read out of the same projection the
759
+ harvest writes a case with, so what this engine can be held to is stated once.
760
+
761
+ `scripts/run-suite.mjs` walks `cases/`, invokes an adapter once per case in a
762
+ child process with a timeout, turns a crash, a hang, a timeout or an answer
763
+ that is not one JSON object into the `error` outcome with the reason in the
764
+ row, and writes the result document of section 9. Both modes of section 10 are
765
+ there: against the expected files, and `--against` a second adapter, where each
766
+ is asked for `--actual` and the runner compares the two channel by channel with
767
+ tolerance zero, whatever the case declares. A case outside the claimed profile
768
+ is skipped and never counted as a pass; any failing, erroring or unsupported
769
+ case exits non-zero. Every harvested case passes on this adapter, alone and
770
+ against itself. `docs/integrating/running-the-suite.md` is the page.
771
+
772
+ **The comparison of section 6 is written once**, in `scripts/lib/compare.mjs`,
773
+ and the adapter and the runner both call it: absence first and never inside a
774
+ tolerance, a non-finite value as its own `nonFinite` outcome, signed zero
775
+ normalised, equality over the binary64 bits rather than a decimal rendering,
776
+ exact by default, and the `max` form of the two bounds with the bound that was
777
+ broken named. A declared tolerance is refused, not clamped, past the cap the
778
+ page prints or without a reason. Every step has a test written against the
779
+ implementation that would get it wrong, and each was run against that mutant.
780
+
781
+ **What the adapter says it does not reach**, reported on the case rather than
782
+ passed over. This engine's backtest answers its own frames from a simulated
783
+ destination and takes none from a file, so the frames it answered are held to
784
+ the case's `frames.csv` byte for byte and a case whose frames the destination
785
+ did not answer is `unsupported`; every harvested case runs. A per-bar or chart
786
+ channel, a warning case, `ticks.csv` and a secondary series are `unsupported`
787
+ by name. A per-column tolerance is not read, because section 6 fixes no shape
788
+ for one. Two facts the page owed a place when the adapter was written: the
789
+ money rounding digit count, which `backtest.json` below now carries and which
790
+ the adapter still takes from the fixture every harvested case ran under until
791
+ it reads that file, and a currency for section 3's default instrument, without
792
+ which the money layer refuses a strategy case that states no `instrument.json`.
793
+ The suite revision has no fixed place either, and the document carries the
794
+ package version until it does.
795
+
796
+ **A case carries what its report was folded under.** `backtest.json` is a new
797
+ file of a strategy case, `conformance.md` sections 2 and 3: the money rounding
798
+ digit count, the charge schedule the host supplied or `null` for the
799
+ declaration's own, and the report window with `null` for an unstated bound. A
800
+ run under a supplied schedule or a narrowed window harvested to a case that said
801
+ nothing about either, so a second engine ran under other values and took the
802
+ blame, and no case file carried the digit count at all. The count is not in
803
+ `instrument.json` because `host-interface.md` 4.1 has no such fact, and the
804
+ three are not in `settings.json` because that file is the script's inputs keyed
805
+ by name. Every field of `BacktestSettings` now has a place in a case,
806
+ `caseFilesFrom` carries the table saying which, and a record whose settings hold
807
+ a field the table does not know is refused by name rather than written into a
808
+ case that ran under something it does not state. The two cases in the tree at
809
+ the time were re-harvested and gain the file; nothing else in them changed. Decision 61 has
810
+ the reasoning.
811
+
812
+ **A record past the tolerance cap makes no case.** Section 6 caps a declared
813
+ tolerance at `rel = 1e-9` and `abs = 1e-12`, and the runner refused a case past
814
+ it while nothing refused a record past it, so a harvest could write a directory
815
+ every runner errors on. `caseFilesFrom` now refuses such a record with OS6021,
816
+ the code the run refuses a setting with, and writes no file. The cap's two
817
+ figures are read out of the page by a test and held to the constants in core,
818
+ which cannot read the page itself. `CaseRefusal` gains `code`: the catalogue
819
+ code a refusal is filed under, or `null` for a refusal about what the record
820
+ holds, which no catalogue entry is about. `reason` is unchanged.
821
+
822
+ **How a number becomes text is one written rule, and one function.**
823
+ `language.md` 5.5 states it completely: the shortest round trip digits, written
824
+ positionally from ten to the minus seventh exclusive up to ten to the twenty
825
+ first exclusive and with an exponent outside that range, spelled `1e21` and
826
+ `1.5e-7` with never a plus, `0` for both zeros, and no spelling for a value that
827
+ is not finite. `canonicalNumber` from the emit module is the writer every number
828
+ goes through: `text(x)`, `text(x, decimals)`, the canonical encoding, and a
829
+ harvested case's csv files. `spec/vectors/number-text.json` carries fifty one
830
+ boundary cases as binary64 bit patterns, decimals and text, so an engine in
831
+ another language can hold its own writer to the rule without parsing this
832
+ repository's source. A new check, `scripts/check-number-writer.mjs`, asks the
833
+ type checker for the type of every operand under `src` and refuses a number
834
+ turned into text by the host anywhere else; the files that still format a
835
+ number for a human are recorded in `spec/number-text-exceptions.json` with an
836
+ exact count each.
837
+
838
+ **`text(x, decimals)` writes the shortest digits at every magnitude.** It used
839
+ to write the exact binary expansion of the rounded whole number inside the
840
+ scaling range and the shortest form past it, so `text(1152921504606846976, 0)`
841
+ gave `1152921504606846976` and now gives `1152921504606847000`, the same digits
842
+ `text(x)` gives the value. Only a scaled whole at or above 2 ** 53 is affected;
843
+ no price shaped value moves by a digit. The scale it multiplies by is now the
844
+ binary64 nearest to the power of ten rather than the host's `pow`, which on this
845
+ host is one ulp off at 23 decimals, so `text(3.0627e-8, 23)` no longer ends in a
846
+ stray 1, and 20.7 prints the measured figures beside the claim.
847
+
848
+ **`str.trim` and `toNumber` use a written whitespace set.** `stdlib.md` section
849
+ 10 now lists the twenty five code points with the Unicode White_Space property,
850
+ and both calls are implemented from the list rather than from the host's trim.
851
+ The one visible change: the byte order mark U+FEFF is no longer removed, and the
852
+ next line character U+0085 now is. A test walks every code point of the basic
853
+ plane against the table read out of the page.
854
+
855
+ **Strings sort by code point, as the specification always said.** `sort` on an
856
+ array of strings orders a symbol outside the basic plane after every code point
857
+ of the plane, where the host's own order put it before U+E000 to U+FFFF.
858
+
859
+ **Two corrections a script can observe, for anybody deciding whether to
860
+ upgrade.** The `<` family of operators on two strings orders by code point, as
861
+ `sort` does and `language.md` 9.3 always said, where it used the host's sixteen
862
+ bit units: a comparison between a symbol outside the basic plane and a code
863
+ point from U+E000 upward changes its answer, and no other pair of strings does.
864
+ `round(x, decimals)` scales by the binary64 nearest to the power of ten, the
865
+ scale `text(x, decimals)` uses, rather than the host's `pow`, so a value rounded
866
+ at 23 decimals can move by one unit in the last place and a value rounded at any
867
+ other count cannot. A stored run that did either reproduces to a different
868
+ number after the upgrade; nothing else changes. Decision 60 has the reasoning
869
+ for both.
870
+
871
+ **A program at a lower minor of the same format major loads.** The engine
872
+ required every table of its own minor, so a program compiled at format 1.0 was
873
+ refused by the 1.1 engine at load, at `requests`, with a message about a
874
+ malformed program. `compiled-program.md` 9.4 step 3 now says what 9.5 always
875
+ promised: a table a later minor added and an earlier program lacks reads as
876
+ empty, never as a refusal. A program at the engine's own minor or a later one
877
+ that omits a table is still refused, because section 2 says an empty table is
878
+ written and never omitted, and the version is what tells an older program from
879
+ a malformed one. Decision 56 has the reasoning.
880
+
881
+ **`loadText(text, options)` is the engine's text boundary.** A program that
882
+ arrives from outside the process as text is parsed, written out again through
883
+ the one canonical writer, and refused with OS6018 naming the character where
884
+ the two part if the text is not the canonical encoding of 2.14; the object then
885
+ goes through `load` so every later refusal applies in the same order.
886
+ `load(object)` is unchanged and is not held to canonicity, because an object
887
+ built beside the engine was never text. Section 13's first checklist line and
888
+ 9.4 step 1 now say the same thing (decision 57). The function lives in
889
+ `src/core/engine/load.ts` and is exported through both doors, the engine's and
890
+ the package's.
891
+
892
+ **The compiled format is held to its page by four checks.**
893
+ `check-format-tables.mjs` reads the instruction table of 4.13 and the tag table
894
+ of 2.2 out of the page and compares them with the compiler's opcode table and
895
+ tag list, probing a formula depth with several counts rather than reading it as
896
+ arithmetic. `spec/format-history.json` records, per released format version,
897
+ the field paths, opcodes and capability tags it defined and the sentence of
898
+ section 9 that justified the bump, and `check-format-additive.mjs` fails on a
899
+ field, opcode or tag the compiler gained that no version records and on one a
900
+ version records that the compiler lost, naming the path. `spec/corpus/` holds
901
+ the canonical encoding and `programHash` of every shipped example, and
902
+ `check-format-corpus.mjs` recompiles each one and compares the bytes, reporting
903
+ the first differing byte offset and the field path at it; the recompiled
904
+ program is given the corpus's own `compiler` stamp first, so a package bump
905
+ alone never fails it. A format change is now a deliberate act: record it in the
906
+ history, rewrite the corpus with `--write`, and review the diff.
907
+
908
+ **The named colours' channel values are published.** `stdlib.md` 11.1 said they
909
+ are fixed in the library manifest and are part of the conformance suite, and
910
+ they lived only in two source files. `spec/colours.json` now holds them, 11.1
911
+ names that file as the authority, and `check-colour-channels.mjs` holds the
912
+ compiler's table and the engine's table to it while stating no value of its
913
+ own. No value changed; what changed is that a second engine can now read them
914
+ from the specification.
915
+
916
+ **The library's arithmetic ships as vectors a second engine can load.**
917
+ `spec/vectors/library/` holds one JSON file per arithmetic function of the
918
+ manifest, keyed by name and argument count, with inputs and results as binary64
919
+ bit patterns (sixteen hex digits, sign bit first) rather than decimals, so
920
+ nothing about this repository's number formatting sits between another
921
+ implementation's arithmetic and this one's. Each function gets several cases:
922
+ the full 80-bar fixture the gate tests already use, the same with holes in every
923
+ series, a history shorter than any length, every constant argument absent, an
924
+ edge table for the stateless calls, and a bar with no volume, no session or no
925
+ tick where the function reads one; every case records the bar facts the
926
+ function read and the warmup index of every output. `index.json` names every
927
+ file and the 135 manifest entries in six groups that hold no arithmetic, each
928
+ with the reason. `docs/integrating/library-vectors.md` says how to decode, drive
929
+ and compare a file from another language, in under a page.
930
+
931
+ **A case that reaches a gap of `stdlib.md` 20.11 is written and marked, not
932
+ left out.** Twenty functions have such a case (the transcendental calls and what
933
+ is built on them, `hma`, `eom`, and the `ma` and `keltner` cases that select
934
+ `hma` by name), and each case carries the gaps its call reaches, read by the
935
+ gate's own reading of the table, so an implementer knows which numbers they are
936
+ not held to. `spec/decisions.md` 59 records why a vector is written where a
937
+ conformance case would be refused. `npm test` regenerates the directory and
938
+ fails on a byte that differs (`scripts/check-library-vectors.mjs`), so a vector
939
+ is never older than the engine, and a regeneration is a deliberate act committed
940
+ with the change that caused it.
941
+
942
+ **The second engine runs a strategy, and the build fails when the two engines
943
+ disagree.** `engine/` now holds an engine rather than a home for one: the
944
+ machine of `compiled-program.md` section 5, both halves of the library in the
945
+ accumulation order `stdlib.md` section 20 fixes, the order ledger and the fold
946
+ of section 17, and the money that turns fills into trades and a summary. The
947
+ conformance adapter joins them over a case directory: it reads `frames.csv` and
948
+ `backtest.json`, delivers each frame after the bar it names and folds it before
949
+ the next execution, applies the order calls a decided bar left behind, and
950
+ answers the `orders`, `trades` and `performance` channels beside `diagnostics`
951
+ and `values`.
952
+
953
+ Every harvested case passes on it, against the expected files and against the
954
+ first engine. That is four hundred bars a case, 104 ledger rows and 51 trades
955
+ folded into five summaries, reproduced to the last bit, with every figure here
956
+ read out of `cases/*/expected.json` rather than remembered.
957
+
958
+ **What was read from the pages, and what was not.** The behaviour is the
959
+ pages': the machine of `compiled-program.md` section 5, the fold and the ledger
960
+ of `stdlib.md` section 17, and the accumulation order section 20 fixes for every
961
+ library function, with the arithmetic held to `spec/vectors/library/` bit for
962
+ bit. The decomposition is not: the modules of `strategy/` and `accounting/`
963
+ fall one to one against the first engine's order ledger and money layer, name
964
+ for name, and much of the prose explaining them is that engine's prose. A reader
965
+ deciding what the gate is worth should have both halves of that. Two engines
966
+ agreeing to the last bit says this one computes what the other computes, which
967
+ is what a host needs and what the release is gated on. It does not say the pages
968
+ alone were enough to build an engine from: a page with a hole in it can be read
969
+ the same wrong way twice by somebody with the other engine open beside them, and
970
+ no suite can tell that apart from two readings that agree because the page is
971
+ complete. Only an implementation from the pages with nothing else to hand can,
972
+ and there has not been one. `docs/integrating/the-python-engine.md` says the
973
+ same on the page somebody reading the engine reaches.
974
+
975
+ **`npm test` runs the two engines against each other.** `npm run suite:agree`
976
+ hands every case to both adapters with `--actual` and compares what each
977
+ computed, channel by channel, at tolerance zero whatever the case declares. A
978
+ difference of one bit fails the build naming the case, the channel, the column
979
+ and both values. `conformance.md` section 10 calls a disagreement between two
980
+ engines a release blocker; this is the sentence made mechanical, and it is the
981
+ gate the phase is measured by. `npm run suite` and `npm run suite:engine` run
982
+ each engine against the expected files on its own.
983
+
984
+ One rule belongs to that comparison alone: a run where every case was skipped
985
+ now exits non-zero saying it compared nothing. A skipped case is one engine
986
+ honestly reporting the profile it claims, which is what the first mode is for,
987
+ but in the second mode it means neither engine was asked about a single case,
988
+ and a green line there is the evidence a suite with no cases in it would
989
+ produce.
990
+
991
+ **The second engine claims the `strategy` profile**, so a strategy case is run
992
+ rather than skipped, and every shortfall is named on the case with the
993
+ `unsupported` outcome: a chart channel it does not draw, a capability it does not
994
+ serve, a library function its manifest does not hold, a `ticks.csv`, a secondary
995
+ series, a calendar in a timezone it cannot read, or a frame delivered after the
996
+ last bar, which no fold boundary reaches. `docs/integrating/running-the-suite.md`
997
+ holds the whole list and says why a cumulative profile table has no word for an
998
+ engine that runs the money and draws nothing.
999
+
1000
+ **Three more cases, and a straight answer about how much the two engines
1001
+ agreeing proves.** The suite held two cases, and the script was the only thing
1002
+ that differed between them: no charge schedule, two rounding digits, the whole
1003
+ of the bars reported, no value stored for an input, one instrument and one entry
1004
+ at a time, in both. Between them, 47 ledger rows and 23 trades, and every frame
1005
+ in both was an order working and then filling whole. Three cases join them,
1006
+ harvested the same way from the same shipped strategies:
1007
+
1008
+ - `perf/report-window` reports 151 of the 400 bars, with a position open at each
1009
+ end of the window. The ledger, the trades and the realised profit are
1010
+ `order/buy`'s to the bit and the summary is not: the bars reported, the bars
1011
+ spent holding a position and the deepest drawdown all change, because every
1012
+ bar still executes and only the ones inside the window are reported.
1013
+ - `perf/money-digits` folds the opening range run under a rounding count of zero
1014
+ instead of two. Every figure is `order/sell`'s, which is the assertion: the
1015
+ count reaches the total of one fill's charges, half to even, and no other
1016
+ figure of the report. An engine reading `conformance.md` section 3 as every
1017
+ money figure being rounded to that count writes a net profit of -654 where
1018
+ this case says -653.55.
1019
+ - `input/host-values` runs the crossing strategy under three values a host
1020
+ stored for its inputs, and is the only case that carries a `settings.json`.
1021
+ Ten ledger rows and five trades, where the declared defaults give thirteen and
1022
+ six.
1023
+
1024
+ That harvest made the suite five cases, 104 ledger rows and 51 trades, each passing against
1025
+ the expected files on both engines and against the other engine exactly. Each
1026
+ one was mutation tested before it was committed: an engine that reports every
1027
+ bar supplied fails the window case at the bar count, one that never opens
1028
+ `settings.json` fails the input case on the length of the ledger, four rows
1029
+ where the case says ten, and one that rounds every money figure fails the digits
1030
+ case at the first trade. The one
1031
+ mutant that survives is an engine that ignores the digit count and rounds at
1032
+ two, and the case says so in its own notes rather than leaving it to be found.
1033
+
1034
+ **A case is a run, so one example can be more than one case.**
1035
+ `scripts/harvest-cases.mjs` now harvests every identity that names an example
1036
+ rather than the first one, and an identity carries what the host chose for its
1037
+ run: a money rounding digit count, a report window as two bar indices, or values
1038
+ for the script's inputs. An identity that chooses none is the run the gate
1039
+ drives everywhere else, which is why the two cases already in the tree are byte
1040
+ for byte what they were. The harvest's report no longer files a strategy nobody
1041
+ has chosen a case identity for under the sentence about gaps, which is what it
1042
+ was doing to the short premium example on every run.
1043
+
1044
+ **What the suite does not reach, written down rather than left to be
1045
+ discovered.** No case supplies a charge schedule: a supplied schedule beside a
1046
+ declared commission is refused before the first bar (OS6023) and every shipped
1047
+ strategy declares one, so a case for it needs a strategy example that declares
1048
+ none. No case hands an engine a partial fill, a repeated frame, two frames in
1049
+ the wrong order, a fill reported after the order had gone terminal, a rejection,
1050
+ a cancellation, or a frame naming no row of the ledger. The frames in a
1051
+ harvested case are the ones its own run was answered, the destination a backtest
1052
+ runs against fills an order once and in full, and no shipped strategy cancels an
1053
+ order or leaves one resting, so none of those shapes can be harvested from what
1054
+ is in the tree today. `docs/integrating/running-the-suite.md` carries the list
1055
+ beside the commands, because that is where somebody reading a green run is
1056
+ standing.
1057
+
1058
+ ---
1059
+
10
1060
  ## 0.4.0
11
1061
 
12
1062
  **A backtest is driven, and what it produces is a document rather than a