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