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,314 @@
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
+
88
+ /** One report bar's standing, folded from the fills settled up to it. */
89
+ export interface EquityPoint {
90
+ readonly barIndex: number;
91
+ readonly time: number | null;
92
+ /** Cumulative gross of closed trades. */
93
+ readonly realised: Money;
94
+ /** Cumulative. */
95
+ readonly charges: Money;
96
+ /** Marked to this bar's close. */
97
+ readonly openProfit: Money;
98
+ /** capital + realised - charges. */
99
+ readonly cash: Money;
100
+ /** cash + openProfit. */
101
+ readonly equity: Money;
102
+ /** The open position's magnitude at this close. */
103
+ readonly exposure: Money;
104
+ /** equity - runningPeak, zero or negative. */
105
+ readonly drawdown: Money;
106
+ /** `drawdown / runningPeak`, a fraction and not a figure times a hundred. */
107
+ readonly drawdownPercent: number;
108
+ /**
109
+ * The distance above the running trough, zero or positive.
110
+ *
111
+ * Drawdown's mirror, and carried for the same reason drawdown is: it is the
112
+ * run's best stretch measured the same way its worst one is, so the two can
113
+ * be read against each other. A reader handed only the depth learns how bad
114
+ * it got and nothing about whether the run ever climbed far enough for that
115
+ * depth to be a giving back rather than a steady bleed.
116
+ */
117
+ readonly runUp: Money;
118
+ /**
119
+ * `runUp / runningTrough`, a fraction and not a figure times a hundred, and
120
+ * zero where the trough is not above zero.
121
+ *
122
+ * Not the same guard drawdown gets, because the two bases are not the same
123
+ * kind of number. The running peak starts at the capital and only ever rises,
124
+ * so a run with capital above zero has a peak above zero at every point. The
125
+ * running trough starts there and only ever falls, so an account that lost
126
+ * everything has a trough at or below zero, and from that bar on this figure
127
+ * is zero rather than a fraction. Zero is the wrong answer for a run that
128
+ * recovered, and it is reported anyway, because the alternative is a
129
+ * percentage against a negative basis: a positive climb reported as a
130
+ * negative fraction, which is the shape that once gave a profit factor of
131
+ * minus a half. `runUp` itself is unaffected and stays the figure to read on
132
+ * such a run.
133
+ */
134
+ readonly runUpPercent: number;
135
+ }
136
+
137
+ /**
138
+ * Whether this trade was still held at the close of this bar.
139
+ *
140
+ * The boundaries are the whole of the rule and both are decisions. A trade is
141
+ * held from the close of the bar it opened on, because an entry that settled
142
+ * during a bar is a position that bar ended holding. It is not held at the
143
+ * close of the bar it closed on, because that bar ended flat. So a trade
144
+ * opened and closed inside one bar is held at no close at all, which is what a
145
+ * run that was flat at every mark should report.
146
+ *
147
+ * Every figure in this module that asks which trades are open asks it here, so
148
+ * the curve, the exposure and the count of bars in the market cannot come to
149
+ * three different answers about one bar.
150
+ */
151
+ export function openOnBar(trade: Trade, barIndex: number): boolean {
152
+ if (barIndex < trade.openedOnBar) return false;
153
+ return trade.closedOnBar === null || barIndex < trade.closedOnBar;
154
+ }
155
+
156
+ /**
157
+ * A ratio against a basis that may not be there to divide by.
158
+ *
159
+ * Capital of zero and a peak of zero are both reachable, and both turn an
160
+ * honest division into a value JSON cannot carry: a report whose worst figure
161
+ * comes back `null` from a round trip is worse than one that says zero. A basis
162
+ * that is not positive has no ratio to state, so the figure beside it, which is
163
+ * money and is always true, is the one a reader is left with.
164
+ */
165
+ export function ratioOf(value: number, basis: number): number {
166
+ return basis > 0 ? value / basis : 0;
167
+ }
168
+
169
+ /**
170
+ * The curve, one point per report bar, in the order the bars arrived.
171
+ *
172
+ * Warmup bars are swept and not reported: their orders were real, so a trade
173
+ * opened during the warmup is already in the fold at the first point, with its
174
+ * charges already paid and its position already marked. A curve that began its
175
+ * fold at the first report bar would lose both, and would lose them silently.
176
+ *
177
+ * The running peak starts at the capital rather than at the first point, so a
178
+ * run that is down from its first bar is in drawdown at its first bar. Starting
179
+ * it at the first point would report every run as having begun at its high.
180
+ *
181
+ * The running trough starts at the capital for the same reason and not for a
182
+ * symmetrical one: a run that is up from its first bar has run up from the
183
+ * money it was given, which is the figure a reader is measuring against. Anchor
184
+ * it at the first point instead and a run that gapped up on bar zero reports
185
+ * that gain as having come from nowhere.
186
+ */
187
+ export function equityOver(
188
+ trades: readonly Trade[],
189
+ marks: readonly BarMark[],
190
+ contract: Contract,
191
+ capital: Money,
192
+ ): readonly EquityPoint[] {
193
+ const points: EquityPoint[] = [];
194
+ let held: Trade[] = [];
195
+ let next = 0;
196
+ let realised = 0;
197
+ let charges = 0;
198
+ let peak = capital;
199
+ let trough = capital;
200
+ let mark: number | null = null;
201
+
202
+ for (const bar of marks) {
203
+ // Opened by this bar, charges and all. The pointer is why the trades have
204
+ // to arrive in the order they opened.
205
+ while (next < trades.length) {
206
+ const opening = trades[next];
207
+ if (opening === undefined || opening.openedOnBar > bar.barIndex) break;
208
+ charges += opening.charges;
209
+ held.push(opening);
210
+ next += 1;
211
+ }
212
+
213
+ // Closed by this bar, gross and all. A trade that opened and closed inside
214
+ // one bar is taken on and given up here in that order, so its cost and its
215
+ // gross are both in this point and its position is in none.
216
+ let closedHere = false;
217
+ for (const trade of held) {
218
+ if (closedBy(trade, bar.barIndex)) {
219
+ realised += trade.grossProfit;
220
+ closedHere = true;
221
+ }
222
+ }
223
+ if (closedHere) held = held.filter((trade) => !closedBy(trade, bar.barIndex));
224
+
225
+ // A close the host did not have leaves the previous mark standing. It is
226
+ // carried across the warmup boundary too, so the first report bar of a run
227
+ // whose close is absent is marked at the last price there was.
228
+ if (bar.close !== null) mark = bar.close;
229
+ if (!bar.inReport) continue;
230
+
231
+ let openProfit = 0;
232
+ let exposure = 0;
233
+ for (const trade of held) {
234
+ // Before the first close there has ever been, a trade is marked at its
235
+ // own entry: no profit, and the position still visible in the exposure.
236
+ const at = mark ?? trade.entryPrice;
237
+ const direction = trade.side === 'long' ? 1 : -1;
238
+ openProfit += direction * (at - trade.entryPrice) * trade.units * contract.pointValue;
239
+ exposure += Math.abs(trade.units * at * contract.pointValue);
240
+ }
241
+
242
+ const cash = capital + realised - charges;
243
+ const equity = cash + openProfit;
244
+ if (equity > peak) peak = equity;
245
+ if (equity < trough) trough = equity;
246
+ const drawdown = equity - peak;
247
+ const runUp = equity - trough;
248
+ points.push({
249
+ barIndex: bar.barIndex,
250
+ time: bar.time,
251
+ realised,
252
+ charges,
253
+ openProfit,
254
+ cash,
255
+ equity,
256
+ exposure,
257
+ drawdown,
258
+ drawdownPercent: ratioOf(drawdown, peak),
259
+ runUp,
260
+ runUpPercent: ratioOf(runUp, trough),
261
+ });
262
+ }
263
+
264
+ return points;
265
+ }
266
+
267
+ /**
268
+ * How many of these bars ended with something held.
269
+ *
270
+ * Swept rather than searched: a sorted list of the bars trades opened on, a
271
+ * sorted list of the bars they closed on, and the running difference between
272
+ * how many of each have gone by. That is the boundary rule `openOnBar` states,
273
+ * arrived at from the other side, and the two are asserted to agree over a
274
+ * generated corpus rather than trusted to. A count that disagrees with the
275
+ * curve beside it about which bars were in the market is exactly the kind of
276
+ * defect a reader finds by adding two of the printed figures up.
277
+ */
278
+ export function barsInMarketOver(
279
+ trades: readonly Trade[],
280
+ equity: readonly EquityPoint[],
281
+ ): number {
282
+ const opens = trades.map((trade) => trade.openedOnBar).sort(ascending);
283
+ const closes = trades
284
+ .filter((trade) => trade.closedOnBar !== null)
285
+ .map((trade) => trade.closedOnBar ?? 0)
286
+ .sort(ascending);
287
+
288
+ let opened = 0;
289
+ let closed = 0;
290
+ let live = 0;
291
+ let bars = 0;
292
+ for (const point of equity) {
293
+ while (opened < opens.length && (opens[opened] ?? 0) <= point.barIndex) {
294
+ live += 1;
295
+ opened += 1;
296
+ }
297
+ while (closed < closes.length && (closes[closed] ?? 0) <= point.barIndex) {
298
+ live -= 1;
299
+ closed += 1;
300
+ }
301
+ if (live > 0) bars += 1;
302
+ }
303
+ return bars;
304
+ }
305
+
306
+ /** Whether this bar is the bar the trade closed on, or one after it. */
307
+ function closedBy(trade: Trade, barIndex: number): boolean {
308
+ return trade.closedOnBar !== null && trade.closedOnBar <= barIndex;
309
+ }
310
+
311
+ /** Smallest first, said once, because a sort without a comparison sorts text. */
312
+ function ascending(left: number, right: number): number {
313
+ return left - right;
314
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The money: what a run made, what it cost, and what that is worth knowing.
3
+ *
4
+ * **This module imports no engine and no emitter, and it never will.** It is
5
+ * arithmetic over portable data: a list of settled fills, a list of bar closes,
6
+ * a charge schedule and the contract the run was carried out under. Two things
7
+ * follow from that and both of them are the reason for it.
8
+ *
9
+ * A stored record can be reported again with no engine present, which is what
10
+ * makes a run record a conformance case rather than a souvenir. And the engine
11
+ * can call this, when the day comes that a script may read its own equity,
12
+ * without a cycle and without a second implementation of any of these formulas
13
+ * sitting inside the execution path disagreeing with this one.
14
+ *
15
+ * The door names what a caller may use. The fold itself, the cost basis, the
16
+ * round trip state machine and the statistics stay behind it: they are one
17
+ * algorithm with one caller.
18
+ *
19
+ * Behaviour lands beside these shapes, each call in the file its type is in.
20
+ * The cost model is here: `chargeFor` is what one fill came to,
21
+ * `scheduleFromDeclaration` is the declaration's own commission as the one kind
22
+ * of schedule this module evaluates, and `scheduleProblem` is what a schedule
23
+ * is refused for before the first bar. The round trips, the equity curve, the
24
+ * statistics and the report land beside their own shapes the same way.
25
+ *
26
+ * The shapes were agreed before any of it computed, because they are what the
27
+ * engine, the backtest driver and a second engine all have to agree about.
28
+ */
29
+ export { chargeFor, scheduleFromDeclaration, scheduleProblem } from './charges.js';
30
+ export type { BarMark, Contract, Money, RecordedFill } from './shapes.js';
31
+ export type {
32
+ ChargeBase,
33
+ ChargeBreakdown,
34
+ ChargeLine,
35
+ ChargeSchedule,
36
+ ChargeSide,
37
+ } from './charges.js';
38
+ export { tradesOf } from './trades.js';
39
+ export type { Trade } from './trades.js';
40
+ export { equityOver } from './equity.js';
41
+ export type { EquityPoint } from './equity.js';
42
+ export { monthlyOver } from './monthly.js';
43
+ export type { MonthlyReturn } from './monthly.js';
44
+ export { markersOf } from './markers.js';
45
+ export type { TradeMarker } from './markers.js';
46
+ export { analysisOf } from './analysis.js';
47
+ export type { SideAnalysis, TradeAnalysis } from './analysis.js';
48
+ export { summaryOf } from './statistics.js';
49
+ export type { Summary } from './statistics.js';
50
+ export { reportOf } from './report.js';
51
+ export type { Report } from './report.js';
@@ -0,0 +1,95 @@
1
+ /**
2
+ * One marker per entry and exit fill, which is the chart's whole input.
3
+ *
4
+ * A chart draws a trade as two marks and a line between them, and everything it
5
+ * needs to do that is in the fills: the bar, the trade the fill belongs to, the
6
+ * side, the units, the price and the tag the order carried. So the markers are
7
+ * produced here, by the module that already knows which fill belongs to which
8
+ * trade, and no chart is needed to produce them and none is imported to.
9
+ */
10
+ import type { RecordedFill } from './shapes.js';
11
+ import { closedBy, openedBy } from './trades.js';
12
+ import type { Trade } from './trades.js';
13
+
14
+ /** One entry or exit fill, addressed as a chart addresses it. */
15
+ export interface TradeMarker {
16
+ readonly barIndex: number;
17
+ readonly time: number | null;
18
+ readonly tradeIndex: number;
19
+ readonly kind: 'entry' | 'exit';
20
+ readonly side: 'buy' | 'sell';
21
+ readonly units: number;
22
+ readonly price: number;
23
+ readonly tag: string;
24
+ }
25
+
26
+ /**
27
+ * One marker per entry and exit fill, against the trades those fills made up.
28
+ *
29
+ * **The fill is read with the same rule the trade list read it with.**
30
+ * `closedBy` and `openedBy` are imported from `trades.ts` rather than restated,
31
+ * because a marker that called an exit an entry would draw the chart the report
32
+ * contradicts. A fill that carried a reference through zero is both: it closes
33
+ * the trade the reference held and opens the next one, so it produces two
34
+ * markers, in that order, which is the order the trade list folded it in.
35
+ *
36
+ * **A trade is found by its reference and by the order it opened**, never by
37
+ * the bar it opened on: a reference that went to zero and was entered again on
38
+ * the same bar is two trades, and addressing them by bar would put both
39
+ * markers on the first. The trades are walked once per reference, oldest first,
40
+ * which is the order `tradesOf` built them in.
41
+ *
42
+ * The count is an identity rather than a claim: the markers of one trade are
43
+ * its `entries` plus its `exits`, and a test asserts it over the same fills.
44
+ */
45
+ export function markersOf(
46
+ fills: readonly RecordedFill[],
47
+ trades: readonly Trade[],
48
+ ): readonly TradeMarker[] {
49
+ const ordered = fills.slice().sort((a, b) => a.seq - b.seq);
50
+ const queues = new Map<number, Trade[]>();
51
+ for (const trade of trades) {
52
+ const held = queues.get(trade.positionRef) ?? [];
53
+ held.push(trade);
54
+ queues.set(trade.positionRef, held);
55
+ }
56
+
57
+ const out: TradeMarker[] = [];
58
+ for (const fill of ordered) {
59
+ const queue = queues.get(fill.positionRef) ?? [];
60
+ const closing = closedBy(fill.refSizeBefore, fill.refSizeAfter);
61
+ const opening = openedBy(fill.refSizeBefore, fill.refSizeAfter);
62
+
63
+ if (closing > 0) {
64
+ const held = queue[0];
65
+ if (held !== undefined) out.push(markerFor(fill, held.index, 'exit', closing));
66
+ // The trade the reference held is finished by a fill that closed the
67
+ // whole of it, and the next trade on that reference is the one the fills
68
+ // after this belong to.
69
+ if (held !== undefined && (opening > 0 || fill.refSizeAfter === 0)) queue.shift();
70
+ }
71
+ if (opening > 0) {
72
+ const held = queue[0];
73
+ if (held !== undefined) out.push(markerFor(fill, held.index, 'entry', opening));
74
+ }
75
+ }
76
+ return out;
77
+ }
78
+
79
+ function markerFor(
80
+ fill: RecordedFill,
81
+ tradeIndex: number,
82
+ kind: 'entry' | 'exit',
83
+ units: number,
84
+ ): TradeMarker {
85
+ return {
86
+ barIndex: fill.barIndex,
87
+ time: fill.barTime,
88
+ tradeIndex,
89
+ kind,
90
+ side: fill.side,
91
+ units,
92
+ price: fill.price,
93
+ tag: fill.tag,
94
+ };
95
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * The month by month table, bucketed in UTC from bar times.
3
+ *
4
+ * **UTC, and it says so on the type.** A calendar month in the instrument's own
5
+ * timezone would need the session machinery and the instrument's calendar, and
6
+ * a table that silently buckets a trade into the wrong month is worse than one
7
+ * that states the basis it used. No timezone library, and no pretence of the
8
+ * instrument's calendar.
9
+ */
10
+ import type { EquityPoint } from './equity.js';
11
+ import type { Money } from './shapes.js';
12
+ import type { Trade } from './trades.js';
13
+
14
+ /** One calendar month of the run, in UTC. */
15
+ export interface MonthlyReturn {
16
+ readonly year: number;
17
+ /** 1 to 12, UTC. */
18
+ readonly month: number;
19
+ readonly netProfit: Money;
20
+ /** Against the equity at the month's first bar. */
21
+ readonly returnPercent: number;
22
+ readonly trades: number;
23
+ }
24
+
25
+ /**
26
+ * The months a run passed through, in the order it passed through them.
27
+ *
28
+ * **A month holds the change in equity across the closes inside it.** The first
29
+ * point of the curve has nothing before it to be compared with, so it opens the
30
+ * table rather than contributing to it, and every later point contributes what
31
+ * it moved from the point before. The consequence is an identity rather than a
32
+ * claim: the months add up to the equity at the last point less the equity at
33
+ * the first, and nothing that happened during the warmup is attributed to a
34
+ * month it did not happen in.
35
+ *
36
+ * `returnPercent` is that change over the equity at the month's first bar,
37
+ * which is the basis this table states and the reason the figure is a fraction
38
+ * rather than a fraction times a hundred: `drawdownPercent` beside it is
39
+ * `drawdown / peak`, and one report with two conventions is a report a reader
40
+ * has to check every figure of.
41
+ *
42
+ * `trades` counts the trades that closed inside the month, by the time they
43
+ * closed. An open trade is in no month, because the month it will be counted in
44
+ * is not decided yet.
45
+ *
46
+ * **A point with no time is in no month.** A bucket is a calendar fact and a
47
+ * bar with no time states no calendar, so such a point carries its change into
48
+ * the month in force rather than opening one, and where none is in force it is
49
+ * outside the table altogether. That is the honest reading: bucketing it by the
50
+ * month that happened to come before would put money in a month on the strength
51
+ * of nothing.
52
+ */
53
+ export function monthlyOver(
54
+ equity: readonly EquityPoint[],
55
+ trades: readonly Trade[],
56
+ ): readonly MonthlyReturn[] {
57
+ const buckets: Bucket[] = [];
58
+ let previous: EquityPoint | undefined;
59
+ let current: Bucket | undefined;
60
+
61
+ for (const point of equity) {
62
+ const at = monthOf(point.time);
63
+ if (at !== null && (current === undefined || current.year !== at.year || current.month !== at.month)) {
64
+ // The equity this month started from, which is where the previous month
65
+ // left the account and not where this one's first bar closed. Taking the
66
+ // opening point's own equity put the month's first move into the
67
+ // numerator and into the denominator at once: a February that doubled a
68
+ // thousand pounds reported fifty percent, because the gain was divided by
69
+ // the two thousand it had already produced.
70
+ current = {
71
+ year: at.year,
72
+ month: at.month,
73
+ netProfit: 0,
74
+ basis: previous === undefined ? point.equity : previous.equity,
75
+ trades: 0,
76
+ };
77
+ buckets.push(current);
78
+ }
79
+ if (previous !== undefined && current !== undefined) {
80
+ current.netProfit += point.equity - previous.equity;
81
+ }
82
+ previous = point;
83
+ }
84
+
85
+ for (const trade of trades) {
86
+ const at = monthOf(trade.closedAt);
87
+ if (at === null) continue;
88
+ const bucket = buckets.find((one) => one.year === at.year && one.month === at.month);
89
+ if (bucket !== undefined) bucket.trades += 1;
90
+ }
91
+
92
+ return buckets.map((one) => ({
93
+ year: one.year,
94
+ month: one.month,
95
+ netProfit: one.netProfit,
96
+ returnPercent: one.basis > 0 ? one.netProfit / one.basis : 0,
97
+ trades: one.trades,
98
+ }));
99
+ }
100
+
101
+ /** One month while it is being filled. */
102
+ interface Bucket {
103
+ readonly year: number;
104
+ readonly month: number;
105
+ netProfit: Money;
106
+ /** The equity at the month's first bar, which the return is measured against. */
107
+ readonly basis: Money;
108
+ trades: number;
109
+ }
110
+
111
+ /**
112
+ * The calendar month an instant falls in, in UTC and in no other zone.
113
+ *
114
+ * Decomposed rather than formatted, so no locale, no zone table and no library
115
+ * is involved: the same instant produces the same month on every machine this
116
+ * ever runs on.
117
+ */
118
+ function monthOf(time: number | null): { readonly year: number; readonly month: number } | null {
119
+ if (time === null || !Number.isFinite(time) || Math.abs(time) > LATEST_INSTANT) return null;
120
+ const at = new Date(time);
121
+ return { year: at.getUTCFullYear(), month: at.getUTCMonth() + 1 };
122
+ }
123
+
124
+ /**
125
+ * The furthest either way an instant can be and still name a month.
126
+ *
127
+ * A finite number outside it decomposes to NaN rather than throwing, and a NaN
128
+ * year never equals the next one, so every point opened a bucket of its own and
129
+ * a fifty thousand bar run produced fifty thousand rows of NaN. The canonical
130
+ * writer then threw a bare error with no code on them, out of core, on a path a
131
+ * host could not tell from an internal fault. The mistake that gets here is
132
+ * ordinary: bar times supplied in nanoseconds rather than milliseconds.
133
+ *
134
+ * A point whose time names no month contributes to no month, which is what an
135
+ * absent time already did.
136
+ */
137
+ const LATEST_INSTANT = 8.64e15;