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
@@ -0,0 +1,362 @@
1
+ /**
2
+ * What a fill costs, as an ordered list of lines the platform brings.
3
+ *
4
+ * **The language has three commission spellings and a slippage in ticks, and a
5
+ * real cost stack is not shaped like that.** It is a flat fee, a percentage, a
6
+ * charge levied on a charge, and a tax that applies to one side of the trade
7
+ * only. Teaching the language one market's stack would be teaching it a market;
8
+ * so the platform passes a schedule instead, and the language keeps the three
9
+ * spellings it has, which turn into a schedule of one line.
10
+ *
11
+ * **Order is part of the result.** The lines are applied in the order they are
12
+ * declared, a line charged on other lines may only name lines declared before
13
+ * it, and that is what makes a schedule evaluable in exactly one order. Two
14
+ * engines that disagree about the order disagree about the money, and a
15
+ * disagreement in the last bit is still a failed conformance comparison
16
+ * (`conformance.md` 6).
17
+ *
18
+ * **Where it is applied is not here.** `stdlib.md` 17.1 puts slippage and
19
+ * commission on the destination: the engine folds the price it is told and
20
+ * never adjusts one, so a cost model inside the ledger would be the engine
21
+ * moving a price, which is the one thing the invariant forbids. This module
22
+ * says what a charge is and works out what one fill came to; the destination is
23
+ * what charges it, and the slippage a schedule carries is measured and applied
24
+ * there, against the tick size this module only refuses the absence of.
25
+ *
26
+ * **What is charged to a fill, and only to a fill.** Every line is measured
27
+ * against this fill and nothing else, so a tier that changes with the month's
28
+ * cumulative volume, a cap counted per day rather than per application, and
29
+ * margin and its interest are outside this model rather than approximated
30
+ * inside it. A cost model that quietly approximates is a report that is wrong
31
+ * in the strategy's favour and says nothing about it.
32
+ */
33
+ import { diagnosticFor } from '../diagnostics/index.js';
34
+ /**
35
+ * The most digits money is rounded to, and why there is a ceiling at all.
36
+ *
37
+ * A rounding scale is a power of ten and a binary64 holds about fifteen
38
+ * significant decimal digits, so past this the scale itself is approximate and
39
+ * the rounding stops being arithmetic and becomes noise. A currency with more
40
+ * than fifteen decimal places is not a currency this module is refusing to
41
+ * support; it is a digit count nobody stated on purpose.
42
+ */
43
+ const MAX_DIGITS = 15;
44
+ /** A percentage, as the declaration states one, over the fraction a rate is. */
45
+ const PERCENT = 100;
46
+ /** What a refusal calls the setting it is about, `errors.md` OS6021. */
47
+ const SETTING = 'The charge schedule';
48
+ /**
49
+ * Where a refusal about a setting points.
50
+ *
51
+ * Nowhere in the script, because a schedule is not something anybody wrote in
52
+ * one: it is what the host stated before the first bar, and a caret drawn under
53
+ * a line of the strategy would blame the one party who did not choose it. The
54
+ * engine gives a load-time failure the same position for the same reason, and
55
+ * the number is written here rather than imported from it because this module
56
+ * imports no engine, which is what lets a stored record be reported again with
57
+ * no engine present.
58
+ */
59
+ const NO_POSITION = { offset: 0, length: 0, line: 0, column: 0 };
60
+ /**
61
+ * What one fill cost, line by line and in total.
62
+ *
63
+ * **The lines are the arithmetic and the total is the money.** Each line's
64
+ * amount is computed in binary64 and left unrounded, and the per-fill total is
65
+ * rounded once, half to even, to the contract's digits. Rounding each line
66
+ * would round once per line, and two engines rounding in two places disagree in
67
+ * the last bit, which is a failed conformance comparison months later on
68
+ * somebody else's engine. So a reader adding the lines up by hand may land a
69
+ * fraction of the last digit away from the total, and that is the honest way
70
+ * round: the total is the figure the report accumulates.
71
+ *
72
+ * **Half to even, and not the language's own rounding.** `round()` in the
73
+ * language is halves away from zero, because a price a trader reads should
74
+ * agree with what they would write down (`stdlib.md` 8.1). Money folded over
75
+ * thousands of fills is a different question: away from zero biases every exact
76
+ * half upward, and half a unit of the last digit per fill is a bias that grows
77
+ * with the length of the backtest. Two rules, two reasons, both written down.
78
+ *
79
+ * **A line that does not apply to this side is not in the breakdown at all.** A
80
+ * name beside a zero reads as a charge that was levied and came to nothing,
81
+ * which is not what happened, and a later line levied on that name is levied on
82
+ * nothing, which is exactly what a tax on one side of the trade does.
83
+ */
84
+ export function chargeFor(schedule, fill, contract) {
85
+ const turnover = fill.units * fill.price * contract.pointValue;
86
+ const applied = [];
87
+ for (const line of schedule.lines) {
88
+ if (line.side !== 'both' && line.side !== fill.side)
89
+ continue;
90
+ const base = baseOf(line, turnover, fill.units, applied);
91
+ applied.push({ name: line.name, amount: bounded(line.rate * base, line) });
92
+ }
93
+ let exact = 0;
94
+ for (const one of applied)
95
+ exact += one.amount;
96
+ return { lines: applied, total: roundMoney(exact, contract.digits) };
97
+ }
98
+ /**
99
+ * What a line's rate is measured against, for this fill.
100
+ *
101
+ * The earlier lines are searched rather than indexed. An index would be a map,
102
+ * and this module has promised to depend on no map's iteration order; a
103
+ * schedule is a handful of lines, so the search costs nothing and the promise
104
+ * costs one less thing to be careful about.
105
+ */
106
+ function baseOf(line, turnover, units, applied) {
107
+ if (line.base === 'turnover')
108
+ return turnover;
109
+ if (line.base === 'units')
110
+ return units;
111
+ if (line.base === 'order')
112
+ return 1;
113
+ // 'charges': the sum of the named earlier lines, as they were charged on this
114
+ // fill. A name whose line did not apply to this side is absent and adds
115
+ // nothing, which is a charge levied on a charge that was never taken.
116
+ let sum = 0;
117
+ for (const named of line.of) {
118
+ for (const one of applied) {
119
+ if (one.name === named)
120
+ sum += one.amount;
121
+ }
122
+ }
123
+ return sum;
124
+ }
125
+ /**
126
+ * The floor and the cap, per application.
127
+ *
128
+ * Both together is the common brokerage plan: a fraction of turnover, never
129
+ * less than one amount and never more than another. Which is applied first does
130
+ * not decide the answer, because a floor above a cap is refused before the
131
+ * first bar rather than resolved here by whichever comparison runs first.
132
+ */
133
+ function bounded(raw, line) {
134
+ let amount = raw;
135
+ if (line.min !== null && amount < line.min)
136
+ amount = line.min;
137
+ if (line.max !== null && amount > line.max)
138
+ amount = line.max;
139
+ return amount;
140
+ }
141
+ /**
142
+ * One money figure, rounded once, halves to even.
143
+ *
144
+ * A digit count this cannot round by is one `scheduleProblem` refuses before
145
+ * the first bar. If one arrives anyway the amount is returned as it stands,
146
+ * because a scale of ten to the power of something impossible turns money into
147
+ * a number JSON carries as null, and an unrounded figure is worth more.
148
+ */
149
+ function roundMoney(amount, digits) {
150
+ if (!Number.isFinite(amount))
151
+ return amount;
152
+ if (!Number.isInteger(digits) || digits < 0 || digits > MAX_DIGITS)
153
+ return amount;
154
+ const scale = 10 ** digits;
155
+ const scaled = amount * scale;
156
+ const below = Math.floor(scaled);
157
+ const fraction = scaled - below;
158
+ let whole = below;
159
+ if (fraction > 0.5)
160
+ whole = below + 1;
161
+ else if (fraction === 0.5 && below % 2 !== 0)
162
+ whole = below + 1;
163
+ const money = whole / scale;
164
+ // A negative zero is the same money as a zero and a different set of bytes.
165
+ return money === 0 ? 0 : money;
166
+ }
167
+ /**
168
+ * The declaration's own cost model, as the one schedule this module evaluates.
169
+ *
170
+ * **The declaration is not a second cost engine.** Its three commission
171
+ * spellings are a schedule of one line: a flat fee is a line charged per fill,
172
+ * a per unit fee is a line charged per unit, and a percentage is a line charged
173
+ * on turnover. A second evaluator for the declaration would be the same money
174
+ * computed two ways, and the day the two disagreed the report and the
175
+ * platform's own cost panel would both be defensible.
176
+ *
177
+ * **A commission of zero is no line at all**, rather than a line charging
178
+ * nothing. A zero line would put a name in every breakdown, and it would make a
179
+ * declaration that states no commission indistinguishable from one that states
180
+ * a commission, which is the distinction a run has to make before it accepts a
181
+ * schedule from the host as well.
182
+ *
183
+ * **A flat fee is charged per fill**, which is the one place `language.md` 13.3
184
+ * lets a reasonable person read the words two ways: per order, or per completed
185
+ * round trip. A charge is attributed to the fill that incurred it everywhere in
186
+ * this module, because that is what attributes it to a trade, so a round trip of
187
+ * two fills is charged twice.
188
+ *
189
+ * The currency and the digit count are parameters because neither is the
190
+ * declaration's to state: the declaration's currency is a label and is often
191
+ * left blank, and money rounding is a fact about the contract. A default here
192
+ * would be this module inventing a rounding rule for somebody else's market.
193
+ *
194
+ * `commissionType`'s value set is declared once, in `check/declaration.ts`, and
195
+ * is not restated here: what is below is a mapping from each spelling to the
196
+ * line it means. A program reaching this has been checked, so a fourth spelling
197
+ * cannot arrive, and the spelling that falls through is the declaration's own
198
+ * default rather than a shape this module made up.
199
+ */
200
+ export function scheduleFromDeclaration(commission, commissionType, slippage, currency, digits) {
201
+ return {
202
+ currency,
203
+ digits,
204
+ slippageTicks: slippage,
205
+ lines: commission === 0 ? [] : [commissionLine(commission, commissionType)],
206
+ source: 'declaration',
207
+ };
208
+ }
209
+ /** The one line a declared commission is, in the base its spelling names. */
210
+ function commissionLine(commission, commissionType) {
211
+ if (commissionType === 'perUnit')
212
+ return only('units', commission);
213
+ if (commissionType === 'percent')
214
+ return only('turnover', commission / PERCENT);
215
+ return only('order', commission);
216
+ }
217
+ /** A line with no bounds, on both sides, levied on nothing: the declaration's shape. */
218
+ function only(base, rate) {
219
+ return { name: 'commission', base, side: 'both', rate, min: null, max: null, of: [] };
220
+ }
221
+ /**
222
+ * Why this schedule cannot be carried out, or null.
223
+ *
224
+ * **Asked before the first bar, and answered once.** Everything here is a fact
225
+ * about the schedule rather than about any fill, so a run that would produce a
226
+ * number nobody can explain is refused while nothing has been computed and the
227
+ * cost of correcting it is one run. The contract is optional because a schedule
228
+ * is checkable on its own: what it adds is the three questions that need both,
229
+ * which are the currency the money is in, the digits it is rounded to, and the
230
+ * tick a slippage in ticks is measured in.
231
+ *
232
+ * The first problem found is the one reported. A list of everything wrong with
233
+ * a schedule reads as a worse schedule than it is, and it is corrected one line
234
+ * at a time regardless.
235
+ */
236
+ export function scheduleProblem(schedule, contract = null) {
237
+ const problem = moneyProblem(schedule, contract) ??
238
+ slippageProblem(schedule, contract) ??
239
+ linesProblem(schedule.lines);
240
+ if (problem === null)
241
+ return null;
242
+ return diagnosticFor('OS6021', NO_POSITION, { setting: SETTING, problem });
243
+ }
244
+ /**
245
+ * The currency the money is in and the digits it is rounded to.
246
+ *
247
+ * A schedule states both and so does the contract, and the two are compared
248
+ * here rather than one of them being quietly preferred. A schedule in another
249
+ * currency charges a fill in money the contract is not priced in, and a total
250
+ * nobody can add to the profit is worse than no total. A schedule rounding to
251
+ * other digits is the same fact stated twice and left to disagree.
252
+ */
253
+ function moneyProblem(schedule, contract) {
254
+ const digits = schedule.digits;
255
+ if (!Number.isInteger(digits) || digits < 0 || digits > MAX_DIGITS) {
256
+ return `it rounds money to ${digits} digits, and a digit count is a whole number from 0 to ${MAX_DIGITS}`;
257
+ }
258
+ if (schedule.currency.trim() === '') {
259
+ return 'it names no currency, so what it charges is a number with no unit on it';
260
+ }
261
+ if (contract === null)
262
+ return null;
263
+ if (schedule.currency !== contract.currency) {
264
+ return `it charges in ${schedule.currency} and the contract is priced in ${contract.currency}`;
265
+ }
266
+ if (digits !== contract.digits) {
267
+ return `it rounds money to ${digits} digits and the contract rounds to ${contract.digits}`;
268
+ }
269
+ return null;
270
+ }
271
+ /**
272
+ * The slippage, which this module refuses and does not apply.
273
+ *
274
+ * A slippage in ticks with no tick size to measure a tick in would charge
275
+ * nothing at all, and a backtest that silently charges nothing is one that lies
276
+ * in the strategy's favour. It is refused here, where the schedule is checked,
277
+ * rather than at the fill, where a zero looks like a cost model that ran.
278
+ */
279
+ function slippageProblem(schedule, contract) {
280
+ const ticks = schedule.slippageTicks;
281
+ if (!Number.isFinite(ticks) || ticks < 0) {
282
+ return `it states ${ticks} ticks of slippage, and slippage is adverse, so it is never negative`;
283
+ }
284
+ if (ticks === 0 || contract === null)
285
+ return null;
286
+ const tick = contract.tickSize;
287
+ if (tick === null || !(tick > 0)) {
288
+ return `it states ${ticks} ticks of slippage and the contract has no tick size to measure a tick in`;
289
+ }
290
+ return null;
291
+ }
292
+ /** Every line, in the order the schedule declares them, against the ones before it. */
293
+ function linesProblem(lines) {
294
+ const declared = [];
295
+ for (const line of lines) {
296
+ const problem = lineProblem(line, declared);
297
+ if (problem !== null)
298
+ return problem;
299
+ declared.push(line.name);
300
+ }
301
+ return null;
302
+ }
303
+ /** One line, against the names declared before it. */
304
+ function lineProblem(line, declared) {
305
+ const name = line.name;
306
+ if (name.trim() === '') {
307
+ return 'a line carries no name, and a line levied on charges names the lines it is levied on';
308
+ }
309
+ if (declared.includes(name)) {
310
+ return `two lines are named "${name}", so a line levied on that name is levied on two answers`;
311
+ }
312
+ if (!Number.isFinite(line.rate) || line.rate < 0) {
313
+ return `the line "${name}" charges a rate of ${line.rate}, and a charge is money taken, never given`;
314
+ }
315
+ return boundProblem(line) ?? levyProblem(line, declared);
316
+ }
317
+ /** The floor and the cap: money, not negative, and the floor no higher than the cap. */
318
+ function boundProblem(line) {
319
+ const { max, min, name } = line;
320
+ if (min !== null && (!Number.isFinite(min) || min < 0)) {
321
+ return `the line "${name}" has a floor of ${min}, and a bound on a charge is money`;
322
+ }
323
+ if (max !== null && (!Number.isFinite(max) || max < 0)) {
324
+ return `the line "${name}" has a cap of ${max}, and a bound on a charge is money`;
325
+ }
326
+ if (min !== null && max !== null && min > max) {
327
+ return `the line "${name}" has a floor of ${min} above its cap of ${max}`;
328
+ }
329
+ return null;
330
+ }
331
+ /**
332
+ * What a line is levied on, which is the rule the whole ordering exists for.
333
+ *
334
+ * A line levied on lines not declared before it has no single evaluation order,
335
+ * so two engines would charge two different amounts and both would be
336
+ * defensible. Naming itself, naming a line declared after it and naming a line
337
+ * that is not in the schedule at all are one problem in three spellings, and
338
+ * they are reported as one: none of the three is declared before this line.
339
+ */
340
+ function levyProblem(line, declared) {
341
+ const name = line.name;
342
+ if (line.base !== 'charges') {
343
+ if (line.of.length === 0)
344
+ return null;
345
+ return `the line "${name}" names ${line.of.length} lines to be levied on and its base is ${line.base}, so the names are read by nothing`;
346
+ }
347
+ if (line.of.length === 0) {
348
+ return `the line "${name}" is levied on charges and names none, so it is levied on nothing`;
349
+ }
350
+ const seen = [];
351
+ for (const named of line.of) {
352
+ if (!declared.includes(named)) {
353
+ return `the line "${name}" is levied on "${named}", which is not declared before it`;
354
+ }
355
+ if (seen.includes(named)) {
356
+ return `the line "${name}" is levied on "${named}" twice, so that line is charged on twice over`;
357
+ }
358
+ seen.push(named);
359
+ }
360
+ return null;
361
+ }
362
+ //# sourceMappingURL=charges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"charges.js","sourceRoot":"","sources":["../../../src/core/accounting/charges.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,191 @@
1
+ /**
2
+ * The equity curve: one point per bar in the report window.
3
+ *
4
+ * **Marked to the close and to nothing else.** An intrabar extreme is a price
5
+ * the strategy could not have acted on, so it is not a profit it had, and a
6
+ * curve drawn through the extremes flatters every run that ever held a losing
7
+ * position. Where a bar's close is absent the previous mark carries and the
8
+ * point says so, rather than a zero a reader would compare against.
9
+ *
10
+ * **Drawdown is stated on equity including open profit**, and the basis is
11
+ * carried on every point rather than left to the reader: `drawdown` is the
12
+ * distance below the running peak, zero or negative, and `drawdownPercent` is
13
+ * that distance against the peak. A report whose drawdown basis is not written
14
+ * down is a report whose worst figure means a different thing to each reader.
15
+ *
16
+ * ## What the curve is folded from, and when each figure lands
17
+ *
18
+ * A trade list and a list of bar closes, and nothing else. A trade carries its
19
+ * own gross, its own charges and the two bars it lived between, so the fold is
20
+ * a sweep: bars in the order they arrived, trades in the order they opened.
21
+ *
22
+ * - **A trade's charges land on the bar it opened.** The trade list does not
23
+ * carry the timing of the individual fills underneath it, so the cost has to
24
+ * land somewhere, and the open is the one place that is never later than the
25
+ * truth: an entry charge is paid the moment the trade is taken on, and an
26
+ * exit charge cannot be paid before it. The alternative, landing the whole
27
+ * cost at the close, shows a run carrying a position for two hundred bars as
28
+ * having paid nothing for it, and leaves a trade that never closed paying
29
+ * nothing at all.
30
+ * - **A trade's gross lands on the bar it closed**, because that is the bar it
31
+ * stopped being an opinion and became a number. While it is open it is in
32
+ * `openProfit` instead, marked to the close, and the two never overlap.
33
+ *
34
+ * So the last point of a full run carries every charge the run paid, open
35
+ * trades included, and every gross it realised, which is an identity the
36
+ * summary is asserted against rather than assumed to share.
37
+ *
38
+ * ## What a trade list cannot say, said here rather than discovered later
39
+ *
40
+ * A trade holds one entry price weighted over its entry fills and one exit
41
+ * price weighted over its exit fills, so a trade whose size changed while it
42
+ * was open is not visible in it: the size a trade held between its open and its
43
+ * close is the size it ended up entering, at the average price it ended up
44
+ * entering at. Both directions are wrong and they are wrong differently.
45
+ *
46
+ * - **A partial close** is marked at the full size from the bar the reduction
47
+ * settled on, so the open profit of the part already closed is counted twice
48
+ * over, once here and once in the realised total.
49
+ * - **A scale-in is the worse of the two**, because it is wrong from the
50
+ * beginning rather than from the middle. A trade that buys a hundred at ten
51
+ * and another hundred at twenty is marked, from the bar it first opened, as
52
+ * two hundred units bought at fifteen. On the bars before the second entry it
53
+ * is therefore marked five points under water on units it did not hold, and
54
+ * the curve reports a drawdown the account never had. The summary's
55
+ * `maxDrawdown` and `maxDrawdownPercent` are folded from this curve, so a
56
+ * pyramiding strategy is reported as having risked more than it did.
57
+ *
58
+ * Neither reaches the realised total, which is folded from the fills: what is
59
+ * affected is `openProfit`, `exposure`, `equity` and every drawdown figure
60
+ * taken off the curve, on the bars a trade's size was not what it ended as.
61
+ *
62
+ * A curve folded from the fills rather than from the trades would have neither,
63
+ * and that is what fixes it: it needs each entry, exit and charge to land on
64
+ * the bar it settled on, which is a different fold from this one rather than a
65
+ * correction to it. It is written here because a limitation nobody wrote down
66
+ * is a limitation somebody finds inside a report they had already believed.
67
+ *
68
+ * ## Two preconditions, both of them the caller's
69
+ *
70
+ * The trades arrive in the order they opened, which is the order `Trade.index`
71
+ * states, and the bars arrive in the order they were loaded. Both are swept
72
+ * with a pointer rather than searched, because a report over fifty thousand
73
+ * bars that rescans its trade list on every one of them is a report nobody
74
+ * waits for.
75
+ *
76
+ * ## And a percentage here is a fraction of its basis
77
+ *
78
+ * `drawdownPercent` is `drawdown / runningPeak`, which is a fraction: a
79
+ * hundredth of a percent down is written `-0.0001` and not `-0.01`. One
80
+ * convention for every figure in this module that divides, because two figures
81
+ * spelled the same way in two units is a number a reader gets wrong once and
82
+ * never trusts again. The multiplication by a hundred belongs to whatever
83
+ * prints it.
84
+ */
85
+ import type { BarMark, Contract, Money } from './shapes.js';
86
+ import type { Trade } from './trades.js';
87
+ /** One report bar's standing, folded from the fills settled up to it. */
88
+ export interface EquityPoint {
89
+ readonly barIndex: number;
90
+ readonly time: number | null;
91
+ /** Cumulative gross of closed trades. */
92
+ readonly realised: Money;
93
+ /** Cumulative. */
94
+ readonly charges: Money;
95
+ /** Marked to this bar's close. */
96
+ readonly openProfit: Money;
97
+ /** capital + realised - charges. */
98
+ readonly cash: Money;
99
+ /** cash + openProfit. */
100
+ readonly equity: Money;
101
+ /** The open position's magnitude at this close. */
102
+ readonly exposure: Money;
103
+ /** equity - runningPeak, zero or negative. */
104
+ readonly drawdown: Money;
105
+ /** `drawdown / runningPeak`, a fraction and not a figure times a hundred. */
106
+ readonly drawdownPercent: number;
107
+ /**
108
+ * The distance above the running trough, zero or positive.
109
+ *
110
+ * Drawdown's mirror, and carried for the same reason drawdown is: it is the
111
+ * run's best stretch measured the same way its worst one is, so the two can
112
+ * be read against each other. A reader handed only the depth learns how bad
113
+ * it got and nothing about whether the run ever climbed far enough for that
114
+ * depth to be a giving back rather than a steady bleed.
115
+ */
116
+ readonly runUp: Money;
117
+ /**
118
+ * `runUp / runningTrough`, a fraction and not a figure times a hundred, and
119
+ * zero where the trough is not above zero.
120
+ *
121
+ * Not the same guard drawdown gets, because the two bases are not the same
122
+ * kind of number. The running peak starts at the capital and only ever rises,
123
+ * so a run with capital above zero has a peak above zero at every point. The
124
+ * running trough starts there and only ever falls, so an account that lost
125
+ * everything has a trough at or below zero, and from that bar on this figure
126
+ * is zero rather than a fraction. Zero is the wrong answer for a run that
127
+ * recovered, and it is reported anyway, because the alternative is a
128
+ * percentage against a negative basis: a positive climb reported as a
129
+ * negative fraction, which is the shape that once gave a profit factor of
130
+ * minus a half. `runUp` itself is unaffected and stays the figure to read on
131
+ * such a run.
132
+ */
133
+ readonly runUpPercent: number;
134
+ }
135
+ /**
136
+ * Whether this trade was still held at the close of this bar.
137
+ *
138
+ * The boundaries are the whole of the rule and both are decisions. A trade is
139
+ * held from the close of the bar it opened on, because an entry that settled
140
+ * during a bar is a position that bar ended holding. It is not held at the
141
+ * close of the bar it closed on, because that bar ended flat. So a trade
142
+ * opened and closed inside one bar is held at no close at all, which is what a
143
+ * run that was flat at every mark should report.
144
+ *
145
+ * Every figure in this module that asks which trades are open asks it here, so
146
+ * the curve, the exposure and the count of bars in the market cannot come to
147
+ * three different answers about one bar.
148
+ */
149
+ export declare function openOnBar(trade: Trade, barIndex: number): boolean;
150
+ /**
151
+ * A ratio against a basis that may not be there to divide by.
152
+ *
153
+ * Capital of zero and a peak of zero are both reachable, and both turn an
154
+ * honest division into a value JSON cannot carry: a report whose worst figure
155
+ * comes back `null` from a round trip is worse than one that says zero. A basis
156
+ * that is not positive has no ratio to state, so the figure beside it, which is
157
+ * money and is always true, is the one a reader is left with.
158
+ */
159
+ export declare function ratioOf(value: number, basis: number): number;
160
+ /**
161
+ * The curve, one point per report bar, in the order the bars arrived.
162
+ *
163
+ * Warmup bars are swept and not reported: their orders were real, so a trade
164
+ * opened during the warmup is already in the fold at the first point, with its
165
+ * charges already paid and its position already marked. A curve that began its
166
+ * fold at the first report bar would lose both, and would lose them silently.
167
+ *
168
+ * The running peak starts at the capital rather than at the first point, so a
169
+ * run that is down from its first bar is in drawdown at its first bar. Starting
170
+ * it at the first point would report every run as having begun at its high.
171
+ *
172
+ * The running trough starts at the capital for the same reason and not for a
173
+ * symmetrical one: a run that is up from its first bar has run up from the
174
+ * money it was given, which is the figure a reader is measuring against. Anchor
175
+ * it at the first point instead and a run that gapped up on bar zero reports
176
+ * that gain as having come from nowhere.
177
+ */
178
+ export declare function equityOver(trades: readonly Trade[], marks: readonly BarMark[], contract: Contract, capital: Money): readonly EquityPoint[];
179
+ /**
180
+ * How many of these bars ended with something held.
181
+ *
182
+ * Swept rather than searched: a sorted list of the bars trades opened on, a
183
+ * sorted list of the bars they closed on, and the running difference between
184
+ * how many of each have gone by. That is the boundary rule `openOnBar` states,
185
+ * arrived at from the other side, and the two are asserted to agree over a
186
+ * generated corpus rather than trusted to. A count that disagrees with the
187
+ * curve beside it about which bars were in the market is exactly the kind of
188
+ * defect a reader finds by adding two of the printed figures up.
189
+ */
190
+ export declare function barsInMarketOver(trades: readonly Trade[], equity: readonly EquityPoint[]): number;
191
+ //# sourceMappingURL=equity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"equity.d.ts","sourceRoot":"","sources":["../../../src/core/accounting/equity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmFG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IACzB,kBAAkB;IAClB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB,kCAAkC;IAClC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IACvB,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,SAAS,KAAK,EAAE,EACxB,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,KAAK,GACb,SAAS,WAAW,EAAE,CAyExB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,EACxB,MAAM,EAAE,SAAS,WAAW,EAAE,GAC7B,MAAM,CAuBR"}