wickra-backtest-wasm 0.1.6 → 0.1.8
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/README.md +73 -73
- package/package.json +1 -1
- package/wickra_backtest_wasm.d.ts +5 -5
- package/wickra_backtest_wasm_bg.js +21 -21
- package/wickra_backtest_wasm_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=514" alt="Wickra Backtest — backtest and live are byte-identical" width="100%"></a>
|
|
2
|
+
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=514-7" alt="Wickra Backtest — backtest and live are byte-identical" width="100%"></a>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
[](https://github.com/wickra-lib/wickra)
|
|
6
|
+
[](https://github.com/wickra-lib/wickra-backtest)
|
|
7
7
|
[](https://github.com/wickra-lib/wickra-backtest/actions/workflows/ci.yml)
|
|
8
8
|
[](https://github.com/wickra-lib/wickra-backtest/actions/workflows/codeql.yml)
|
|
9
9
|
[](https://codecov.io/gh/wickra-lib/wickra-backtest)
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
[](https://github.com/wickra-lib/wickra-backtest/attestations)
|
|
22
22
|
[](https://backtest.wickra.org)
|
|
23
23
|
[](golden/)
|
|
24
|
-
[](https://backtest-live.wickra.org)
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
@@ -29,9 +29,11 @@
|
|
|
29
29
|
event-driven backtester built on the [Wickra](https://github.com/wickra-lib/wickra)
|
|
30
30
|
indicator core.
|
|
31
31
|
|
|
32
|
-
> **▶ Live
|
|
32
|
+
> **▶ Live demos:** the backtester compiled to WebAssembly, an equity curve building bar by bar — **[backtest-live.wickra.org](https://backtest-live.wickra.org)**;
|
|
33
|
+
> one StrategySpec side by side in Python, Rust, JS and Go — **[playground.wickra.org](https://playground.wickra.org)**;
|
|
34
|
+
> all 514 indicators of the core over a real Binance feed — **[live.wickra.org](https://live.wickra.org)**. Zero backend, all of them.
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
**Part of the [Wickra ecosystem](#ecosystem):** the same data-driven core and ten-language binding surface also power [wickra-exchange](https://github.com/wickra-lib/wickra-exchange), [wickra-terminal](https://github.com/wickra-lib/wickra-terminal), [wickra-screener](https://github.com/wickra-lib/wickra-screener) and 20 more — see [the full list](https://github.com/wickra-lib).
|
|
35
37
|
|
|
36
38
|
The engine consumes the **exact same `wickra-core` O(1) indicator kernels** that
|
|
37
39
|
power live Wickra, and a strategy is **data (a JSON spec), not code** — so a
|
|
@@ -78,6 +80,13 @@ with wbt.StreamingBacktest(spec=spec) as live:
|
|
|
78
80
|
The two reports are byte-identical. That is the whole claim, and a shared
|
|
79
81
|
[golden corpus](golden/) holds every one of the ten bindings to it.
|
|
80
82
|
|
|
83
|
+
## Status
|
|
84
|
+
|
|
85
|
+
**0.1.8 — the current release.** The engine, the data-driven `StrategySpec`, the
|
|
86
|
+
full execution and cost model, the microstructure feeds and all ten language
|
|
87
|
+
bindings are implemented and tested; a shared [golden corpus](golden/) pins the
|
|
88
|
+
cross-language equality byte-for-byte.
|
|
89
|
+
|
|
81
90
|
## Documentation
|
|
82
91
|
|
|
83
92
|
- **[Strategy spec reference](docs/STRATEGY_SPEC.md)** — the full DSL: operands,
|
|
@@ -123,15 +132,6 @@ runs unchanged from ten languages and a shared golden corpus pins every one of
|
|
|
123
132
|
them to the same report, byte for byte. No other engine in this table offers that
|
|
124
133
|
because none of them needs to.
|
|
125
134
|
|
|
126
|
-
## Status
|
|
127
|
-
|
|
128
|
-
**Alpha / work in progress.** The engine, the data-driven `StrategySpec`, the
|
|
129
|
-
full execution and cost model, the microstructure feeds and all ten language
|
|
130
|
-
bindings are implemented and tested; a shared [golden corpus](golden/) pins the
|
|
131
|
-
cross-language equality byte-for-byte. Released as **v0.1.0** to every registry:
|
|
132
|
-
crates.io, PyPI, npm, NuGet, Maven Central, the Go module proxy and
|
|
133
|
-
R-universe.
|
|
134
|
-
|
|
135
135
|
## Quickstart
|
|
136
136
|
|
|
137
137
|
A strategy is **data** — a JSON spec. Run one over a candle file with the `wkbt` CLI:
|
|
@@ -174,7 +174,7 @@ the **same engine** one bar at a time — backtest and live are one code path. A
|
|
|
174
174
|
single `run_json` request bundles candles, the spec and any feeds, and is the
|
|
175
175
|
uniform entry point every binding wraps.
|
|
176
176
|
|
|
177
|
-
##
|
|
177
|
+
## Use in any language
|
|
178
178
|
|
|
179
179
|
Every binding takes the same OHLCV arrays (or a `run_json` request) and JSON spec
|
|
180
180
|
and returns the same report — byte-identical (a dict in Python). Each has a
|
|
@@ -201,63 +201,6 @@ The C, C++, C#, Go, Java and R bindings all call through the same C ABI hub; the
|
|
|
201
201
|
both the plain OHLCV path and the order-book / trade / derivatives /
|
|
202
202
|
cross-section feed paths.
|
|
203
203
|
|
|
204
|
-
## Benchmarks
|
|
205
|
-
|
|
206
|
-
O(1) per bar — about **1.7M bars/second** on one core (a year of 1-minute bars in
|
|
207
|
-
~0.3 s). The cost of a bar is bounded by the indicators the spec configures, never
|
|
208
|
-
by how much history precedes it. Full tables and how to reproduce them live in
|
|
209
|
-
**[BENCHMARKS.md](BENCHMARKS.md)**.
|
|
210
|
-
|
|
211
|
-
### Pick your language with eyes open — per-binding throughput
|
|
212
|
-
|
|
213
|
-
Every binding drives the **same** Rust engine, so this is **not** a speed claim —
|
|
214
|
-
it is the raw cost of crossing each language's FFI boundary, measured with the
|
|
215
|
-
[shared example strategy](examples/ema-cross.json) over 100,000 bars (median of
|
|
216
|
-
three runs, one development machine). **Batch collapses towards the floor;
|
|
217
|
-
streaming is where the boundary shows** — so if you drive a live loop bar by bar,
|
|
218
|
-
the table tells you which binding keeps up.
|
|
219
|
-
|
|
220
|
-
| Binding | streaming | ns/bar | batch | ns/bar |
|
|
221
|
-
|---------|----------:|-------:|------:|-------:|
|
|
222
|
-
| C | 6,750,000 b/s | 148 | 6,548,000 b/s | 153 |
|
|
223
|
-
| C# | 6,188,000 b/s | 162 | 6,315,000 b/s | 158 |
|
|
224
|
-
| Go | 4,621,000 b/s | 216 | 6,448,000 b/s | 155 |
|
|
225
|
-
| Java | 4,493,000 b/s | 223 | 5,565,000 b/s | 180 |
|
|
226
|
-
| WASM | 4,127,000 b/s | 242 | 4,878,000 b/s | 205 |
|
|
227
|
-
| Node | 3,438,000 b/s | 291 | 2,530,000 b/s | 395 |
|
|
228
|
-
| Python | 1,411,000 b/s | 709 | 1,486,000 b/s | 673 |
|
|
229
|
-
| R | 284,000 b/s | 3,527 | 6,213,000 b/s | 161 |
|
|
230
|
-
|
|
231
|
-
**C is the floor**: it calls the exported functions directly, with no marshalling
|
|
232
|
-
of its own, so its ~148 ns/bar is the engine plus a function call — every other
|
|
233
|
-
row is that number plus what the language adds. Two results are the opposite of
|
|
234
|
-
what one might assume: **Node's batch path is slower than its streaming path**
|
|
235
|
-
(marshalling six JavaScript arrays across napi costs more than 100,000 scalar
|
|
236
|
-
calls), and **WASM beats the native Node binding on both paths**. All ten share
|
|
237
|
-
one verified implementation, so the *numbers* differ but the *values* do not.
|
|
238
|
-
Methodology and the per-binding discussion are in
|
|
239
|
-
[BENCHMARKS.md](BENCHMARKS.md#per-binding-throughput--the-cost-of-the-boundary).
|
|
240
|
-
|
|
241
|
-
## Requirements
|
|
242
|
-
|
|
243
|
-
The minimum supported version per language. The same engine kernel runs behind
|
|
244
|
-
every binding; the C-ABI bindings that compile on install — Go (cgo) and R
|
|
245
|
-
(`.Call`) — also need a C compiler, and Java runs with
|
|
246
|
-
`--enable-native-access=ALL-UNNAMED`.
|
|
247
|
-
|
|
248
|
-
| Language | Package | Minimum supported |
|
|
249
|
-
|----------|-------------------------------------------|----------------------------|
|
|
250
|
-
| Rust | crates.io · `wickra-backtest` | 1.86 (MSRV) |
|
|
251
|
-
| Python | PyPI · `wickra-backtest` (abi3 wheel) | 3.9 (tested through 3.13) |
|
|
252
|
-
| Node.js | npm · `wickra-backtest` (N-API 8) | 22 (tested on 22 · 24 LTS) |
|
|
253
|
-
| WASM | npm · `wickra-backtest-wasm` | any modern JS engine |
|
|
254
|
-
| C | `wickra_backtest.h` + library (releases) | C99 compiler |
|
|
255
|
-
| C++ | the C ABI + optional `wickra_backtest.hpp` | C++14 compiler |
|
|
256
|
-
| C# | NuGet · `Wickra.Backtest` | .NET 8 (`net8.0`) |
|
|
257
|
-
| Go | module · `wickra-lib/wickra-backtest-go` | Go 1.23 (cgo) |
|
|
258
|
-
| Java | Maven Central · `org.wickra:wickra-backtest` | Java 22 (FFM / Panama) |
|
|
259
|
-
| R | r-universe · `wickrabacktest` | R ≥ 4.1 (Rtools on Win.) |
|
|
260
|
-
|
|
261
204
|
## Project layout
|
|
262
205
|
|
|
263
206
|
```
|
|
@@ -364,6 +307,63 @@ than argued.
|
|
|
364
307
|
> compare to a relative tolerance instead. None currently does, and that is a
|
|
365
308
|
> property of the corpus worth keeping deliberately rather than by accident.
|
|
366
309
|
|
|
310
|
+
## Requirements
|
|
311
|
+
|
|
312
|
+
The minimum supported version per language. The same engine kernel runs behind
|
|
313
|
+
every binding; the C-ABI bindings that compile on install — Go (cgo) and R
|
|
314
|
+
(`.Call`) — also need a C compiler, and Java runs with
|
|
315
|
+
`--enable-native-access=ALL-UNNAMED`.
|
|
316
|
+
|
|
317
|
+
| Language | Package | Minimum supported |
|
|
318
|
+
|----------|-------------------------------------------|----------------------------|
|
|
319
|
+
| Rust | crates.io · `wickra-backtest` | 1.86 (MSRV) |
|
|
320
|
+
| Python | PyPI · `wickra-backtest` (abi3 wheel) | 3.9 (tested through 3.13) |
|
|
321
|
+
| Node.js | npm · `wickra-backtest` (N-API 8) | 22 (tested on 22 · 24 LTS) |
|
|
322
|
+
| WASM | npm · `wickra-backtest-wasm` | any modern JS engine |
|
|
323
|
+
| C | `wickra_backtest.h` + library (releases) | C99 compiler |
|
|
324
|
+
| C++ | the C ABI + optional `wickra_backtest.hpp` | C++14 compiler |
|
|
325
|
+
| C# | NuGet · `Wickra.Backtest` | .NET 8 (`net8.0`) |
|
|
326
|
+
| Go | module · `wickra-lib/wickra-backtest-go` | Go 1.23 (cgo) |
|
|
327
|
+
| Java | Maven Central · `org.wickra:wickra-backtest` | Java 22 (FFM / Panama) |
|
|
328
|
+
| R | r-universe · `wickrabacktest` | R ≥ 4.1 (Rtools on Win.) |
|
|
329
|
+
|
|
330
|
+
## Benchmarks
|
|
331
|
+
|
|
332
|
+
O(1) per bar — about **1.7M bars/second** on one core (a year of 1-minute bars in
|
|
333
|
+
~0.3 s). The cost of a bar is bounded by the indicators the spec configures, never
|
|
334
|
+
by how much history precedes it. Full tables and how to reproduce them live in
|
|
335
|
+
**[BENCHMARKS.md](BENCHMARKS.md)**.
|
|
336
|
+
|
|
337
|
+
### Pick your language with eyes open — per-binding throughput
|
|
338
|
+
|
|
339
|
+
Every binding drives the **same** Rust engine, so this is **not** a speed claim —
|
|
340
|
+
it is the raw cost of crossing each language's FFI boundary, measured with the
|
|
341
|
+
[shared example strategy](examples/ema-cross.json) over 100,000 bars (median of
|
|
342
|
+
three runs, one development machine). **Batch collapses towards the floor;
|
|
343
|
+
streaming is where the boundary shows** — so if you drive a live loop bar by bar,
|
|
344
|
+
the table tells you which binding keeps up.
|
|
345
|
+
|
|
346
|
+
| Binding | streaming | ns/bar | batch | ns/bar |
|
|
347
|
+
|---------|----------:|-------:|------:|-------:|
|
|
348
|
+
| C | 6,750,000 b/s | 148 | 6,548,000 b/s | 153 |
|
|
349
|
+
| C# | 6,188,000 b/s | 162 | 6,315,000 b/s | 158 |
|
|
350
|
+
| Go | 4,621,000 b/s | 216 | 6,448,000 b/s | 155 |
|
|
351
|
+
| Java | 4,493,000 b/s | 223 | 5,565,000 b/s | 180 |
|
|
352
|
+
| WASM | 4,127,000 b/s | 242 | 4,878,000 b/s | 205 |
|
|
353
|
+
| Node | 3,438,000 b/s | 291 | 2,530,000 b/s | 395 |
|
|
354
|
+
| Python | 1,411,000 b/s | 709 | 1,486,000 b/s | 673 |
|
|
355
|
+
| R | 284,000 b/s | 3,527 | 6,213,000 b/s | 161 |
|
|
356
|
+
|
|
357
|
+
**C is the floor**: it calls the exported functions directly, with no marshalling
|
|
358
|
+
of its own, so its ~148 ns/bar is the engine plus a function call — every other
|
|
359
|
+
row is that number plus what the language adds. Two results are the opposite of
|
|
360
|
+
what one might assume: **Node's batch path is slower than its streaming path**
|
|
361
|
+
(marshalling six JavaScript arrays across napi costs more than 100,000 scalar
|
|
362
|
+
calls), and **WASM beats the native Node binding on both paths**. All ten share
|
|
363
|
+
one verified implementation, so the *numbers* differ but the *values* do not.
|
|
364
|
+
Methodology and the per-binding discussion are in
|
|
365
|
+
[BENCHMARKS.md](BENCHMARKS.md#per-binding-throughput--the-cost-of-the-boundary).
|
|
366
|
+
|
|
367
367
|
## Ecosystem
|
|
368
368
|
|
|
369
369
|
Part of the [Wickra](https://github.com/wickra-lib/wickra) family — each one a
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"kingchenc <support@wickra.org>"
|
|
6
6
|
],
|
|
7
7
|
"description": "WebAssembly bindings for the wickra-backtest streaming backtester — backtest in the browser.",
|
|
8
|
-
"version": "0.1.
|
|
8
|
+
"version": "0.1.8",
|
|
9
9
|
"license": "MIT OR Apache-2.0",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -41,11 +41,6 @@ export class StreamingBacktest {
|
|
|
41
41
|
* Build from a JSON strategy spec and starting capital.
|
|
42
42
|
*/
|
|
43
43
|
constructor(spec_json: string, capital: number);
|
|
44
|
-
/**
|
|
45
|
-
* Advance by one OHLCV bar. `volume` defaults to 0 and `time` to the number
|
|
46
|
-
* of bars fed so far.
|
|
47
|
-
*/
|
|
48
|
-
step(open: number, high: number, low: number, close: number, volume?: number | null, time?: number | null): void;
|
|
49
44
|
/**
|
|
50
45
|
* Advance by one bar described as a request document:
|
|
51
46
|
* `{"candle": {...}, "feeds": {...}}`, where `feeds` optionally carries this
|
|
@@ -53,6 +48,11 @@ export class StreamingBacktest {
|
|
|
53
48
|
* This is the only form that can drive a strategy reading a side feed.
|
|
54
49
|
*/
|
|
55
50
|
stepJson(step_json: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* Advance by one OHLCV bar. `volume` defaults to 0 and `time` to the number
|
|
53
|
+
* of bars fed so far.
|
|
54
|
+
*/
|
|
55
|
+
step(open: number, high: number, low: number, close: number, volume?: number | null, time?: number | null): void;
|
|
56
56
|
/**
|
|
57
57
|
* Whether the run has been finished or closed.
|
|
58
58
|
*/
|
|
@@ -168,19 +168,18 @@ export class StreamingBacktest {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
171
|
-
* Advance by one
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* @param {
|
|
176
|
-
* @param {number} close
|
|
177
|
-
* @param {number | null} [volume]
|
|
178
|
-
* @param {number | null} [time]
|
|
171
|
+
* Advance by one bar described as a request document:
|
|
172
|
+
* `{"candle": {...}, "feeds": {...}}`, where `feeds` optionally carries this
|
|
173
|
+
* bar's `reference` / `deriv` / `orderbook` / `trades` / `cross_section`.
|
|
174
|
+
* This is the only form that can drive a strategy reading a side feed.
|
|
175
|
+
* @param {string} step_json
|
|
179
176
|
*/
|
|
180
|
-
|
|
177
|
+
stepJson(step_json) {
|
|
181
178
|
try {
|
|
182
179
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
183
|
-
|
|
180
|
+
const ptr0 = passStringToWasm0(step_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
181
|
+
const len0 = WASM_VECTOR_LEN;
|
|
182
|
+
wasm.streamingbacktest_stepJson(retptr, this.__wbg_ptr, ptr0, len0);
|
|
184
183
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
185
184
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
186
185
|
if (r1) {
|
|
@@ -191,18 +190,19 @@ export class StreamingBacktest {
|
|
|
191
190
|
}
|
|
192
191
|
}
|
|
193
192
|
/**
|
|
194
|
-
* Advance by one bar
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* @param {
|
|
193
|
+
* Advance by one OHLCV bar. `volume` defaults to 0 and `time` to the number
|
|
194
|
+
* of bars fed so far.
|
|
195
|
+
* @param {number} open
|
|
196
|
+
* @param {number} high
|
|
197
|
+
* @param {number} low
|
|
198
|
+
* @param {number} close
|
|
199
|
+
* @param {number | null} [volume]
|
|
200
|
+
* @param {number | null} [time]
|
|
199
201
|
*/
|
|
200
|
-
|
|
202
|
+
step(open, high, low, close, volume, time) {
|
|
201
203
|
try {
|
|
202
204
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
203
|
-
|
|
204
|
-
const len0 = WASM_VECTOR_LEN;
|
|
205
|
-
wasm.streamingbacktest_stepJson(retptr, this.__wbg_ptr, ptr0, len0);
|
|
205
|
+
wasm.streamingbacktest_step(retptr, this.__wbg_ptr, open, high, low, close, !isLikeNone(volume), isLikeNone(volume) ? 0 : volume, !isLikeNone(time), isLikeNone(time) ? 0 : time);
|
|
206
206
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
207
207
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
208
208
|
if (r1) {
|
|
@@ -319,11 +319,11 @@ export function version() {
|
|
|
319
319
|
wasm.__wbindgen_export3(deferred1_0, deferred1_1, 1);
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
|
-
export function
|
|
322
|
+
export function __wbg_Error_67e7344beaa85059(arg0, arg1) {
|
|
323
323
|
const ret = Error(getStringFromWasm0(arg0, arg1));
|
|
324
324
|
return addHeapObject(ret);
|
|
325
325
|
}
|
|
326
|
-
export function
|
|
326
|
+
export function __wbg___wbindgen_throw_5d9e815e6fdf150f(arg0, arg1) {
|
|
327
327
|
throw new Error(getStringFromWasm0(arg0, arg1));
|
|
328
328
|
}
|
|
329
329
|
const StreamingBacktestFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
Binary file
|