openalgo-script 0.5.0 → 0.7.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 (343) hide show
  1. package/CHANGELOG.md +226 -0
  2. package/README.md +53 -22
  3. package/dist/adapters/charts/run.d.ts.map +1 -1
  4. package/dist/adapters/charts/run.js +6 -0
  5. package/dist/adapters/charts/run.js.map +1 -1
  6. package/dist/adapters/charts/tables.d.ts +6 -19
  7. package/dist/adapters/charts/tables.d.ts.map +1 -1
  8. package/dist/adapters/charts/tables.js.map +1 -1
  9. package/dist/adapters/charts/undrawable.d.ts +4 -0
  10. package/dist/adapters/charts/undrawable.d.ts.map +1 -0
  11. package/dist/adapters/charts/undrawable.js +47 -0
  12. package/dist/adapters/charts/undrawable.js.map +1 -0
  13. package/dist/core/backtest/drive.d.ts +36 -1
  14. package/dist/core/backtest/drive.d.ts.map +1 -1
  15. package/dist/core/backtest/drive.js +13 -107
  16. package/dist/core/backtest/drive.js.map +1 -1
  17. package/dist/core/backtest/index.d.ts +2 -0
  18. package/dist/core/backtest/index.d.ts.map +1 -1
  19. package/dist/core/backtest/index.js.map +1 -1
  20. package/dist/core/backtest/walk.d.ts +39 -0
  21. package/dist/core/backtest/walk.d.ts.map +1 -0
  22. package/dist/core/backtest/walk.js +125 -0
  23. package/dist/core/backtest/walk.js.map +1 -0
  24. package/dist/core/catalogue/catalogue.generated.d.ts +178 -1
  25. package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
  26. package/dist/core/catalogue/catalogue.generated.js +18 -1
  27. package/dist/core/catalogue/catalogue.generated.js.map +1 -1
  28. package/dist/core/catalogue/types.d.ts +8 -2
  29. package/dist/core/catalogue/types.d.ts.map +1 -1
  30. package/dist/core/catalogue/values.generated.d.ts +51 -0
  31. package/dist/core/catalogue/values.generated.d.ts.map +1 -1
  32. package/dist/core/check/assigned.d.ts +19 -0
  33. package/dist/core/check/assigned.d.ts.map +1 -0
  34. package/dist/core/check/assigned.js +14 -0
  35. package/dist/core/check/assigned.js.map +1 -0
  36. package/dist/core/check/call-sites.d.ts.map +1 -1
  37. package/dist/core/check/call-sites.js +9 -4
  38. package/dist/core/check/call-sites.js.map +1 -1
  39. package/dist/core/check/calls.d.ts.map +1 -1
  40. package/dist/core/check/calls.js +12 -4
  41. package/dist/core/check/calls.js.map +1 -1
  42. package/dist/core/check/check.d.ts.map +1 -1
  43. package/dist/core/check/check.js +15 -2
  44. package/dist/core/check/check.js.map +1 -1
  45. package/dist/core/check/checker.d.ts +26 -0
  46. package/dist/core/check/checker.d.ts.map +1 -1
  47. package/dist/core/check/checker.js +38 -2
  48. package/dist/core/check/checker.js.map +1 -1
  49. package/dist/core/check/constant.d.ts +16 -0
  50. package/dist/core/check/constant.d.ts.map +1 -1
  51. package/dist/core/check/constant.js +52 -0
  52. package/dist/core/check/constant.js.map +1 -1
  53. package/dist/core/check/deleted.d.ts +3 -0
  54. package/dist/core/check/deleted.d.ts.map +1 -0
  55. package/dist/core/check/deleted.js +109 -0
  56. package/dist/core/check/deleted.js.map +1 -0
  57. package/dist/core/check/expressions.js +1 -1
  58. package/dist/core/check/expressions.js.map +1 -1
  59. package/dist/core/check/library-output.d.ts.map +1 -1
  60. package/dist/core/check/library-output.js +1 -0
  61. package/dist/core/check/library-output.js.map +1 -1
  62. package/dist/core/check/library-prose.generated.js +10 -10
  63. package/dist/core/check/library-prose.generated.js.map +1 -1
  64. package/dist/core/check/library.d.ts +7 -0
  65. package/dist/core/check/library.d.ts.map +1 -1
  66. package/dist/core/check/library.js +1 -0
  67. package/dist/core/check/library.js.map +1 -1
  68. package/dist/core/check/surface.d.ts +9 -0
  69. package/dist/core/check/surface.d.ts.map +1 -1
  70. package/dist/core/check/surface.js +13 -0
  71. package/dist/core/check/surface.js.map +1 -1
  72. package/dist/core/engine/bar-source.d.ts +65 -0
  73. package/dist/core/engine/bar-source.d.ts.map +1 -0
  74. package/dist/core/engine/bar-source.js +58 -0
  75. package/dist/core/engine/bar-source.js.map +1 -0
  76. package/dist/core/engine/channels.js +2 -2
  77. package/dist/core/engine/channels.js.map +1 -1
  78. package/dist/core/engine/engine.d.ts +9 -11
  79. package/dist/core/engine/engine.d.ts.map +1 -1
  80. package/dist/core/engine/engine.js +26 -27
  81. package/dist/core/engine/engine.js.map +1 -1
  82. package/dist/core/engine/guard.d.ts.map +1 -1
  83. package/dist/core/engine/guard.js +10 -0
  84. package/dist/core/engine/guard.js.map +1 -1
  85. package/dist/core/engine/host.d.ts +4 -2
  86. package/dist/core/engine/host.d.ts.map +1 -1
  87. package/dist/core/engine/host.js.map +1 -1
  88. package/dist/core/engine/index.d.ts +1 -0
  89. package/dist/core/engine/index.d.ts.map +1 -1
  90. package/dist/core/engine/index.js.map +1 -1
  91. package/dist/core/engine/library/binding.d.ts +2 -0
  92. package/dist/core/engine/library/binding.d.ts.map +1 -1
  93. package/dist/core/engine/library/binding.js.map +1 -1
  94. package/dist/core/engine/library/index.d.ts +8 -0
  95. package/dist/core/engine/library/index.d.ts.map +1 -1
  96. package/dist/core/engine/library/index.js +10 -0
  97. package/dist/core/engine/library/index.js.map +1 -1
  98. package/dist/core/engine/library/maths.js +2 -2
  99. package/dist/core/engine/library/maths.js.map +1 -1
  100. package/dist/core/engine/library/objects.d.ts.map +1 -1
  101. package/dist/core/engine/library/objects.js +1 -1
  102. package/dist/core/engine/library/objects.js.map +1 -1
  103. package/dist/core/engine/machine.js +5 -5
  104. package/dist/core/engine/machine.js.map +1 -1
  105. package/dist/core/engine/memory.js +2 -2
  106. package/dist/core/engine/memory.js.map +1 -1
  107. package/dist/core/engine/registers.js +2 -2
  108. package/dist/core/engine/registers.js.map +1 -1
  109. package/dist/core/engine/request-body.d.ts +3 -1
  110. package/dist/core/engine/request-body.d.ts.map +1 -1
  111. package/dist/core/engine/request-body.js +3 -1
  112. package/dist/core/engine/request-body.js.map +1 -1
  113. package/dist/core/engine/request-plan.d.ts +3 -3
  114. package/dist/core/engine/request-plan.js +3 -3
  115. package/dist/core/engine/requests.d.ts +2 -2
  116. package/dist/core/engine/requests.d.ts.map +1 -1
  117. package/dist/core/engine/requests.js +5 -4
  118. package/dist/core/engine/requests.js.map +1 -1
  119. package/dist/core/engine/series.d.ts +18 -6
  120. package/dist/core/engine/series.d.ts.map +1 -1
  121. package/dist/core/engine/series.js +24 -5
  122. package/dist/core/engine/series.js.map +1 -1
  123. package/dist/core/engine/session/record.d.ts +4 -4
  124. package/dist/core/engine/session/record.js +4 -4
  125. package/dist/core/engine/values/index.d.ts +1 -1
  126. package/dist/core/engine/values/index.d.ts.map +1 -1
  127. package/dist/core/engine/values/index.js +1 -1
  128. package/dist/core/engine/values/index.js.map +1 -1
  129. package/dist/core/engine/values/value.d.ts +2 -0
  130. package/dist/core/engine/values/value.d.ts.map +1 -1
  131. package/dist/core/engine/values/value.js +4 -0
  132. package/dist/core/engine/values/value.js.map +1 -1
  133. package/dist/core/importer/calls.d.ts +29 -0
  134. package/dist/core/importer/calls.d.ts.map +1 -0
  135. package/dist/core/importer/calls.js +267 -0
  136. package/dist/core/importer/calls.js.map +1 -0
  137. package/dist/core/importer/context.d.ts +118 -0
  138. package/dist/core/importer/context.d.ts.map +1 -0
  139. package/dist/core/importer/context.js +120 -0
  140. package/dist/core/importer/context.js.map +1 -0
  141. package/dist/core/importer/expressions.d.ts +44 -0
  142. package/dist/core/importer/expressions.d.ts.map +1 -0
  143. package/dist/core/importer/expressions.js +223 -0
  144. package/dist/core/importer/expressions.js.map +1 -0
  145. package/dist/core/importer/importer.d.ts +13 -0
  146. package/dist/core/importer/importer.d.ts.map +1 -0
  147. package/dist/core/importer/importer.js +220 -0
  148. package/dist/core/importer/importer.js.map +1 -0
  149. package/dist/core/importer/index.d.ts +20 -0
  150. package/dist/core/importer/index.d.ts.map +1 -0
  151. package/dist/core/importer/index.js +18 -0
  152. package/dist/core/importer/index.js.map +1 -0
  153. package/dist/core/importer/lexer.d.ts +68 -0
  154. package/dist/core/importer/lexer.d.ts.map +1 -0
  155. package/dist/core/importer/lexer.js +207 -0
  156. package/dist/core/importer/lexer.js.map +1 -0
  157. package/dist/core/importer/names.d.ts +41 -0
  158. package/dist/core/importer/names.d.ts.map +1 -0
  159. package/dist/core/importer/names.js +78 -0
  160. package/dist/core/importer/names.js.map +1 -0
  161. package/dist/core/importer/orders.d.ts +8 -0
  162. package/dist/core/importer/orders.d.ts.map +1 -0
  163. package/dist/core/importer/orders.js +332 -0
  164. package/dist/core/importer/orders.js.map +1 -0
  165. package/dist/core/importer/outputs.d.ts +19 -0
  166. package/dist/core/importer/outputs.d.ts.map +1 -0
  167. package/dist/core/importer/outputs.js +266 -0
  168. package/dist/core/importer/outputs.js.map +1 -0
  169. package/dist/core/importer/parser.d.ts +5 -0
  170. package/dist/core/importer/parser.d.ts.map +1 -0
  171. package/dist/core/importer/parser.js +439 -0
  172. package/dist/core/importer/parser.js.map +1 -0
  173. package/dist/core/importer/presence.d.ts +32 -0
  174. package/dist/core/importer/presence.d.ts.map +1 -0
  175. package/dist/core/importer/presence.js +146 -0
  176. package/dist/core/importer/presence.js.map +1 -0
  177. package/dist/core/importer/settle.d.ts +23 -0
  178. package/dist/core/importer/settle.d.ts.map +1 -0
  179. package/dist/core/importer/settle.js +135 -0
  180. package/dist/core/importer/settle.js.map +1 -0
  181. package/dist/core/importer/statements.d.ts +4 -0
  182. package/dist/core/importer/statements.d.ts.map +1 -0
  183. package/dist/core/importer/statements.js +339 -0
  184. package/dist/core/importer/statements.js.map +1 -0
  185. package/dist/core/importer/syntax.d.ts +182 -0
  186. package/dist/core/importer/syntax.d.ts.map +1 -0
  187. package/dist/core/importer/syntax.js +14 -0
  188. package/dist/core/importer/syntax.js.map +1 -0
  189. package/dist/core/importer/table.d.ts +52 -0
  190. package/dist/core/importer/table.d.ts.map +1 -0
  191. package/dist/core/importer/table.js +156 -0
  192. package/dist/core/importer/table.js.map +1 -0
  193. package/dist/core/importer/values.d.ts +27 -0
  194. package/dist/core/importer/values.d.ts.map +1 -0
  195. package/dist/core/importer/values.js +236 -0
  196. package/dist/core/importer/values.js.map +1 -0
  197. package/dist/core/index.d.ts +12 -0
  198. package/dist/core/index.d.ts.map +1 -1
  199. package/dist/core/index.js +11 -0
  200. package/dist/core/index.js.map +1 -1
  201. package/dist/core/stdlib/averages/exponential.d.ts.map +1 -1
  202. package/dist/core/stdlib/averages/exponential.js +3 -6
  203. package/dist/core/stdlib/averages/exponential.js.map +1 -1
  204. package/dist/core/stdlib/averages/gaussian.d.ts +15 -0
  205. package/dist/core/stdlib/averages/gaussian.d.ts.map +1 -0
  206. package/dist/core/stdlib/averages/gaussian.js +55 -0
  207. package/dist/core/stdlib/averages/gaussian.js.map +1 -0
  208. package/dist/core/stdlib/averages/shaped.d.ts +2 -6
  209. package/dist/core/stdlib/averages/shaped.d.ts.map +1 -1
  210. package/dist/core/stdlib/averages/shaped.js +8 -22
  211. package/dist/core/stdlib/averages/shaped.js.map +1 -1
  212. package/dist/core/stdlib/calendar/civil.d.ts.map +1 -1
  213. package/dist/core/stdlib/calendar/civil.js +5 -2
  214. package/dist/core/stdlib/calendar/civil.js.map +1 -1
  215. package/dist/core/stdlib/maths/arithmetic.d.ts.map +1 -1
  216. package/dist/core/stdlib/maths/arithmetic.js +2 -1
  217. package/dist/core/stdlib/maths/arithmetic.js.map +1 -1
  218. package/dist/core/stdlib/maths/elementary.d.ts +3 -20
  219. package/dist/core/stdlib/maths/elementary.d.ts.map +1 -1
  220. package/dist/core/stdlib/maths/elementary.js +17 -13
  221. package/dist/core/stdlib/maths/elementary.js.map +1 -1
  222. package/dist/core/stdlib/maths/hypot.d.ts +8 -0
  223. package/dist/core/stdlib/maths/hypot.d.ts.map +1 -0
  224. package/dist/core/stdlib/maths/hypot.js +68 -0
  225. package/dist/core/stdlib/maths/hypot.js.map +1 -0
  226. package/dist/core/stdlib/maths/power.d.ts +3 -0
  227. package/dist/core/stdlib/maths/power.d.ts.map +1 -0
  228. package/dist/core/stdlib/maths/power.js +83 -0
  229. package/dist/core/stdlib/maths/power.js.map +1 -0
  230. package/dist/core/stdlib/maths/transcendental.d.ts +15 -0
  231. package/dist/core/stdlib/maths/transcendental.d.ts.map +1 -0
  232. package/dist/core/stdlib/maths/transcendental.js +185 -0
  233. package/dist/core/stdlib/maths/transcendental.js.map +1 -0
  234. package/dist/core/stdlib/maths/trigonometric.d.ts +4 -0
  235. package/dist/core/stdlib/maths/trigonometric.d.ts.map +1 -0
  236. package/dist/core/stdlib/maths/trigonometric.js +196 -0
  237. package/dist/core/stdlib/maths/trigonometric.js.map +1 -0
  238. package/dist/core/stdlib/series/conditions.d.ts +2 -4
  239. package/dist/core/stdlib/series/conditions.d.ts.map +1 -1
  240. package/dist/core/stdlib/series/conditions.js +9 -13
  241. package/dist/core/stdlib/series/conditions.js.map +1 -1
  242. package/dist/core/stdlib/series/statistics.d.ts.map +1 -1
  243. package/dist/core/stdlib/series/statistics.js +2 -0
  244. package/dist/core/stdlib/series/statistics.js.map +1 -1
  245. package/dist/core/stdlib/values/history.d.ts +26 -0
  246. package/dist/core/stdlib/values/history.d.ts.map +1 -0
  247. package/dist/core/stdlib/values/history.js +52 -0
  248. package/dist/core/stdlib/values/history.js.map +1 -0
  249. package/dist/core/stdlib/values/index.d.ts +1 -0
  250. package/dist/core/stdlib/values/index.d.ts.map +1 -1
  251. package/dist/core/stdlib/values/index.js +1 -0
  252. package/dist/core/stdlib/values/index.js.map +1 -1
  253. package/dist/core/stdlib/values/lookback.d.ts +4 -1
  254. package/dist/core/stdlib/values/lookback.d.ts.map +1 -1
  255. package/dist/core/stdlib/values/lookback.js +48 -45
  256. package/dist/core/stdlib/values/lookback.js.map +1 -1
  257. package/dist/core/stdlib/values/region.d.ts +6 -5
  258. package/dist/core/stdlib/values/region.d.ts.map +1 -1
  259. package/dist/core/stdlib/values/region.js +3 -3
  260. package/dist/core/stdlib/values/region.js.map +1 -1
  261. package/dist/core/stdlib/volatility/measures.d.ts.map +1 -1
  262. package/dist/core/stdlib/volatility/measures.js +6 -4
  263. package/dist/core/stdlib/volatility/measures.js.map +1 -1
  264. package/dist/core/version/version.generated.d.ts +1 -1
  265. package/dist/core/version/version.generated.js +1 -1
  266. package/package.json +13 -2
  267. package/spec/errors.json +380 -20
  268. package/src/adapters/charts/run.ts +6 -0
  269. package/src/adapters/charts/tables.ts +6 -19
  270. package/src/adapters/charts/undrawable.ts +51 -0
  271. package/src/core/backtest/drive.ts +66 -154
  272. package/src/core/backtest/index.ts +2 -0
  273. package/src/core/backtest/walk.ts +182 -0
  274. package/src/core/catalogue/catalogue.generated.ts +18 -1
  275. package/src/core/catalogue/types.ts +8 -2
  276. package/src/core/catalogue/values.generated.ts +16 -0
  277. package/src/core/check/assigned.ts +31 -0
  278. package/src/core/check/call-sites.ts +11 -4
  279. package/src/core/check/calls.ts +13 -4
  280. package/src/core/check/check.ts +14 -2
  281. package/src/core/check/checker.ts +39 -2
  282. package/src/core/check/constant.ts +57 -0
  283. package/src/core/check/deleted.ts +135 -0
  284. package/src/core/check/expressions.ts +1 -1
  285. package/src/core/check/library-output.ts +1 -0
  286. package/src/core/check/library-prose.generated.ts +10 -10
  287. package/src/core/check/library.ts +8 -0
  288. package/src/core/check/surface.ts +16 -0
  289. package/src/core/engine/bar-source.ts +113 -0
  290. package/src/core/engine/channels.ts +2 -2
  291. package/src/core/engine/engine.ts +29 -28
  292. package/src/core/engine/guard.ts +12 -1
  293. package/src/core/engine/host.ts +4 -2
  294. package/src/core/engine/index.ts +1 -0
  295. package/src/core/engine/library/binding.ts +2 -0
  296. package/src/core/engine/library/index.ts +11 -0
  297. package/src/core/engine/library/maths.ts +2 -2
  298. package/src/core/engine/library/objects.ts +1 -6
  299. package/src/core/engine/machine.ts +5 -5
  300. package/src/core/engine/memory.ts +2 -2
  301. package/src/core/engine/registers.ts +2 -2
  302. package/src/core/engine/request-body.ts +5 -7
  303. package/src/core/engine/request-plan.ts +3 -3
  304. package/src/core/engine/requests.ts +11 -8
  305. package/src/core/engine/series.ts +30 -6
  306. package/src/core/engine/session/record.ts +4 -4
  307. package/src/core/engine/values/index.ts +1 -0
  308. package/src/core/engine/values/value.ts +5 -0
  309. package/src/core/importer/calls.ts +274 -0
  310. package/src/core/importer/context.ts +197 -0
  311. package/src/core/importer/expressions.ts +247 -0
  312. package/src/core/importer/importer.ts +226 -0
  313. package/src/core/importer/index.ts +19 -0
  314. package/src/core/importer/lexer.ts +285 -0
  315. package/src/core/importer/names.ts +104 -0
  316. package/src/core/importer/orders.ts +307 -0
  317. package/src/core/importer/outputs.ts +245 -0
  318. package/src/core/importer/parser.ts +436 -0
  319. package/src/core/importer/presence.ts +164 -0
  320. package/src/core/importer/settle.ts +141 -0
  321. package/src/core/importer/statements.ts +331 -0
  322. package/src/core/importer/syntax.ts +144 -0
  323. package/src/core/importer/table.ts +191 -0
  324. package/src/core/importer/values.ts +241 -0
  325. package/src/core/index.ts +13 -0
  326. package/src/core/stdlib/averages/exponential.ts +3 -4
  327. package/src/core/stdlib/averages/gaussian.ts +52 -0
  328. package/src/core/stdlib/averages/shaped.ts +8 -21
  329. package/src/core/stdlib/calendar/civil.ts +5 -2
  330. package/src/core/stdlib/maths/arithmetic.ts +2 -1
  331. package/src/core/stdlib/maths/elementary.ts +20 -33
  332. package/src/core/stdlib/maths/hypot.ts +63 -0
  333. package/src/core/stdlib/maths/power.ts +57 -0
  334. package/src/core/stdlib/maths/transcendental.ts +160 -0
  335. package/src/core/stdlib/maths/trigonometric.ts +160 -0
  336. package/src/core/stdlib/series/conditions.ts +9 -12
  337. package/src/core/stdlib/series/statistics.ts +1 -0
  338. package/src/core/stdlib/values/history.ts +70 -0
  339. package/src/core/stdlib/values/index.ts +1 -0
  340. package/src/core/stdlib/values/lookback.ts +51 -47
  341. package/src/core/stdlib/values/region.ts +7 -5
  342. package/src/core/stdlib/volatility/measures.ts +6 -4
  343. package/src/core/version/version.generated.ts +1 -1
package/spec/errors.json CHANGED
@@ -12,14 +12,16 @@
12
12
  "parse": "Reading tokens into a tree.",
13
13
  "check": "Names, types, scope and call sites, before any bar runs.",
14
14
  "runtime": "Executing a bar.",
15
- "host": "The host answering the engine: data, limits and order destinations."
15
+ "host": "The host answering the engine: data, limits and order destinations.",
16
+ "import": "Reading a script written in another chart language and writing it as OpenScript."
16
17
  },
17
18
  "stageLabels": {
18
19
  "lex": "lexer",
19
20
  "parse": "parser",
20
21
  "check": "checker",
21
22
  "runtime": "engine",
22
- "host": "host"
23
+ "host": "host",
24
+ "import": "importer"
23
25
  },
24
26
  "ranges": [
25
27
  {
@@ -69,6 +71,12 @@
69
71
  "kind": "Warnings",
70
72
  "means": "The script compiles and runs, and something in it is probably not meant.",
71
73
  "severity": "warning"
74
+ },
75
+ {
76
+ "prefix": "OS9",
77
+ "kind": "Import",
78
+ "means": "A script written in another chart language could not be translated as written, or was translated with a stated difference.",
79
+ "severity": "error or warning"
72
80
  }
73
81
  ],
74
82
  "entries": [
@@ -902,7 +910,7 @@
902
910
  },
903
911
  "spec": "language.md 15.1",
904
912
  "refines": null,
905
- "test": null
913
+ "test": "tests/unit/check-names.test.ts"
906
914
  },
907
915
  {
908
916
  "code": "OS2011",
@@ -1175,7 +1183,7 @@
1175
1183
  "placeholders": {
1176
1184
  "option": "the option that was given a bar-dependent value"
1177
1185
  },
1178
- "cause": "The declaration builds the legend, the axis and the settings dialog before bar 0 runs, so its options must be literals, arithmetic over literals, or an input(). A value that changes per bar has no single answer at the moment the dialog is built. The rule covers more than the declaration's own options: every argument that lands in a declaration fixed before bar 0 arrives here too, signal's at, shape and color, table's position, rows and cols, and a plot's style arguments among them. An input() counts as a constant for this purpose, because the engine resolves inputs at load and substitutes the resolved value before bar 0 runs.",
1186
+ "cause": "The declaration builds the legend, the axis and the settings dialog before bar 0 runs, so its options must be literals, arithmetic over literals, or an input() written as the whole of the value; an expression over an input is OS3025. A value that changes per bar has no single answer at the moment the dialog is built. The rule covers more than the declaration's own options: every argument that lands in a declaration fixed before bar 0 arrives here too, signal's at, shape and color, table's position, rows and cols, and a plot's style arguments among them. An input() counts as a constant for this purpose, because the engine resolves inputs at load and substitutes the resolved value before bar 0 runs.",
1179
1187
  "fix": "Use a literal, or make it tunable with an input(): {option} = input(2, \"{option}\").",
1180
1188
  "autofix": false,
1181
1189
  "example": {
@@ -1644,6 +1652,48 @@
1644
1652
  "refines": "OS3021",
1645
1653
  "test": "tests/unit/check-calls.test.ts"
1646
1654
  },
1655
+ {
1656
+ "code": "OS3025",
1657
+ "title": "A setting is part of a larger expression here",
1658
+ "severity": "error",
1659
+ "stage": "check",
1660
+ "since": 1,
1661
+ "message": "{option} is fixed before the first bar and holds an input() only as the whole of its value, so it cannot hold an expression over one.",
1662
+ "placeholders": {
1663
+ "option": "the option or field whose value reads a setting as part of something larger"
1664
+ },
1665
+ "cause": "A field fixed before bar 0 is written into the compiled program as one of two things: the value itself, which the compiler folds, or a reference to one input, which the engine resolves once at load (compiled-program.md 2.3). An expression over a setting is neither. The compiler cannot fold it, because the setting's value is not known until the host resolves the settings, and the program has no form for an expression evaluated at load, because that would be a second evaluator every engine has to agree on before the first bar. An input's own default, bounds and step are the same kind of field and are held tighter still: they are what the settings dialog shows before anybody has chosen, so none of them may read another setting at all.",
1666
+ "fix": "Declare the setting as the value itself, with an input() written as the whole of {option}, or write the value out as a literal.",
1667
+ "autofix": false,
1668
+ "example": {
1669
+ "before": "w = input(1, \"Width\")\nplot(close, \"Close\", aqua, width = w + 1)",
1670
+ "after": "w = input(2, \"Width\")\nplot(close, \"Close\", aqua, width = w)"
1671
+ },
1672
+ "spec": "language.md 13.2",
1673
+ "refines": null,
1674
+ "test": "tests/unit/check-plot-options.test.ts"
1675
+ },
1676
+ {
1677
+ "code": "OS3026",
1678
+ "title": "This option cannot be a setting",
1679
+ "severity": "error",
1680
+ "stage": "check",
1681
+ "since": 1,
1682
+ "message": "{option} is written into the compiled program as a plain value, so a setting cannot choose it in this format version.",
1683
+ "placeholders": {
1684
+ "option": "the option that was written from an input()"
1685
+ },
1686
+ "cause": "An option fixed before bar 0 carries either its value or a reference to one input, resolved at load (compiled-program.md 2.3), and almost every option takes both. A plot's style takes only the value: its field in the compiled format is a plain string. So an input() written into it could only be folded to its default, the settings row it declares would move nothing, and a select offering values the option does not accept would pass, because the set check reads a value written out and a setting is not one. Widening the field changes the compiled format that every other engine is written against, so it is refused here rather than half carried.",
1687
+ "fix": "Write the option out as a string literal and drop the input(): style = \"step\", for instance, is one of the values OS3008 names.",
1688
+ "autofix": false,
1689
+ "example": {
1690
+ "before": "st = input(\"line\", \"Style\", options = [\"line\", \"step\", \"area\"])\nplot(close, \"Close\", aqua, style = st)",
1691
+ "after": "plot(close, \"Close\", aqua, style = \"step\")"
1692
+ },
1693
+ "spec": "language.md 13.4",
1694
+ "refines": null,
1695
+ "test": "tests/unit/check-plot-options.test.ts"
1696
+ },
1647
1697
  {
1648
1698
  "code": "OS4001",
1649
1699
  "title": "History index is not usable",
@@ -1709,7 +1759,7 @@
1709
1759
  },
1710
1760
  "spec": "language.md 14.1",
1711
1761
  "refines": null,
1712
- "test": null
1762
+ "test": "tests/engine/machine.test.ts"
1713
1763
  },
1714
1764
  {
1715
1765
  "code": "OS4004",
@@ -1824,8 +1874,7 @@
1824
1874
  },
1825
1875
  "spec": "language.md 15.3",
1826
1876
  "refines": null,
1827
- "test": null,
1828
- "deferred": "Nothing raises this yet. A write outside the declared grid raises the broader OS4004, which names an index rather than the shape the declaration fixed. Raised when the table surface reports the cell against the table it was written to, language.md 15.3."
1877
+ "test": "tests/engine/tables.test.ts"
1829
1878
  },
1830
1879
  {
1831
1880
  "code": "OS4009",
@@ -2428,17 +2477,18 @@
2428
2477
  "since": 1,
2429
2478
  "message": "The host did not supply {fact} for {symbol}.",
2430
2479
  "placeholders": {
2431
- "fact": "the missing fact: tick size, lot size, session or timezone",
2480
+ "fact": "what the instrument record lacks for something that cannot default it: a timezone for the session it states, a timezone the calendar can read, a session spelled HH:MM, or session days numbered 1 to 7",
2432
2481
  "symbol": "the instrument it is missing for"
2433
2482
  },
2434
- "cause": "Tick size, lot size, the session and the timezone come from the instrument record (host-interface.md 4.1), not from the bars. A script that rounds to a tick or sizes in lots cannot invent them, and guessing would produce orders the exchange rejects.",
2435
- "fix": "Supply {fact} in the host's instrument record, or stop depending on it: round with a number the script chooses rather than chart.tickSize.",
2483
+ "cause": "An instrument fact the host did not state is absent, and a bare read of one, chart.tickSize or chart.lotSize among them, returns the absent value a script can test with isNone (stdlib.md 3.4). This error is the other case: something that needs the fact and has no way to proceed without it. A session with no timezone is a window with no clock to read it in, so every session test on that host would answer absence on every bar and every session study would draw nothing with nothing said. The engine refuses the record at load instead, before any bar runs, because the fact comes from the instrument record (host-interface.md 4.1) and not from the bars, and a guessed zone is silently wrong for half the year anywhere a clock changes with the season.",
2484
+ "fix": "Supply {fact} in the host's instrument record for {symbol}, or leave the session out of the record, which makes every session read absent rather than wrong.",
2436
2485
  "autofix": false,
2437
2486
  "example": {
2438
- "before": "qty = lots * chart.lotSize",
2439
- "after": "lotSize = input(1, \"Lot size\", min = 1)\nqty = lots * lotSize"
2487
+ "kind": "transcript",
2488
+ "before": "host instrument record:\n session 09:15 to 15:30, days 1 to 5\n timezone not stated",
2489
+ "after": "host instrument record:\n session 09:15 to 15:30, days 1 to 5\n timezone UTC"
2440
2490
  },
2441
- "spec": "language.md 15.2",
2491
+ "spec": "host-interface.md 4.5",
2442
2492
  "refines": null,
2443
2493
  "test": "tests/engine/requests-host.test.ts"
2444
2494
  },
@@ -2698,6 +2748,51 @@
2698
2748
  "refines": null,
2699
2749
  "test": "tests/backtest/settings.test.ts"
2700
2750
  },
2751
+ {
2752
+ "code": "OS6024",
2753
+ "title": "The host cannot draw something this study declares",
2754
+ "severity": "error",
2755
+ "stage": "host",
2756
+ "since": 1,
2757
+ "message": "This host cannot draw {what}: {limit}.",
2758
+ "placeholders": {
2759
+ "what": "the declaration that will not be drawn, named by its title where it has one",
2760
+ "limit": "the host's own sentence for why, which is the only part a host writes"
2761
+ },
2762
+ "cause": "A compiled program carries every output the language can express, and a host draws what its own surface has room for. The two are allowed to differ, and what is not allowed is the difference being invisible: a study whose second panel never appears looks like a study with a bug in its cells, and a band drawn in a colour the script did not choose looks like a script that chose it. So a host states its limit before any bar runs, rather than drawing part of the study and saying nothing. The compiled program carries no source position for a declaration, which is why the refusal names the declaration by its title rather than pointing at a line.",
2763
+ "fix": "Declare what this host draws, or run the study on a host that draws {what}.",
2764
+ "autofix": false,
2765
+ "example": {
2766
+ "kind": "transcript",
2767
+ "before": "study declares:\n table \"Summary\", 2 rows, 2 columns\n table \"Detail\", 4 rows, 2 columns\nhost draws: one grid per pane",
2768
+ "after": "study declares:\n table \"Summary\", 6 rows, 2 columns\nhost draws: one grid per pane"
2769
+ },
2770
+ "spec": "compiled-program.md 11",
2771
+ "refines": null,
2772
+ "test": "tests/adapters/charts/undrawable.test.ts"
2773
+ },
2774
+ {
2775
+ "code": "OS6025",
2776
+ "title": "A bar has no time",
2777
+ "severity": "error",
2778
+ "stage": "host",
2779
+ "since": 1,
2780
+ "message": "Bar {index} was handed over with no time.",
2781
+ "placeholders": {
2782
+ "index": "the index of the bar that carries no time"
2783
+ },
2784
+ "cause": "Every bar states its open instant (host-interface.md 3.1), and the order rule, the history operator, warmup and every calendar fold are built on it. A bar dated nothing cannot be put in order against the bar before it, so it is not OS6011, which is about two stated instants in the wrong order: it is a bar of the wrong shape. Reading it as the absent value would step it over every calendar fold and make every session fact absent for a bar that is on the chart, which draws a gap where the host has data.",
2785
+ "fix": "State the bar's open instant in whole milliseconds since the Unix epoch, UTC, and leave out a bar the host cannot date rather than handing it over without a time.",
2786
+ "autofix": false,
2787
+ "example": {
2788
+ "kind": "transcript",
2789
+ "before": "host input:\n bar 41 09:10\n bar 42 (no time)\n bar 43 09:20",
2790
+ "after": "host input:\n bar 41 09:10\n bar 42 09:15\n bar 43 09:20"
2791
+ },
2792
+ "spec": "host-interface.md 3.5",
2793
+ "refines": null,
2794
+ "test": "tests/engine/series.test.ts"
2795
+ },
2701
2796
  {
2702
2797
  "code": "OS7001",
2703
2798
  "title": "Only a strategy can do that",
@@ -3274,7 +3369,7 @@
3274
3369
  },
3275
3370
  "spec": "language.md 13.2, 15.3",
3276
3371
  "refines": null,
3277
- "test": null
3372
+ "test": "tests/unit/check-plot-options.test.ts"
3278
3373
  },
3279
3374
  {
3280
3375
  "code": "OS8008",
@@ -3380,7 +3475,7 @@
3380
3475
  },
3381
3476
  "spec": "language.md 6.4, 6.5",
3382
3477
  "refines": null,
3383
- "test": null
3478
+ "test": "tests/unit/check-dead-code.test.ts"
3384
3479
  },
3385
3480
  {
3386
3481
  "code": "OS8013",
@@ -3449,7 +3544,7 @@
3449
3544
  },
3450
3545
  "spec": "language.md 10.3",
3451
3546
  "refines": null,
3452
- "test": null
3547
+ "test": "tests/unit/check-dead-code.test.ts"
3453
3548
  },
3454
3549
  {
3455
3550
  "code": "OS8016",
@@ -3470,7 +3565,7 @@
3470
3565
  },
3471
3566
  "spec": "language.md 11.3",
3472
3567
  "refines": null,
3473
- "test": null
3568
+ "test": "tests/unit/check-dead-code.test.ts"
3474
3569
  },
3475
3570
  {
3476
3571
  "code": "OS8017",
@@ -3491,7 +3586,7 @@
3491
3586
  },
3492
3587
  "spec": "language.md 10.2",
3493
3588
  "refines": null,
3494
- "test": null
3589
+ "test": "tests/unit/check-dead-code.test.ts"
3495
3590
  },
3496
3591
  {
3497
3592
  "code": "OS8018",
@@ -3536,8 +3631,273 @@
3536
3631
  },
3537
3632
  "spec": "language.md 5.4",
3538
3633
  "refines": null,
3539
- "test": null,
3540
- "deferred": "Nothing raises this yet. The checker does not follow a reference to a deleted object, so the held handle this warns about compiles silently. Raised when the checker follows a deletion to the names and elements still holding the object, language.md 5.4."
3634
+ "test": "tests/unit/check-deleted.test.ts"
3635
+ },
3636
+ {
3637
+ "code": "OS9001",
3638
+ "title": "Version not read by the importer",
3639
+ "severity": "error",
3640
+ "stage": "import",
3641
+ "since": 1,
3642
+ "message": "The importer reads scripts written for version 5 or 6 of the source dialect, and this one {found}.",
3643
+ "placeholders": {
3644
+ "found": "what the version annotation says instead: that it declares another version, naming it, or that it declares none"
3645
+ },
3646
+ "cause": "A script in the source dialect opens with an annotation comment naming the version of the dialect it is written for. The importer's reader, its table of built-ins and every rule it applies about where the two languages differ are written for versions 5 and 6, whose built-ins live in namespaces and whose declaration is an indicator or strategy call. An earlier version names its built-ins differently and differs in rules the importer does not model, so reading one under these rules would produce a translation that compiles and means something else. Nothing is translated: the result's source is empty and this is its only finding.",
3647
+ "fix": "Bring the script up to version 5 or 6 in the source dialect, or translate it by hand, and import it again.",
3648
+ "autofix": false,
3649
+ "example": {
3650
+ "kind": "import",
3651
+ "before": "//@version=4\nstudy(\"Range\")\nplot(high - low)",
3652
+ "after": "version 1\n\nstudy(\"Range\")\nplot(high - low, \"Range\")"
3653
+ },
3654
+ "spec": "docs/writing/importing-a-script.md 2",
3655
+ "refines": null,
3656
+ "test": "tests/importer/refusals.test.ts"
3657
+ },
3658
+ {
3659
+ "code": "OS9002",
3660
+ "title": "Construct with no equivalent",
3661
+ "severity": "error",
3662
+ "stage": "import",
3663
+ "since": 1,
3664
+ "message": "The importer cannot translate {construct}, so the statement holding it was kept as a comment.",
3665
+ "placeholders": {
3666
+ "construct": "the form the importer met, named in a few words: a tuple assignment, an if used as a value, a comparison with na"
3667
+ },
3668
+ "cause": "Some forms of the source dialect have no OpenScript spelling that keeps their meaning: a value produced by an if or a switch, a tuple, a type, a method, an import, an array literal, a loop over a collection, and an equality test against na, which the source dialect answers false on every bar while OpenScript's own test for absence answers true where the value is absent. A few more are written in forms the importer's reader does not accept. The importer keeps the whole top level statement holding one as a comment line per source line, marked not translated, so the output still compiles and the original is there to translate from.",
3669
+ "fix": "Translate that statement by hand from the comment that holds it: the rest of the script was translated around it.",
3670
+ "autofix": false,
3671
+ "example": {
3672
+ "kind": "import",
3673
+ "before": "//@version=5\nindicator(\"Side\")\nside = if close > open\n 1\nelse\n -1\nplot(side)",
3674
+ "after": "version 1\n\nstudy(\"Side\")\nside = close > open ? 1 : -1\nplot(side, \"Side\")"
3675
+ },
3676
+ "spec": "docs/writing/importing-a-script.md 4",
3677
+ "refines": null,
3678
+ "test": "tests/importer/refusals.test.ts"
3679
+ },
3680
+ {
3681
+ "code": "OS9003",
3682
+ "title": "Built-in with no mapping",
3683
+ "severity": "error",
3684
+ "stage": "import",
3685
+ "since": 1,
3686
+ "message": "{name} is not one of the built-ins the importer maps, so the statement holding it was kept as a comment.",
3687
+ "placeholders": {
3688
+ "name": "the built-in as the source writes it, namespace included"
3689
+ },
3690
+ "cause": "The importer translates a built-in only where it holds a row saying which OpenScript name computes the same thing and how the arguments line up. A built-in with no row, or a name the source never declares, is not guessed at: a function with a similar name can differ in its arguments, its warmup or its arithmetic, and a translation that compiled with the wrong one would draw a line that looks right and is not. The whole top level statement holding it is kept as a comment.",
3691
+ "fix": "Find the OpenScript library call that does the same job, check how it treats its first bars and absent values, and write the statement by hand.",
3692
+ "autofix": false,
3693
+ "example": {
3694
+ "kind": "import",
3695
+ "before": "//@version=5\nindicator(\"Range position\")\nrank = ta.percentrank(close, 20)\nplot(rank)",
3696
+ "after": "version 1\n\nstudy(\"Range position\")\nrank = percentRank(close, 20)\nplot(rank, \"Rank\")"
3697
+ },
3698
+ "spec": "docs/writing/importing-a-script.md 3",
3699
+ "refines": null,
3700
+ "test": "tests/importer/refusals.test.ts"
3701
+ },
3702
+ {
3703
+ "code": "OS9004",
3704
+ "title": "Declaration the importer cannot place",
3705
+ "severity": "error",
3706
+ "stage": "import",
3707
+ "since": 1,
3708
+ "message": "The importer writes the declaration from one indicator or strategy call at the top level, and this script {found}.",
3709
+ "placeholders": {
3710
+ "found": "what the importer found instead: that the script has none, that it declares a library, or that it makes a second declaration here"
3711
+ },
3712
+ "cause": "An OpenScript file carries exactly one declaration, and it is what makes the file a study or a strategy. The importer writes it from the source's indicator or strategy call and from nothing else, because a declaration it invented would decide whether the translation may place orders. A script with none, and a library, which has no declaration an OpenScript file can carry, are not translated at all: the result's source is empty and this is its only finding. A second declaration is kept as a comment and the first one is used.",
3713
+ "fix": "Give the source script exactly one indicator or strategy declaration at the top level, and import it again.",
3714
+ "autofix": false,
3715
+ "example": {
3716
+ "kind": "import",
3717
+ "before": "//@version=5\nbasis = ta.sma(close, 20)\nplot(basis)",
3718
+ "after": "version 1\n\nstudy(\"Basis\")\nbasis = sma(close, 20)\nplot(basis, \"Basis\")"
3719
+ },
3720
+ "spec": "docs/writing/importing-a-script.md 2",
3721
+ "refines": null,
3722
+ "test": "tests/importer/refusals.test.ts"
3723
+ },
3724
+ {
3725
+ "code": "OS9005",
3726
+ "title": "Reads a statement that was not translated",
3727
+ "severity": "error",
3728
+ "stage": "import",
3729
+ "since": 1,
3730
+ "message": "{name} is declared by the statement at line {line}, which was not translated, so this statement that reads it was kept as a comment too.",
3731
+ "placeholders": {
3732
+ "name": "the name, as the source writes it",
3733
+ "line": "the line of the statement that declares it and was kept as a comment"
3734
+ },
3735
+ "cause": "A statement that was not translated declares nothing in the output, so a later statement reading one of its names would not compile. Rather than hand back a file that fails, the importer keeps every such statement as a comment as well, in source order, so that one refusal is reported once at its cause and once at each statement that follows from it.",
3736
+ "fix": "Translate the statement at line {line} by hand first, then this one, which was kept as a comment only because it reads {name}.",
3737
+ "autofix": false,
3738
+ "example": {
3739
+ "kind": "import",
3740
+ "before": "//@version=5\nindicator(\"Rank\")\nrank = ta.percentrank(close, 20)\nsmooth = ta.sma(rank, 5)\nplot(smooth)",
3741
+ "after": "version 1\n\nstudy(\"Rank\")\nrank = percentRank(close, 20)\nsmooth = sma(rank, 5)\nplot(smooth, \"Smooth\")"
3742
+ },
3743
+ "spec": "docs/writing/importing-a-script.md 4",
3744
+ "refines": null,
3745
+ "test": "tests/importer/refusals.test.ts"
3746
+ },
3747
+ {
3748
+ "code": "OS9006",
3749
+ "title": "Argument with no equivalent",
3750
+ "severity": "error",
3751
+ "stage": "import",
3752
+ "since": 1,
3753
+ "message": "{argument} changes what {call} computes or trades, and OpenScript has no equivalent the importer can write.",
3754
+ "placeholders": {
3755
+ "argument": "the argument, as the source names it or by its position",
3756
+ "call": "the call it was given to, as the source writes it"
3757
+ },
3758
+ "cause": "Some arguments change the numbers a script produces or the orders it sends: a declaration's timeframe, a strategy's margin or its pyramiding above one, a limit price on an entry, a trailing exit, a quantity on a close, and a quantity on an entry in a strategy that sizes its orders in anything but units, which the source dialect counts in contracts and OpenScript in the declaration's own unit. Leaving one out would produce a translation that compiles and computes something else. An argument to the declaration or to an input is left out of that call, because the file needs its declaration and the rest of the script reads the input; every other call carrying one is kept as a comment.",
3759
+ "fix": "Translate the call by hand, deciding what OpenScript should do in place of {argument}, or remove {argument} from the source script if the script does not depend on it.",
3760
+ "autofix": false,
3761
+ "example": {
3762
+ "kind": "import",
3763
+ "before": "//@version=5\nindicator(\"Daily basis\", timeframe = \"D\")\nplot(ta.sma(close, 20))",
3764
+ "after": "version 1\n\nstudy(\"Daily basis\")\nplot(req.timeframe(\"1D\", sma(close, 20)), \"Basis\")"
3765
+ },
3766
+ "spec": "docs/writing/importing-a-script.md 4",
3767
+ "refines": null,
3768
+ "test": "tests/importer/arguments.test.ts"
3769
+ },
3770
+ {
3771
+ "code": "OS9007",
3772
+ "title": "Warmup and absent bars follow OpenScript",
3773
+ "severity": "warning",
3774
+ "stage": "import",
3775
+ "since": 1,
3776
+ "message": "{call} became {target} here and wherever else the script calls it, and {target} follows OpenScript's own warmup, arithmetic and handling of absent values, which the original is not guaranteed to share.",
3777
+ "placeholders": {
3778
+ "call": "the built-in as the source writes it",
3779
+ "target": "the OpenScript call it was translated to"
3780
+ },
3781
+ "cause": "OpenScript fixes, for every function, the first bar a value appears on, how a seeded average is seeded, the order its arithmetic is done in and what an absent value inside the window does (stdlib.md sections 2.4 and 20). The source dialect does not fix the same things to the same precision, and where it says anything it can say something else: it skips absent values in windows where OpenScript propagates them. So a translated average can be absent on a first bar where the original drew a value, or differ in the last digits. Over a source with no absent values, and after the first bars, the two compute the same quantity. The finding is given once per built-in, at its first call.",
3782
+ "fix": "Compare the first bars of the translation with the original, and where the script depends on them, write the value as an explicit recurrence held in var, seeded the way the original seeds it.",
3783
+ "autofix": false,
3784
+ "example": {
3785
+ "kind": "import",
3786
+ "before": "//@version=5\nindicator(\"Average\")\nplot(ta.ema(close, 20))",
3787
+ "after": "version 1\n\nstudy(\"Average\")\nplot(ema(close, 20), \"Average\")"
3788
+ },
3789
+ "spec": "language.md 6.7, 7.3; stdlib.md 2.4, 20.2",
3790
+ "refines": null,
3791
+ "test": "tests/importer/differences.test.ts"
3792
+ },
3793
+ {
3794
+ "code": "OS9008",
3795
+ "title": "Whole-number division in version 5",
3796
+ "severity": "warning",
3797
+ "stage": "import",
3798
+ "since": 1,
3799
+ "message": "Both sides of this division are whole-number constants, which version 5 of the source dialect divides without a fractional part, and OpenScript keeps the fraction.",
3800
+ "placeholders": {},
3801
+ "cause": "Version 5 of the source dialect gives the quotient of two whole-number constants as a whole number, and version 6 and OpenScript both keep the fraction (language.md 9.2). A constant here is a whole-number literal, or a name the script binds once to one and never reassigns. The importer writes the division as OpenScript writes it and says so, rather than deciding which of the two results the script's author was relying on.",
3802
+ "fix": "If the script relied on the whole-number result, wrap the translated division in trunc(); otherwise leave it as it is.",
3803
+ "autofix": false,
3804
+ "example": {
3805
+ "kind": "import",
3806
+ "before": "//@version=5\nindicator(\"Half\")\nhalf = 7 / 2\nplot(close * half)",
3807
+ "after": "version 1\n\nstudy(\"Half\")\nhalf = trunc(7 / 2)\nplot(close * half, \"Half\")"
3808
+ },
3809
+ "spec": "language.md 9.2",
3810
+ "refines": null,
3811
+ "test": "tests/importer/differences.test.ts"
3812
+ },
3813
+ {
3814
+ "code": "OS9009",
3815
+ "title": "Presentation argument left out",
3816
+ "severity": "warning",
3817
+ "stage": "import",
3818
+ "since": 1,
3819
+ "message": "{argument} changes how {call} is displayed or what it may allocate, not what it computes, and OpenScript has no equivalent, so the translation leaves it out.",
3820
+ "placeholders": {
3821
+ "argument": "the argument, as the source names it",
3822
+ "call": "the call it was given to, as the source writes it"
3823
+ },
3824
+ "cause": "Many arguments in the source dialect are about the picture rather than the numbers: a line that tracks the price, a label size, a legend entry, a count of drawings to keep, a style the OpenScript call does not offer. Leaving one out changes what the chart looks like and nothing the script computes or trades, so the call is translated and the argument is reported rather than silently dropped.",
3825
+ "fix": "Nothing needs to change unless the display mattered; where it did, restyle the translated call with the arguments OpenScript does have, such as its colour, width and style.",
3826
+ "autofix": false,
3827
+ "example": {
3828
+ "kind": "import",
3829
+ "before": "//@version=5\nindicator(\"Close\")\nplot(close, \"Close\", trackprice = true)",
3830
+ "after": "version 1\n\nstudy(\"Close\")\nplot(close, \"Close\")"
3831
+ },
3832
+ "spec": "docs/writing/importing-a-script.md 5",
3833
+ "refines": null,
3834
+ "test": "tests/importer/arguments.test.ts"
3835
+ },
3836
+ {
3837
+ "code": "OS9010",
3838
+ "title": "Order placed under OpenScript's order model",
3839
+ "severity": "warning",
3840
+ "stage": "import",
3841
+ "since": 1,
3842
+ "message": "{call} was translated into OpenScript's order calls here and wherever else the script uses it, and OpenScript decides when an order fills, how a reversal and a repeated entry are handled and when an exit level starts to apply by its own order model.",
3843
+ "placeholders": {
3844
+ "call": "the order call as the source writes it"
3845
+ },
3846
+ "cause": "The importer translates an entry into a close of any opposite position and an entry guarded so that it never adds to a position already held on its side, which is what the source dialect does with its default pyramiding, and an exit into a leg level guarded by the side of the entry it names, with profit and loss distances converted from ticks by the instrument's tick size. That is as close as the two order models come, and they are not the same model: an OpenScript exit sets the leg's level when it is called rather than when its entry fills, a repeated entry past the pyramiding limit is refused rather than ignored, and an order a script places while flat is sized and filled by the rules of stdlib.md section 17. The finding is given once per order call, at its first use.",
3847
+ "fix": "Backtest the translation beside the original and compare the two trade lists before relying on it, and adjust the order calls by hand where they part.",
3848
+ "autofix": false,
3849
+ "example": {
3850
+ "kind": "import",
3851
+ "before": "//@version=5\nstrategy(\"Cross\")\nfast = ta.sma(close, 10)\nslow = ta.sma(close, 30)\nif ta.crossover(fast, slow)\n strategy.entry(\"Long\", strategy.long)",
3852
+ "after": "version 1\n\nstrategy(\"Cross\", capital = 1000000)\nfast = sma(close, 10)\nslow = sma(close, 30)\nif crossUp(fast, slow)\n if pos.size < 0\n close()\n if pos.size <= 0\n buy(tag = \"Long\")"
3853
+ },
3854
+ "spec": "stdlib.md 17.1, 17.2",
3855
+ "refines": null,
3856
+ "test": "tests/importer/orders.test.ts"
3857
+ },
3858
+ {
3859
+ "code": "OS9011",
3860
+ "title": "Equality over two absent values",
3861
+ "severity": "warning",
3862
+ "stage": "import",
3863
+ "since": 1,
3864
+ "message": "Both sides of {operator} can be absent on the same bar, and OpenScript counts two absent values as equal, which the original is not guaranteed to do.",
3865
+ "placeholders": {
3866
+ "operator": "the equality operator, == or !="
3867
+ },
3868
+ "cause": "OpenScript's equality is total: none == none is true and none != none is false, so that a test for absence can be written as a comparison (language.md 6.5). The source dialect does not count two absent values as equal. Where both sides of an equality can be absent together, which is every warmup bar of two averages, the translation and the original can take different branches on exactly those bars.",
3869
+ "fix": "If the original relied on two absent values never comparing equal, guard the translated comparison with isNone() on either side.",
3870
+ "autofix": false,
3871
+ "example": {
3872
+ "kind": "import",
3873
+ "before": "//@version=5\nindicator(\"Touch\")\nfast = ta.sma(close, 5)\nslow = ta.sma(close, 20)\nplot(fast == slow ? 1 : 0)",
3874
+ "after": "version 1\n\nstudy(\"Touch\")\nfast = sma(close, 5)\nslow = sma(close, 20)\nplot(not isNone(fast) and fast == slow ? 1 : 0, \"Touch\")"
3875
+ },
3876
+ "spec": "language.md 6.5",
3877
+ "refines": null,
3878
+ "test": "tests/importer/differences.test.ts"
3879
+ },
3880
+ {
3881
+ "code": "OS9012",
3882
+ "title": "The translation does not compile",
3883
+ "severity": "error",
3884
+ "stage": "import",
3885
+ "since": 1,
3886
+ "message": "The OpenScript compiler refused the translation of this statement with {code}, so the statement was kept as a comment.",
3887
+ "placeholders": {
3888
+ "code": "the catalogue code the compiler raised on the translated text"
3889
+ },
3890
+ "cause": "The importer compiles its own output before it returns it, so that what it hands back compiles. A statement can translate construct by construct and still be refused as a whole: version 5 of the source dialect lets a number stand where a condition is expected, and the source dialect lets a name inside a block keep a history and lets two values of different types meet where OpenScript refuses. The importer does not type the source script, so it learns of these from the compiler, keeps the statement as a comment and compiles again.",
3891
+ "fix": "Look up {code} in this catalogue and translate the statement by hand in the form that code's fix describes.",
3892
+ "autofix": false,
3893
+ "example": {
3894
+ "kind": "import",
3895
+ "before": "//@version=5\nindicator(\"Traded\")\ntraded = volume ? 1 : 0\nplot(traded)",
3896
+ "after": "version 1\n\nstudy(\"Traded\")\ntraded = volume > 0 ? 1 : 0\nplot(traded, \"Traded\")"
3897
+ },
3898
+ "spec": "docs/writing/importing-a-script.md 4",
3899
+ "refines": null,
3900
+ "test": "tests/importer/refusals.test.ts"
3541
3901
  }
3542
3902
  ]
3543
3903
  }
@@ -39,6 +39,7 @@ import type { SourceFile } from '../../core/index.js';
39
39
  import { hostBar, hostNow, stateFor } from './bars.js';
40
40
  import type { ChartBar, ChartCalcContext, ChartSettings, ChartStore } from './contract.js';
41
41
  import { refused, stopped } from './errors.js';
42
+ import { undrawable } from './undrawable.js';
42
43
  import { stationIn, stationOf } from './requests.js';
43
44
  import { engineSettings, signatureOf } from './settings.js';
44
45
  import { answersFor, needsVenue, routeInto, venueFor } from './venue.js';
@@ -335,6 +336,11 @@ function start(
335
336
  ? { ...options, orders: routeInto(holder) }
336
337
  : options;
337
338
 
339
+ // What this chart has no room for is refused before the engine is asked,
340
+ // because a study drawn without it is a study that looks broken.
341
+ const narrower = undrawable(program);
342
+ if (narrower !== undefined) throw refused(narrower);
343
+
338
344
  const loaded = load(program, {
339
345
  settings: engineSettings(program, settings),
340
346
  host: hostFor(ctx, withRoute, requests),
@@ -28,25 +28,12 @@
28
28
  * direction: a cell nothing was written into is blank, never a zero.
29
29
  *
30
30
  * **A chart pane holds one grid and the language declares as many as it likes.**
31
- * The descriptor has one `table` hook, so the first declared grid is the one
32
- * drawn and any after it are not. That is a gap in the chart's own contract
33
- * rather than a choice worth making here: there is no second hook to write to,
34
- * and merging two grids into one would put cells somewhere the script never
35
- * asked for.
36
- *
37
- * **What ought to happen is a refusal, and it cannot be written yet.** A study
38
- * whose second grid never appears is a study with no explanation, so the second
39
- * `table()` should be refused where it is written, naming the limit. Every
40
- * diagnostic in this project carries a catalogue code and the catalogue has
41
- * none for "the chart cannot draw something this study declares"; inventing one
42
- * here is the one thing that is not allowed, so the gap is reported instead, in
43
- * `issues/0011-a-second-declared-grid-is-dropped-with-nothing-said.md`, with the
44
- * sentence it should carry.
45
- *
46
- * Until it lands, the drop is at least no longer silent inside this repository:
47
- * it is recorded in `spec/chart-narrowings.json` with its reason, and
48
- * `scripts/check-chart-surface.mjs` fails the build if this adapter ever drops
49
- * anything else a compiled program declares without recording it there.
31
+ * The descriptor has one `table` hook, and merging two grids into it would put
32
+ * cells somewhere the script never asked for. So a program declaring a second
33
+ * grid never reaches this file: `undrawable.ts` refuses it before any bar runs,
34
+ * with OS6024, and this reads the one grid a program that runs can have.
35
+ * `spec/chart-narrowings.json` records the refusal and
36
+ * `scripts/check-chart-surface.mjs` proves it with a study that declares two.
50
37
  */
51
38
  import type { CompiledProgram, Grid as DeclaredGrid } from '../../core/emit/index.js';
52
39
  import type { Grid, GridCell } from '../../core/engine/index.js';
@@ -0,0 +1,51 @@
1
+ /**
2
+ * What this chart cannot draw of what a program declares, refused before a bar.
3
+ *
4
+ * `compiled-program.md` section 11: a host draws what its surface has room for,
5
+ * and refuses what it does not with OS6024 rather than drawing part of a study
6
+ * and saying nothing. Two things a version 1 program can declare have no place
7
+ * on this chart's surface, and both used to be dropped in silence.
8
+ *
9
+ * - **A second grid.** The descriptor has one `table` hook, so one grid reaches
10
+ * a pane. Merging two into it would put cells somewhere the script never
11
+ * asked for, and drawing the first alone is a study whose second panel never
12
+ * appears and whose cells look broken.
13
+ * - **A band colour computed per bar.** The chart's band takes one colour for
14
+ * the whole run and has no channel to point at, so a band whose colour the
15
+ * script computes was drawn in the first plot's colour faded, which is a
16
+ * colour the script did not choose.
17
+ *
18
+ * The compiled program carries no source position for a declaration, so the
19
+ * refusal names the declaration by its title, which is what a reader sees in
20
+ * the legend and the settings dialog, and its span is the load's own.
21
+ */
22
+ import { diagnosticFor, makeSpan } from '../../core/index.js';
23
+ import type { Diagnostic } from '../../core/index.js';
24
+ import type { CompiledProgram, Field } from '../../core/emit/index.js';
25
+
26
+ /** A load refusal has no line of its own: the program is what was refused. */
27
+ const AT_LOAD = makeSpan(0, 0, 0, 0);
28
+
29
+ export function undrawable(program: CompiledProgram): Diagnostic | undefined {
30
+ const second = program.outputs.tables[1];
31
+ if (second !== undefined) {
32
+ return diagnosticFor('OS6024', AT_LOAD, {
33
+ what: `the table ${titled(second.title, 'after the first')}, the second grid this study declares`,
34
+ limit: 'a chart pane draws one grid',
35
+ });
36
+ }
37
+ for (const band of program.outputs.fills) {
38
+ if (band.colorUpChannel !== null || band.colorDownChannel !== null) {
39
+ return diagnosticFor('OS6024', AT_LOAD, {
40
+ what: "a band's colour that is computed per bar",
41
+ limit: "a chart's band takes one colour for the whole run",
42
+ });
43
+ }
44
+ }
45
+ return undefined;
46
+ }
47
+
48
+ /** A title as a reader sees it, or a description where it is a setting's. */
49
+ function titled(title: Field, otherwise: string): string {
50
+ return typeof title === 'string' ? `"${title}"` : otherwise;
51
+ }