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,94 @@
1
+ /**
2
+ * The report: what a run came to, folded from its fills and its bar closes.
3
+ *
4
+ * **The evaluation order is fixed here and nowhere else**, because a report
5
+ * that is right to eleven digits and different in the twelfth fails a
6
+ * conformance comparison months later on somebody else's engine, and the cause
7
+ * is always a line nobody thought was arithmetic. Fills in `seq` order, charge
8
+ * lines in declaration order, one rounding per fill total, no collection
9
+ * re-summed in another order, no dependence on a map's iteration order, no wall
10
+ * clock and no random number generator anywhere in this module.
11
+ *
12
+ * **What the report is not is read by a script.** The money entries of the
13
+ * `pos` namespace stay planned and go on refusing at the call. The moment a
14
+ * script can read its own equity mid-run, the money layer joins the execution
15
+ * path, and therefore joins the conformance surface, and every formula in this
16
+ * module has to be agreed by a second engine before a script may branch on it.
17
+ * This module computes the money after the fact from a record; whether a script
18
+ * may read it is a later decision. That this module imports no engine is the
19
+ * structural half of keeping that decision open: when it is taken, the engine
20
+ * calls this, and there is no second implementation to disagree with.
21
+ */
22
+ import { analysisOf } from './analysis.js';
23
+ import type { TradeAnalysis } from './analysis.js';
24
+ import { chargeFor } from './charges.js';
25
+ import type { ChargeSchedule } from './charges.js';
26
+ import { equityOver } from './equity.js';
27
+ import type { EquityPoint } from './equity.js';
28
+ import { monthlyOver } from './monthly.js';
29
+ import type { MonthlyReturn } from './monthly.js';
30
+ import { markersOf } from './markers.js';
31
+ import type { TradeMarker } from './markers.js';
32
+ import type { BarMark, Contract, Money, RecordedFill } from './shapes.js';
33
+ import { summaryOf } from './statistics.js';
34
+ import type { Summary } from './statistics.js';
35
+ import { tradesOf } from './trades.js';
36
+ import type { Trade } from './trades.js';
37
+
38
+ /** Everything a run is reported as, and nothing a chart has to compute. */
39
+ export interface Report {
40
+ readonly summary: Summary;
41
+ /** The trades by direction, by extreme and by run. */
42
+ readonly analysis: TradeAnalysis;
43
+ readonly trades: readonly Trade[];
44
+ readonly equity: readonly EquityPoint[];
45
+ readonly monthly: readonly MonthlyReturn[];
46
+ readonly markers: readonly TradeMarker[];
47
+ }
48
+
49
+ /**
50
+ * The whole report, folded from the fills, the bar closes and the schedule.
51
+ *
52
+ * **One pass, in one order, and the order is the result.** The fills are put in
53
+ * `seq` order once, here, and every fold below reads that same list: the
54
+ * charges are computed in it, the trades are built from it, the curve is marked
55
+ * along it and the markers come off it. A second ordering anywhere would be a
56
+ * report that is right to eleven digits and different in the twelfth on
57
+ * somebody else's engine, which is the failure this module is shaped to make
58
+ * impossible rather than unlikely.
59
+ *
60
+ * **A charge belongs to the fill that incurred it.** `chargeFor` rounds one
61
+ * fill's total once, and nothing here rounds it again or re-sums the collection
62
+ * in another order, so `sum(trade.charges)` and `summary.charges` are the same
63
+ * money and a test asserts it rather than a page claiming it.
64
+ *
65
+ * A run carried out under no schedule at all is charged nothing, which is a
66
+ * study of the strategy before costs and is a thing worth being able to ask
67
+ * for. It is not a default: `scheduleFromDeclaration` is what a run under the
68
+ * declaration's own commission uses, and the caller states which it wants.
69
+ */
70
+ export function reportOf(
71
+ fills: readonly RecordedFill[],
72
+ marks: readonly BarMark[],
73
+ schedule: ChargeSchedule | null,
74
+ contract: Contract,
75
+ capital: Money,
76
+ ): Report {
77
+ const ordered = fills.slice().sort((a, b) => a.seq - b.seq);
78
+ const charges = ordered.map((fill) =>
79
+ schedule === null ? 0 : chargeFor(schedule, fill, contract).total,
80
+ );
81
+
82
+ const trades = tradesOf(ordered, charges, marks, contract);
83
+ const equity = equityOver(trades, marks, contract, capital);
84
+ const summary = summaryOf(trades, equity, contract, capital);
85
+
86
+ return {
87
+ summary,
88
+ analysis: analysisOf(trades),
89
+ trades,
90
+ equity,
91
+ monthly: monthlyOver(equity, trades),
92
+ markers: markersOf(ordered, trades),
93
+ };
94
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The atoms the money is folded from: a fill, a contract and a bar's close.
3
+ *
4
+ * **Everything in this module is portable data.** No class, no function, no
5
+ * object reference, no absent field, no map and no date: a shape here is what
6
+ * `JSON.parse` gives back, so a report can be computed here, stored by a
7
+ * platform, sent to another process and recomputed there without this
8
+ * implementation being present. That is not a convenience. A run record is the
9
+ * conformance case a second engine is handed, and a case that can only be read
10
+ * by the engine that wrote it proves nothing about either.
11
+ *
12
+ * **A fill is the only thing money is folded from.** Not a position, not a
13
+ * ledger row, not a running total the engine happened to be holding: the fills
14
+ * the engine settled, in the order it settled them, each naming the position
15
+ * reference it moved and the size of that reference either side of the
16
+ * settlement. Every figure in a report is a function of that list and of the
17
+ * bars it is marked against, which is what makes a report reproducible from a
18
+ * record with no engine in the room.
19
+ */
20
+
21
+ /**
22
+ * Money, in the contract's own currency.
23
+ *
24
+ * A number rather than a type of its own, because a type of its own would be a
25
+ * class and a class does not survive `JSON.parse`. The rounding is stated once,
26
+ * on the contract, and applied once per fill total.
27
+ */
28
+ export type Money = number;
29
+
30
+ /**
31
+ * The instrument facts a run was carried out under, as the host stated them.
32
+ *
33
+ * A snapshot rather than a reference. An instrument's lot size and tick size
34
+ * change, and a report recomputed months later under today's facts would be a
35
+ * different study wearing the same name, so the facts travel with the run.
36
+ */
37
+ export interface Contract {
38
+ readonly symbol: string | null;
39
+ readonly exchange: string | null;
40
+ readonly currency: string;
41
+ readonly tickSize: number | null;
42
+ readonly lotSize: number | null;
43
+ /** Money per 1.0 of price per unit; 1 when the host states none. */
44
+ readonly pointValue: number;
45
+ /** Money rounding digits, half to even, once per fill total. */
46
+ readonly digits: number;
47
+ }
48
+
49
+ /** One settled fill. Every money figure is folded from these and nothing else. */
50
+ export interface RecordedFill {
51
+ readonly seq: number;
52
+ readonly intentId: number;
53
+ readonly orderRef: string;
54
+ readonly tag: string;
55
+ readonly positionRef: number;
56
+ readonly side: 'buy' | 'sell';
57
+ /** Positive, this fill's own quantity. */
58
+ readonly units: number;
59
+ readonly price: number;
60
+ readonly barIndex: number;
61
+ readonly barTime: number | null;
62
+ readonly refSizeBefore: number;
63
+ readonly refSizeAfter: number;
64
+ }
65
+
66
+ /** One bar as the report marks against it. Close only: 17.4 marks to the close. */
67
+ export interface BarMark {
68
+ readonly barIndex: number;
69
+ readonly time: number | null;
70
+ readonly close: number | null;
71
+ /** False for a warmup bar. */
72
+ readonly inReport: boolean;
73
+ }
@@ -0,0 +1,387 @@
1
+ /**
2
+ * The summary, and the two figures in it that decide whether a run means
3
+ * anything.
4
+ *
5
+ * **Win rate is over closed trades, on net profit after charges**, and a trade
6
+ * whose net is exactly zero is a scratch counted in neither half. It is null
7
+ * where nothing closed rather than zero, because zero is a number a reader
8
+ * compares against and "nothing has closed yet" is not a losing run.
9
+ *
10
+ * **Expectancy is money per closed trade**, and it has two spellings that must
11
+ * agree: the win rate against the average win and the average loss, and the net
12
+ * profit over the trade count. Two spellings of one figure that disagree is how
13
+ * a report loses its reader, so one of them is the computation and the other is
14
+ * a test, and the test says which sequence of roundings it allows for.
15
+ *
16
+ * **And its standard error is what answers the question a comparison asks.**
17
+ * The sample standard deviation of per-trade net over the square root of the
18
+ * trade count is what turns "this run made more" into "this run made more than
19
+ * the noise", and without it a difference of two percent over eleven trades
20
+ * reads like a result.
21
+ *
22
+ * Bar times rather than bar indices, wherever a figure addresses a bar: loading
23
+ * more history shifts every index, so a report that addresses a bar by index
24
+ * changes when the warmup changes.
25
+ *
26
+ * ## Which trades a figure is counted over, which is three different answers
27
+ *
28
+ * A summary folds a list holding closed trades and open ones together, and
29
+ * almost every defect this file can have is a figure counted over the wrong
30
+ * half of it.
31
+ *
32
+ * - **Net profit, and everything derived from it**, is over closed trades. An
33
+ * open trade's net is its charges so far with no gross against them, so
34
+ * counting it would report a run holding a winner as having lost money.
35
+ * - **Charges are over every trade**, open ones included, because the money
36
+ * left the account whether or not the position came back. This is the figure
37
+ * the equity curve's last point carries, and the two are asserted equal.
38
+ * - **The drawdown figures are over the curve** and not over the trades at all.
39
+ * A drawdown is a thing equity did between two trades as often as during one.
40
+ *
41
+ * ## The cases where a statistic is not a number
42
+ *
43
+ * Every one of them is a division, and every one of them is answered here
44
+ * rather than left to arrive as a value JSON turns into `null`:
45
+ *
46
+ * - **Nothing closed.** The win rate is null, which is a different claim from
47
+ * zero. Expectancy and its error are zero, because the type is money and
48
+ * money is not nullable, and `tradeCount` beside them is the field that says
49
+ * whether they mean anything.
50
+ * - **One closed trade.** A sample of one has no spread, so the standard error
51
+ * is zero. Zero here means not measurable, never measured: anything dividing
52
+ * by it checks the trade count first.
53
+ * - **Every closed trade a scratch.** There is no denominator for a win rate,
54
+ * so it is null for the same reason as nothing closed.
55
+ * - **Nothing lost.** The profit factor is null rather than an infinity, which
56
+ * is the same decision `spec/conformance.md` takes about absence: a value
57
+ * that does not survive being written down is not a value a report may hold.
58
+ * - **Everything lost.** The profit factor is zero, expectancy is negative, the
59
+ * average win is zero, and none of the four is a division by zero.
60
+ */
61
+ import { barsInMarketOver, ratioOf } from './equity.js';
62
+ import type { EquityPoint } from './equity.js';
63
+ import type { Contract, Money } from './shapes.js';
64
+ import type { Trade } from './trades.js';
65
+
66
+ /** What the whole run came to. */
67
+ export interface Summary {
68
+ readonly capital: Money;
69
+ readonly currency: string;
70
+ readonly netProfit: Money;
71
+ /** Sum of winning trades, before charges. */
72
+ readonly grossProfit: Money;
73
+ /**
74
+ * The losing trades' gross, as a magnitude, and it can come out at or below
75
+ * zero.
76
+ *
77
+ * A trade wins or loses on its net after charges and contributes its gross
78
+ * here, so a trade whose gross was positive and whose charges took it under
79
+ * lands in the losses carrying a positive gross, which lowers this figure and
80
+ * with few enough trades beside it takes it to zero or past it. `tallyOf`
81
+ * says why that is preferred to counting one trade as a loser in one figure
82
+ * and a winner in another. It is written here because it was documented as a
83
+ * positive magnitude and is not one, and a reader dividing by it was handed a
84
+ * negative profit factor with nothing saying that could happen.
85
+ */
86
+ readonly grossLoss: Money;
87
+ readonly charges: Money;
88
+ /** `netProfit / capital`, a fraction and not a figure times a hundred. */
89
+ readonly returnPercent: number;
90
+ /** Closed only. */
91
+ readonly tradeCount: number;
92
+ readonly openTradeCount: number;
93
+ readonly wins: number;
94
+ readonly losses: number;
95
+ /** Exactly zero net. */
96
+ readonly scratches: number;
97
+ /** wins / (wins + losses), null when none closed. */
98
+ readonly winRate: number | null;
99
+ readonly averageWin: Money;
100
+ /** Positive magnitude. */
101
+ readonly averageLoss: Money;
102
+ /** Money per closed trade. */
103
+ readonly expectancy: Money;
104
+ readonly expectancyStandardError: Money;
105
+ /**
106
+ * Gross profit over gross loss, or null where there is no ratio to take.
107
+ *
108
+ * Null when the gross loss is not above zero: a run with no losing trade has
109
+ * nothing to divide by, and one whose losses cost less in gross than their
110
+ * charges has a denominator at or below zero. A profit factor is a
111
+ * non-negative ratio everywhere it is used, so a negative one is not a
112
+ * surprising value, it is a number nobody can act on. It used to be
113
+ * reported: two trades, one charged into a loss on a positive gross, gave a
114
+ * profit factor of -0.5.
115
+ */
116
+ readonly profitFactor: number | null;
117
+ /** Zero or negative, the same sign the curve states it with. */
118
+ readonly maxDrawdown: Money;
119
+ /** The deepest point's own fraction, not the worst fraction of any point. */
120
+ readonly maxDrawdownPercent: number;
121
+ /** A bar time, never an index. */
122
+ readonly maxDrawdownAt: number | null;
123
+ readonly longestDrawdownBars: number;
124
+ /**
125
+ * Zero or positive, the mirror of `maxDrawdown` and read against it.
126
+ *
127
+ * The pair is the point of it. A run that made ten and gave back nine is a
128
+ * different strategy from one that made one and never gave any of it back,
129
+ * and the two report the same net. Neither figure says that on its own.
130
+ */
131
+ readonly maxRunUp: Money;
132
+ /** The highest point's own fraction, not the best fraction of any point. */
133
+ readonly maxRunUpPercent: number;
134
+ /** A bar time, never an index, and not the bar `maxDrawdownAt` addresses. */
135
+ readonly maxRunUpAt: number | null;
136
+ readonly averageBarsHeld: number | null;
137
+ readonly barsInMarket: number;
138
+ readonly barCount: number;
139
+ }
140
+
141
+ /** The closed trades split three ways, and the sums each half contributes. */
142
+ interface Tally {
143
+ readonly netProfit: Money;
144
+ readonly grossProfit: Money;
145
+ readonly grossLoss: Money;
146
+ readonly charges: Money;
147
+ readonly tradeCount: number;
148
+ readonly openTradeCount: number;
149
+ readonly wins: number;
150
+ readonly losses: number;
151
+ readonly scratches: number;
152
+ readonly winTotal: Money;
153
+ readonly lossTotal: Money;
154
+ readonly heldTotal: number;
155
+ readonly heldCount: number;
156
+ }
157
+
158
+ /** The deepest point of the curve, and how long the run stayed under water. */
159
+ interface Depth {
160
+ readonly maxDrawdown: Money;
161
+ readonly maxDrawdownPercent: number;
162
+ readonly maxDrawdownAt: number | null;
163
+ readonly longestDrawdownBars: number;
164
+ readonly maxRunUp: Money;
165
+ readonly maxRunUpPercent: number;
166
+ readonly maxRunUpAt: number | null;
167
+ }
168
+
169
+ /**
170
+ * The whole run in one shape, folded from its trades and its own curve.
171
+ *
172
+ * The curve is passed in rather than recomputed, because a summary that folded
173
+ * its own would be a second equity curve with a second set of rounding, and the
174
+ * first disagreement between them would be a drawdown figure that no point in
175
+ * the reported curve ever reached.
176
+ */
177
+ export function summaryOf(
178
+ trades: readonly Trade[],
179
+ equity: readonly EquityPoint[],
180
+ contract: Contract,
181
+ capital: Money,
182
+ ): Summary {
183
+ const tally = tallyOf(trades);
184
+ const depth = depthOf(equity);
185
+ const decided = tally.wins + tally.losses;
186
+
187
+ // Net over the closed count, and nothing else, because this is the figure the
188
+ // other spelling is checked against. The win rate spelling divides by the
189
+ // decided trades instead, so the two are the same number exactly when no
190
+ // trade scratched, and `tests/accounting/statistics.test.ts` asserts both the
191
+ // agreement and the one case that parts them.
192
+ const expectancy = tally.tradeCount === 0 ? 0 : tally.netProfit / tally.tradeCount;
193
+
194
+ return {
195
+ capital,
196
+ currency: contract.currency,
197
+ netProfit: tally.netProfit,
198
+ grossProfit: tally.grossProfit,
199
+ grossLoss: tally.grossLoss,
200
+ charges: tally.charges,
201
+ returnPercent: ratioOf(tally.netProfit, capital),
202
+ tradeCount: tally.tradeCount,
203
+ openTradeCount: tally.openTradeCount,
204
+ wins: tally.wins,
205
+ losses: tally.losses,
206
+ scratches: tally.scratches,
207
+ winRate: decided === 0 ? null : tally.wins / decided,
208
+ averageWin: tally.wins === 0 ? 0 : tally.winTotal / tally.wins,
209
+ averageLoss: tally.losses === 0 ? 0 : -tally.lossTotal / tally.losses,
210
+ expectancy,
211
+ expectancyStandardError: standardErrorOf(trades, expectancy, tally.tradeCount),
212
+ profitFactor: tally.grossLoss > 0 ? tally.grossProfit / tally.grossLoss : null,
213
+ maxDrawdown: depth.maxDrawdown,
214
+ maxDrawdownPercent: depth.maxDrawdownPercent,
215
+ maxDrawdownAt: depth.maxDrawdownAt,
216
+ longestDrawdownBars: depth.longestDrawdownBars,
217
+ maxRunUp: depth.maxRunUp,
218
+ maxRunUpPercent: depth.maxRunUpPercent,
219
+ maxRunUpAt: depth.maxRunUpAt,
220
+ averageBarsHeld: tally.heldCount === 0 ? null : tally.heldTotal / tally.heldCount,
221
+ barsInMarket: barsInMarketOver(trades, equity),
222
+ barCount: equity.length,
223
+ };
224
+ }
225
+
226
+ /**
227
+ * One pass over the trades, in the order they are given.
228
+ *
229
+ * A trade wins or loses on its net after charges, and its gross is what it
230
+ * contributes to the gross figures: "the sum of the winning trades, before
231
+ * charges" is two statements and this is where they meet. The one arrangement
232
+ * that reads oddly is a trade whose gross was positive and whose charges took
233
+ * it under, which lands in the losses and takes its positive gross with it,
234
+ * lowering the gross loss. That is on purpose. The alternative is a trade
235
+ * counted as a loser in one figure and a winner in another, and a profit factor
236
+ * whose two halves are counted over different sets is worse than one whose
237
+ * magnitude is odd on a trade that barely moved.
238
+ *
239
+ * What is not on purpose, and is why `profitFactor` is null rather than a ratio
240
+ * whenever this figure is not above zero: enough of those trades and the gross
241
+ * loss reaches zero or goes under, and dividing by it reported a profit factor
242
+ * of minus a half. A statistic being odd is a thing a reader can weigh. A
243
+ * statistic being negative where every use of it is a non-negative ratio is a
244
+ * number nobody can act on.
245
+ */
246
+ function tallyOf(trades: readonly Trade[]): Tally {
247
+ let netProfit = 0;
248
+ let grossProfit = 0;
249
+ let grossLoss = 0;
250
+ let charges = 0;
251
+ let tradeCount = 0;
252
+ let openTradeCount = 0;
253
+ let wins = 0;
254
+ let losses = 0;
255
+ let scratches = 0;
256
+ let winTotal = 0;
257
+ let lossTotal = 0;
258
+ let heldTotal = 0;
259
+ let heldCount = 0;
260
+
261
+ for (const trade of trades) {
262
+ charges += trade.charges;
263
+ if (trade.isOpen) {
264
+ openTradeCount += 1;
265
+ continue;
266
+ }
267
+ tradeCount += 1;
268
+ netProfit += trade.netProfit;
269
+ if (trade.barsHeld !== null) {
270
+ heldTotal += trade.barsHeld;
271
+ heldCount += 1;
272
+ }
273
+ if (trade.netProfit > 0) {
274
+ wins += 1;
275
+ winTotal += trade.netProfit;
276
+ grossProfit += trade.grossProfit;
277
+ } else if (trade.netProfit < 0) {
278
+ losses += 1;
279
+ lossTotal += trade.netProfit;
280
+ grossLoss -= trade.grossProfit;
281
+ } else {
282
+ scratches += 1;
283
+ }
284
+ }
285
+
286
+ return {
287
+ netProfit,
288
+ grossProfit,
289
+ grossLoss,
290
+ charges,
291
+ tradeCount,
292
+ openTradeCount,
293
+ wins,
294
+ losses,
295
+ scratches,
296
+ winTotal,
297
+ lossTotal,
298
+ heldTotal,
299
+ heldCount,
300
+ };
301
+ }
302
+
303
+ /**
304
+ * The deepest the curve went, named as one point rather than as three figures.
305
+ *
306
+ * The money, the fraction and the time all come from the same point, and the
307
+ * point is the deepest in money with the earliest one winning a tie. Taking the
308
+ * worst fraction from one bar and the worst money from another would describe a
309
+ * moment the run never had, and a reader comparing the two figures would find
310
+ * them inconsistent with every point in the curve they were drawn from.
311
+ *
312
+ * `longestDrawdownBars` is the longest run of consecutive bars under a peak: it
313
+ * starts at the first bar below one and ends at the bar before the recovery, so
314
+ * a run still under water at the last bar counts to the end. It is often the
315
+ * figure that actually stops a trader, and it is not the total number of bars
316
+ * spent under water, which is a different and much larger number.
317
+ */
318
+ function depthOf(equity: readonly EquityPoint[]): Depth {
319
+ let maxDrawdown = 0;
320
+ let maxDrawdownPercent = 0;
321
+ let maxDrawdownAt: number | null = null;
322
+ let longestDrawdownBars = 0;
323
+ let under = 0;
324
+ let maxRunUp = 0;
325
+ let maxRunUpPercent = 0;
326
+ let maxRunUpAt: number | null = null;
327
+
328
+ for (const point of equity) {
329
+ if (point.drawdown < maxDrawdown) {
330
+ maxDrawdown = point.drawdown;
331
+ maxDrawdownPercent = point.drawdownPercent;
332
+ maxDrawdownAt = point.time;
333
+ }
334
+ if (point.drawdown < 0) {
335
+ under += 1;
336
+ if (under > longestDrawdownBars) longestDrawdownBars = under;
337
+ } else {
338
+ under = 0;
339
+ }
340
+ // Strictly greater, so the earliest bar reaching the height wins the tie,
341
+ // which is the rule the depth above is picked by. The two figures address
342
+ // different bars and each addresses the first bar that reached it.
343
+ if (point.runUp > maxRunUp) {
344
+ maxRunUp = point.runUp;
345
+ maxRunUpPercent = point.runUpPercent;
346
+ maxRunUpAt = point.time;
347
+ }
348
+ }
349
+
350
+ return {
351
+ maxDrawdown,
352
+ maxDrawdownPercent,
353
+ maxDrawdownAt,
354
+ longestDrawdownBars,
355
+ maxRunUp,
356
+ maxRunUpPercent,
357
+ maxRunUpAt,
358
+ };
359
+ }
360
+
361
+ /**
362
+ * The standard error of the expectancy: the sample deviation over the root of
363
+ * the count.
364
+ *
365
+ * The sample deviation, with the count less one under it, and not the
366
+ * population one. The trades a run took are a sample of the trades the strategy
367
+ * would take, which is the whole reason this figure is here, and the population
368
+ * spelling understates the spread by exactly the amount that matters on the
369
+ * short runs where the question is asked.
370
+ *
371
+ * Fewer than two closed trades has no spread to measure and gives zero.
372
+ */
373
+ function standardErrorOf(
374
+ trades: readonly Trade[],
375
+ expectancy: Money,
376
+ tradeCount: number,
377
+ ): Money {
378
+ if (tradeCount < 2) return 0;
379
+
380
+ let squares = 0;
381
+ for (const trade of trades) {
382
+ if (trade.isOpen) continue;
383
+ const away = trade.netProfit - expectancy;
384
+ squares += away * away;
385
+ }
386
+ return Math.sqrt(squares / (tradeCount - 1) / tradeCount);
387
+ }