openalgo-script 0.2.0 → 0.4.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.
- package/CHANGELOG.md +269 -0
- package/README.md +65 -31
- package/dist/adapters/charts/surfaces.d.ts +17 -0
- package/dist/adapters/charts/surfaces.d.ts.map +1 -1
- package/dist/adapters/charts/tables.js +82 -0
- package/dist/adapters/charts/tables.js.map +1 -1
- package/dist/adapters/codemirror/commands.d.ts +14 -0
- package/dist/adapters/codemirror/commands.d.ts.map +1 -0
- package/dist/adapters/codemirror/commands.js +52 -0
- package/dist/adapters/codemirror/commands.js.map +1 -0
- package/dist/adapters/codemirror/completion.d.ts +15 -0
- package/dist/adapters/codemirror/completion.d.ts.map +1 -0
- package/dist/adapters/codemirror/completion.js +64 -0
- package/dist/adapters/codemirror/completion.js.map +1 -0
- package/dist/adapters/codemirror/contract.d.ts +156 -0
- package/dist/adapters/codemirror/contract.d.ts.map +1 -0
- package/dist/adapters/codemirror/contract.js +42 -0
- package/dist/adapters/codemirror/contract.js.map +1 -0
- package/dist/adapters/codemirror/index.d.ts +43 -0
- package/dist/adapters/codemirror/index.d.ts.map +1 -0
- package/dist/adapters/codemirror/index.js +8 -0
- package/dist/adapters/codemirror/index.js.map +1 -0
- package/dist/adapters/codemirror/lint.d.ts +17 -0
- package/dist/adapters/codemirror/lint.d.ts.map +1 -0
- package/dist/adapters/codemirror/lint.js +62 -0
- package/dist/adapters/codemirror/lint.js.map +1 -0
- package/dist/adapters/codemirror/positions.d.ts +16 -0
- package/dist/adapters/codemirror/positions.d.ts.map +1 -0
- package/dist/adapters/codemirror/positions.js +65 -0
- package/dist/adapters/codemirror/positions.js.map +1 -0
- package/dist/adapters/codemirror/stream.d.ts +23 -0
- package/dist/adapters/codemirror/stream.d.ts.map +1 -0
- package/dist/adapters/codemirror/stream.js +70 -0
- package/dist/adapters/codemirror/stream.js.map +1 -0
- package/dist/adapters/codemirror/tokens.d.ts +44 -0
- package/dist/adapters/codemirror/tokens.d.ts.map +1 -0
- package/dist/adapters/codemirror/tokens.js +34 -0
- package/dist/adapters/codemirror/tokens.js.map +1 -0
- package/dist/adapters/codemirror/tooltips.d.ts +35 -0
- package/dist/adapters/codemirror/tooltips.d.ts.map +1 -0
- package/dist/adapters/codemirror/tooltips.js +108 -0
- package/dist/adapters/codemirror/tooltips.js.map +1 -0
- package/dist/core/accounting/charges.d.ts +136 -0
- package/dist/core/accounting/charges.d.ts.map +1 -0
- package/dist/core/accounting/charges.js +362 -0
- package/dist/core/accounting/charges.js.map +1 -0
- package/dist/core/accounting/equity.d.ts +158 -0
- package/dist/core/accounting/equity.d.ts.map +1 -0
- package/dist/core/accounting/equity.js +155 -0
- package/dist/core/accounting/equity.js.map +1 -0
- package/dist/core/accounting/index.d.ts +44 -0
- package/dist/core/accounting/index.d.ts.map +1 -0
- package/dist/core/accounting/index.js +36 -0
- package/dist/core/accounting/index.js.map +1 -0
- package/dist/core/accounting/markers.d.ts +43 -0
- package/dist/core/accounting/markers.d.ts.map +1 -0
- package/dist/core/accounting/markers.js +64 -0
- package/dist/core/accounting/markers.js.map +1 -0
- package/dist/core/accounting/monthly.d.ts +52 -0
- package/dist/core/accounting/monthly.d.ts.map +1 -0
- package/dist/core/accounting/monthly.js +99 -0
- package/dist/core/accounting/monthly.js.map +1 -0
- package/dist/core/accounting/report.d.ts +38 -0
- package/dist/core/accounting/report.d.ts.map +1 -0
- package/dist/core/accounting/report.js +63 -0
- package/dist/core/accounting/report.js.map +1 -0
- package/dist/core/accounting/shapes.d.ts +70 -0
- package/dist/core/accounting/shapes.d.ts.map +1 -0
- package/dist/core/accounting/shapes.js +21 -0
- package/dist/core/accounting/shapes.js.map +1 -0
- package/dist/core/accounting/statistics.d.ts +75 -0
- package/dist/core/accounting/statistics.d.ts.map +1 -0
- package/dist/core/accounting/statistics.js +246 -0
- package/dist/core/accounting/statistics.js.map +1 -0
- package/dist/core/accounting/trades.d.ts +118 -0
- package/dist/core/accounting/trades.d.ts.map +1 -0
- package/dist/core/accounting/trades.js +186 -0
- package/dist/core/accounting/trades.js.map +1 -0
- package/dist/core/backtest/compare.d.ts +38 -0
- package/dist/core/backtest/compare.d.ts.map +1 -0
- package/dist/core/backtest/compare.js +189 -0
- package/dist/core/backtest/compare.js.map +1 -0
- package/dist/core/backtest/declaration.d.ts +52 -0
- package/dist/core/backtest/declaration.d.ts.map +1 -0
- package/dist/core/backtest/declaration.js +48 -0
- package/dist/core/backtest/declaration.js.map +1 -0
- package/dist/core/backtest/drive.d.ts +29 -0
- package/dist/core/backtest/drive.d.ts.map +1 -0
- package/dist/core/backtest/drive.js +262 -0
- package/dist/core/backtest/drive.js.map +1 -0
- package/dist/core/backtest/index.d.ts +46 -0
- package/dist/core/backtest/index.d.ts.map +1 -0
- package/dist/core/backtest/index.js +36 -0
- package/dist/core/backtest/index.js.map +1 -0
- package/dist/core/backtest/range.d.ts +84 -0
- package/dist/core/backtest/range.d.ts.map +1 -0
- package/dist/core/backtest/range.js +90 -0
- package/dist/core/backtest/range.js.map +1 -0
- package/dist/core/backtest/record.d.ts +238 -0
- package/dist/core/backtest/record.d.ts.map +1 -0
- package/dist/core/backtest/record.js +176 -0
- package/dist/core/backtest/record.js.map +1 -0
- package/dist/core/backtest/replay.d.ts +48 -0
- package/dist/core/backtest/replay.d.ts.map +1 -0
- package/dist/core/backtest/replay.js +127 -0
- package/dist/core/backtest/replay.js.map +1 -0
- package/dist/core/backtest/resting.d.ts +62 -0
- package/dist/core/backtest/resting.d.ts.map +1 -0
- package/dist/core/backtest/resting.js +59 -0
- package/dist/core/backtest/resting.js.map +1 -0
- package/dist/core/backtest/settings.d.ts +117 -0
- package/dist/core/backtest/settings.d.ts.map +1 -0
- package/dist/core/backtest/settings.js +207 -0
- package/dist/core/backtest/settings.js.map +1 -0
- package/dist/core/backtest/simulate.d.ts +146 -0
- package/dist/core/backtest/simulate.d.ts.map +1 -0
- package/dist/core/backtest/simulate.js +217 -0
- package/dist/core/backtest/simulate.js.map +1 -0
- package/dist/core/catalogue/catalogue.generated.d.ts +66 -0
- package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
- package/dist/core/catalogue/catalogue.generated.js +6 -0
- package/dist/core/catalogue/catalogue.generated.js.map +1 -1
- package/dist/core/catalogue/values.generated.d.ts +24 -0
- package/dist/core/catalogue/values.generated.d.ts.map +1 -1
- package/dist/core/check/index.d.ts +2 -1
- package/dist/core/check/index.d.ts.map +1 -1
- package/dist/core/check/index.js +1 -1
- package/dist/core/check/index.js.map +1 -1
- package/dist/core/check/library-prose.generated.d.ts +16 -0
- package/dist/core/check/library-prose.generated.d.ts.map +1 -0
- package/dist/core/check/library-prose.generated.js +353 -0
- package/dist/core/check/library-prose.generated.js.map +1 -0
- package/dist/core/check/surface.d.ts +24 -0
- package/dist/core/check/surface.d.ts.map +1 -1
- package/dist/core/check/surface.js +29 -0
- package/dist/core/check/surface.js.map +1 -1
- package/dist/core/emit/defaults.d.ts +35 -16
- package/dist/core/emit/defaults.d.ts.map +1 -1
- package/dist/core/emit/defaults.js +66 -0
- package/dist/core/emit/defaults.js.map +1 -1
- package/dist/core/emit/index.d.ts +2 -0
- package/dist/core/emit/index.d.ts.map +1 -1
- package/dist/core/emit/index.js +2 -0
- package/dist/core/emit/index.js.map +1 -1
- package/dist/core/engine/index.d.ts +1 -1
- package/dist/core/engine/index.d.ts.map +1 -1
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/index.js.map +1 -1
- package/dist/core/engine/load.d.ts +15 -1
- package/dist/core/engine/load.d.ts.map +1 -1
- package/dist/core/engine/load.js +1 -0
- package/dist/core/engine/load.js.map +1 -1
- package/dist/core/index.d.ts +37 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +17 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/version/version.generated.d.ts +1 -1
- package/dist/core/version/version.generated.js +1 -1
- package/dist/editor/complete.d.ts +40 -0
- package/dist/editor/complete.d.ts.map +1 -0
- package/dist/editor/complete.js +206 -0
- package/dist/editor/complete.js.map +1 -0
- package/dist/editor/diagnose.d.ts +18 -0
- package/dist/editor/diagnose.d.ts.map +1 -0
- package/dist/editor/diagnose.js +70 -0
- package/dist/editor/diagnose.js.map +1 -0
- package/dist/editor/format.d.ts +11 -0
- package/dist/editor/format.d.ts.map +1 -0
- package/dist/editor/format.js +50 -0
- package/dist/editor/format.js.map +1 -0
- package/dist/editor/highlight.d.ts +28 -0
- package/dist/editor/highlight.d.ts.map +1 -0
- package/dist/editor/highlight.js +65 -0
- package/dist/editor/highlight.js.map +1 -0
- package/dist/editor/hover.d.ts +40 -0
- package/dist/editor/hover.d.ts.map +1 -0
- package/dist/editor/hover.js +147 -0
- package/dist/editor/hover.js.map +1 -0
- package/dist/editor/index.d.ts +60 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/index.js +7 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/editor/kinds.d.ts +27 -0
- package/dist/editor/kinds.d.ts.map +1 -0
- package/dist/editor/kinds.js +118 -0
- package/dist/editor/kinds.js.map +1 -0
- package/dist/editor/layout.d.ts +47 -0
- package/dist/editor/layout.d.ts.map +1 -0
- package/dist/editor/layout.js +135 -0
- package/dist/editor/layout.js.map +1 -0
- package/dist/editor/manifest.d.ts +47 -0
- package/dist/editor/manifest.d.ts.map +1 -0
- package/dist/editor/manifest.js +93 -0
- package/dist/editor/manifest.js.map +1 -0
- package/dist/editor/reading.d.ts +41 -0
- package/dist/editor/reading.d.ts.map +1 -0
- package/dist/editor/reading.js +51 -0
- package/dist/editor/reading.js.map +1 -0
- package/dist/editor/scan.d.ts +26 -0
- package/dist/editor/scan.d.ts.map +1 -0
- package/dist/editor/scan.js +139 -0
- package/dist/editor/scan.js.map +1 -0
- package/dist/editor/scope.d.ts +19 -0
- package/dist/editor/scope.d.ts.map +1 -0
- package/dist/editor/scope.js +99 -0
- package/dist/editor/scope.js.map +1 -0
- package/dist/editor/signature.d.ts +38 -0
- package/dist/editor/signature.d.ts.map +1 -0
- package/dist/editor/signature.js +112 -0
- package/dist/editor/signature.js.map +1 -0
- package/dist/editor/site.d.ts +46 -0
- package/dist/editor/site.d.ts.map +1 -0
- package/dist/editor/site.js +184 -0
- package/dist/editor/site.js.map +1 -0
- package/dist/editor/spacing.d.ts +29 -0
- package/dist/editor/spacing.d.ts.map +1 -0
- package/dist/editor/spacing.js +116 -0
- package/dist/editor/spacing.js.map +1 -0
- package/package.json +30 -3
- package/spec/errors.json +140 -0
- package/src/adapters/charts/surfaces.ts +17 -0
- package/src/adapters/charts/tables.ts +88 -0
- package/src/adapters/codemirror/commands.ts +53 -0
- package/src/adapters/codemirror/completion.ts +74 -0
- package/src/adapters/codemirror/contract.ts +156 -0
- package/src/adapters/codemirror/index.ts +66 -0
- package/src/adapters/codemirror/lint.ts +66 -0
- package/src/adapters/codemirror/positions.ts +79 -0
- package/src/adapters/codemirror/stream.ts +87 -0
- package/src/adapters/codemirror/tokens.ts +64 -0
- package/src/adapters/codemirror/tooltips.ts +113 -0
- package/src/core/accounting/charges.ts +452 -0
- package/src/core/accounting/equity.ts +276 -0
- package/src/core/accounting/index.ts +49 -0
- package/src/core/accounting/markers.ts +95 -0
- package/src/core/accounting/monthly.ts +137 -0
- package/src/core/accounting/report.ts +89 -0
- package/src/core/accounting/shapes.ts +73 -0
- package/src/core/accounting/statistics.ts +350 -0
- package/src/core/accounting/trades.ts +313 -0
- package/src/core/backtest/compare.ts +244 -0
- package/src/core/backtest/declaration.ts +97 -0
- package/src/core/backtest/drive.ts +364 -0
- package/src/core/backtest/index.ts +52 -0
- package/src/core/backtest/range.ts +137 -0
- package/src/core/backtest/record.ts +341 -0
- package/src/core/backtest/replay.ts +158 -0
- package/src/core/backtest/resting.ts +125 -0
- package/src/core/backtest/settings.ts +280 -0
- package/src/core/backtest/simulate.ts +304 -0
- package/src/core/catalogue/catalogue.generated.ts +6 -0
- package/src/core/catalogue/values.generated.ts +6 -0
- package/src/core/check/index.ts +3 -0
- package/src/core/check/library-prose.generated.ts +367 -0
- package/src/core/check/surface.ts +34 -0
- package/src/core/emit/defaults.ts +70 -0
- package/src/core/emit/index.ts +2 -0
- package/src/core/engine/index.ts +1 -1
- package/src/core/engine/load.ts +16 -2
- package/src/core/index.ts +85 -1
- package/src/core/version/version.generated.ts +1 -1
- package/src/editor/complete.ts +266 -0
- package/src/editor/diagnose.ts +71 -0
- package/src/editor/format.ts +85 -0
- package/src/editor/highlight.ts +110 -0
- package/src/editor/hover.ts +199 -0
- package/src/editor/index.ts +65 -0
- package/src/editor/kinds.ts +157 -0
- package/src/editor/layout.ts +189 -0
- package/src/editor/manifest.ts +119 -0
- package/src/editor/reading.ts +69 -0
- package/src/editor/scan.ts +162 -0
- package/src/editor/scope.ts +122 -0
- package/src/editor/signature.ts +150 -0
- package/src/editor/site.ts +233 -0
- package/src/editor/spacing.ts +132 -0
|
@@ -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,350 @@
|
|
|
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
|
+
readonly averageBarsHeld: number | null;
|
|
125
|
+
readonly barsInMarket: number;
|
|
126
|
+
readonly barCount: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The closed trades split three ways, and the sums each half contributes. */
|
|
130
|
+
interface Tally {
|
|
131
|
+
readonly netProfit: Money;
|
|
132
|
+
readonly grossProfit: Money;
|
|
133
|
+
readonly grossLoss: Money;
|
|
134
|
+
readonly charges: Money;
|
|
135
|
+
readonly tradeCount: number;
|
|
136
|
+
readonly openTradeCount: number;
|
|
137
|
+
readonly wins: number;
|
|
138
|
+
readonly losses: number;
|
|
139
|
+
readonly scratches: number;
|
|
140
|
+
readonly winTotal: Money;
|
|
141
|
+
readonly lossTotal: Money;
|
|
142
|
+
readonly heldTotal: number;
|
|
143
|
+
readonly heldCount: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The deepest point of the curve, and how long the run stayed under water. */
|
|
147
|
+
interface Depth {
|
|
148
|
+
readonly maxDrawdown: Money;
|
|
149
|
+
readonly maxDrawdownPercent: number;
|
|
150
|
+
readonly maxDrawdownAt: number | null;
|
|
151
|
+
readonly longestDrawdownBars: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The whole run in one shape, folded from its trades and its own curve.
|
|
156
|
+
*
|
|
157
|
+
* The curve is passed in rather than recomputed, because a summary that folded
|
|
158
|
+
* its own would be a second equity curve with a second set of rounding, and the
|
|
159
|
+
* first disagreement between them would be a drawdown figure that no point in
|
|
160
|
+
* the reported curve ever reached.
|
|
161
|
+
*/
|
|
162
|
+
export function summaryOf(
|
|
163
|
+
trades: readonly Trade[],
|
|
164
|
+
equity: readonly EquityPoint[],
|
|
165
|
+
contract: Contract,
|
|
166
|
+
capital: Money,
|
|
167
|
+
): Summary {
|
|
168
|
+
const tally = tallyOf(trades);
|
|
169
|
+
const depth = depthOf(equity);
|
|
170
|
+
const decided = tally.wins + tally.losses;
|
|
171
|
+
|
|
172
|
+
// Net over the closed count, and nothing else, because this is the figure the
|
|
173
|
+
// other spelling is checked against. The win rate spelling divides by the
|
|
174
|
+
// decided trades instead, so the two are the same number exactly when no
|
|
175
|
+
// trade scratched, and `tests/accounting/statistics.test.ts` asserts both the
|
|
176
|
+
// agreement and the one case that parts them.
|
|
177
|
+
const expectancy = tally.tradeCount === 0 ? 0 : tally.netProfit / tally.tradeCount;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
capital,
|
|
181
|
+
currency: contract.currency,
|
|
182
|
+
netProfit: tally.netProfit,
|
|
183
|
+
grossProfit: tally.grossProfit,
|
|
184
|
+
grossLoss: tally.grossLoss,
|
|
185
|
+
charges: tally.charges,
|
|
186
|
+
returnPercent: ratioOf(tally.netProfit, capital),
|
|
187
|
+
tradeCount: tally.tradeCount,
|
|
188
|
+
openTradeCount: tally.openTradeCount,
|
|
189
|
+
wins: tally.wins,
|
|
190
|
+
losses: tally.losses,
|
|
191
|
+
scratches: tally.scratches,
|
|
192
|
+
winRate: decided === 0 ? null : tally.wins / decided,
|
|
193
|
+
averageWin: tally.wins === 0 ? 0 : tally.winTotal / tally.wins,
|
|
194
|
+
averageLoss: tally.losses === 0 ? 0 : -tally.lossTotal / tally.losses,
|
|
195
|
+
expectancy,
|
|
196
|
+
expectancyStandardError: standardErrorOf(trades, expectancy, tally.tradeCount),
|
|
197
|
+
profitFactor: tally.grossLoss > 0 ? tally.grossProfit / tally.grossLoss : null,
|
|
198
|
+
maxDrawdown: depth.maxDrawdown,
|
|
199
|
+
maxDrawdownPercent: depth.maxDrawdownPercent,
|
|
200
|
+
maxDrawdownAt: depth.maxDrawdownAt,
|
|
201
|
+
longestDrawdownBars: depth.longestDrawdownBars,
|
|
202
|
+
averageBarsHeld: tally.heldCount === 0 ? null : tally.heldTotal / tally.heldCount,
|
|
203
|
+
barsInMarket: barsInMarketOver(trades, equity),
|
|
204
|
+
barCount: equity.length,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* One pass over the trades, in the order they are given.
|
|
210
|
+
*
|
|
211
|
+
* A trade wins or loses on its net after charges, and its gross is what it
|
|
212
|
+
* contributes to the gross figures: "the sum of the winning trades, before
|
|
213
|
+
* charges" is two statements and this is where they meet. The one arrangement
|
|
214
|
+
* that reads oddly is a trade whose gross was positive and whose charges took
|
|
215
|
+
* it under, which lands in the losses and takes its positive gross with it,
|
|
216
|
+
* lowering the gross loss. That is on purpose. The alternative is a trade
|
|
217
|
+
* counted as a loser in one figure and a winner in another, and a profit factor
|
|
218
|
+
* whose two halves are counted over different sets is worse than one whose
|
|
219
|
+
* magnitude is odd on a trade that barely moved.
|
|
220
|
+
*
|
|
221
|
+
* What is not on purpose, and is why `profitFactor` is null rather than a ratio
|
|
222
|
+
* whenever this figure is not above zero: enough of those trades and the gross
|
|
223
|
+
* loss reaches zero or goes under, and dividing by it reported a profit factor
|
|
224
|
+
* of minus a half. A statistic being odd is a thing a reader can weigh. A
|
|
225
|
+
* statistic being negative where every use of it is a non-negative ratio is a
|
|
226
|
+
* number nobody can act on.
|
|
227
|
+
*/
|
|
228
|
+
function tallyOf(trades: readonly Trade[]): Tally {
|
|
229
|
+
let netProfit = 0;
|
|
230
|
+
let grossProfit = 0;
|
|
231
|
+
let grossLoss = 0;
|
|
232
|
+
let charges = 0;
|
|
233
|
+
let tradeCount = 0;
|
|
234
|
+
let openTradeCount = 0;
|
|
235
|
+
let wins = 0;
|
|
236
|
+
let losses = 0;
|
|
237
|
+
let scratches = 0;
|
|
238
|
+
let winTotal = 0;
|
|
239
|
+
let lossTotal = 0;
|
|
240
|
+
let heldTotal = 0;
|
|
241
|
+
let heldCount = 0;
|
|
242
|
+
|
|
243
|
+
for (const trade of trades) {
|
|
244
|
+
charges += trade.charges;
|
|
245
|
+
if (trade.isOpen) {
|
|
246
|
+
openTradeCount += 1;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
tradeCount += 1;
|
|
250
|
+
netProfit += trade.netProfit;
|
|
251
|
+
if (trade.barsHeld !== null) {
|
|
252
|
+
heldTotal += trade.barsHeld;
|
|
253
|
+
heldCount += 1;
|
|
254
|
+
}
|
|
255
|
+
if (trade.netProfit > 0) {
|
|
256
|
+
wins += 1;
|
|
257
|
+
winTotal += trade.netProfit;
|
|
258
|
+
grossProfit += trade.grossProfit;
|
|
259
|
+
} else if (trade.netProfit < 0) {
|
|
260
|
+
losses += 1;
|
|
261
|
+
lossTotal += trade.netProfit;
|
|
262
|
+
grossLoss -= trade.grossProfit;
|
|
263
|
+
} else {
|
|
264
|
+
scratches += 1;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
netProfit,
|
|
270
|
+
grossProfit,
|
|
271
|
+
grossLoss,
|
|
272
|
+
charges,
|
|
273
|
+
tradeCount,
|
|
274
|
+
openTradeCount,
|
|
275
|
+
wins,
|
|
276
|
+
losses,
|
|
277
|
+
scratches,
|
|
278
|
+
winTotal,
|
|
279
|
+
lossTotal,
|
|
280
|
+
heldTotal,
|
|
281
|
+
heldCount,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* The deepest the curve went, named as one point rather than as three figures.
|
|
287
|
+
*
|
|
288
|
+
* The money, the fraction and the time all come from the same point, and the
|
|
289
|
+
* point is the deepest in money with the earliest one winning a tie. Taking the
|
|
290
|
+
* worst fraction from one bar and the worst money from another would describe a
|
|
291
|
+
* moment the run never had, and a reader comparing the two figures would find
|
|
292
|
+
* them inconsistent with every point in the curve they were drawn from.
|
|
293
|
+
*
|
|
294
|
+
* `longestDrawdownBars` is the longest run of consecutive bars under a peak: it
|
|
295
|
+
* starts at the first bar below one and ends at the bar before the recovery, so
|
|
296
|
+
* a run still under water at the last bar counts to the end. It is often the
|
|
297
|
+
* figure that actually stops a trader, and it is not the total number of bars
|
|
298
|
+
* spent under water, which is a different and much larger number.
|
|
299
|
+
*/
|
|
300
|
+
function depthOf(equity: readonly EquityPoint[]): Depth {
|
|
301
|
+
let maxDrawdown = 0;
|
|
302
|
+
let maxDrawdownPercent = 0;
|
|
303
|
+
let maxDrawdownAt: number | null = null;
|
|
304
|
+
let longestDrawdownBars = 0;
|
|
305
|
+
let under = 0;
|
|
306
|
+
|
|
307
|
+
for (const point of equity) {
|
|
308
|
+
if (point.drawdown < maxDrawdown) {
|
|
309
|
+
maxDrawdown = point.drawdown;
|
|
310
|
+
maxDrawdownPercent = point.drawdownPercent;
|
|
311
|
+
maxDrawdownAt = point.time;
|
|
312
|
+
}
|
|
313
|
+
if (point.drawdown < 0) {
|
|
314
|
+
under += 1;
|
|
315
|
+
if (under > longestDrawdownBars) longestDrawdownBars = under;
|
|
316
|
+
} else {
|
|
317
|
+
under = 0;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return { maxDrawdown, maxDrawdownPercent, maxDrawdownAt, longestDrawdownBars };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* The standard error of the expectancy: the sample deviation over the root of
|
|
326
|
+
* the count.
|
|
327
|
+
*
|
|
328
|
+
* The sample deviation, with the count less one under it, and not the
|
|
329
|
+
* population one. The trades a run took are a sample of the trades the strategy
|
|
330
|
+
* would take, which is the whole reason this figure is here, and the population
|
|
331
|
+
* spelling understates the spread by exactly the amount that matters on the
|
|
332
|
+
* short runs where the question is asked.
|
|
333
|
+
*
|
|
334
|
+
* Fewer than two closed trades has no spread to measure and gives zero.
|
|
335
|
+
*/
|
|
336
|
+
function standardErrorOf(
|
|
337
|
+
trades: readonly Trade[],
|
|
338
|
+
expectancy: Money,
|
|
339
|
+
tradeCount: number,
|
|
340
|
+
): Money {
|
|
341
|
+
if (tradeCount < 2) return 0;
|
|
342
|
+
|
|
343
|
+
let squares = 0;
|
|
344
|
+
for (const trade of trades) {
|
|
345
|
+
if (trade.isOpen) continue;
|
|
346
|
+
const away = trade.netProfit - expectancy;
|
|
347
|
+
squares += away * away;
|
|
348
|
+
}
|
|
349
|
+
return Math.sqrt(squares / (tradeCount - 1) / tradeCount);
|
|
350
|
+
}
|