wickra-backtest-wasm 0.1.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/LICENSE-APACHE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or Derivative
95
+ Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 kingchenc and the Wickra contributors
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/LICENSE-MIT ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kingchenc and the Wickra contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,396 @@
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>
3
+ </p>
4
+
5
+ [![Built on Wickra](https://img.shields.io/badge/built%20on-wickra-3b82f6)](https://github.com/wickra-lib/wickra)
6
+ [![Status](https://img.shields.io/badge/status-alpha%20(WIP)-orange)](https://github.com/wickra-lib/wickra-backtest)
7
+ [![CI](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/ci.svg)](https://github.com/wickra-lib/wickra-backtest/actions/workflows/ci.yml)
8
+ [![CodeQL](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/codeql.svg)](https://github.com/wickra-lib/wickra-backtest/actions/workflows/codeql.yml)
9
+ [![codecov](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/codecov.svg)](https://codecov.io/gh/wickra-lib/wickra-backtest)
10
+ [![GitHub release](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/release.svg)](https://github.com/wickra-lib/wickra-backtest/releases/latest)
11
+ [![crates.io](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/crates.svg)](https://crates.io/crates/wickra-backtest)
12
+ [![PyPI](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/pypi.svg)](https://pypi.org/project/wickra-backtest/)
13
+ [![npm](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/npm.svg)](https://www.npmjs.com/package/wickra-backtest)
14
+ [![NuGet](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/nuget.svg)](https://www.nuget.org/packages/Wickra.Backtest)
15
+ [![Maven Central](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/maven.svg)](https://central.sonatype.com/artifact/org.wickra/wickra-backtest)
16
+ [![Go module](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/go.svg)](https://pkg.go.dev/github.com/wickra-lib/wickra-backtest-go)
17
+ [![R-universe](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/r-universe.svg)](https://wickra-lib.r-universe.dev)
18
+ [![License: MIT OR Apache-2.0](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/license.svg)](#license)
19
+ [![OpenSSF Scorecard](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/scorecard.svg)](https://scorecard.dev/viewer/?uri=github.com/wickra-lib/wickra-backtest)
20
+ [![OpenSSF Best Practices](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/best-practices.svg)](https://www.bestpractices.dev/)
21
+ [![Build provenance](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/provenance.svg)](https://github.com/wickra-lib/wickra-backtest/attestations)
22
+ [![Docs](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/docs.svg)](https://backtest.wickra.org)
23
+ [![Verified across 10 languages](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/wickra-backtest/verified.svg)](golden/)
24
+ [![Live demo](https://img.shields.io/badge/live%20demo-backtest--live.wickra.org-3b82f6)](https://backtest-live.wickra.org)
25
+
26
+ ---
27
+
28
+ **Backtest and live — byte-identical, in 10 languages.** A streaming-native,
29
+ event-driven backtester built on the [Wickra](https://github.com/wickra-lib/wickra)
30
+ indicator core.
31
+
32
+ > **▶ Live demo:** run a strategy in your browser and watch the equity curve build bar by bar — **[backtest-live.wickra.org](https://backtest-live.wickra.org)** · zero backend, the same engine this repository ships, compiled to WebAssembly.
33
+
34
+ > **Part of the [Wickra ecosystem](https://github.com/wickra-lib):** 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), [wickra-xray](https://github.com/wickra-lib/wickra-xray), [wickra-radar](https://github.com/wickra-lib/wickra-radar), [wickra-copilot](https://github.com/wickra-lib/wickra-copilot) and [wickra-shazam](https://github.com/wickra-lib/wickra-shazam).
35
+
36
+ The engine consumes the **exact same `wickra-core` O(1) indicator kernels** that
37
+ power live Wickra, and a strategy is **data (a JSON spec), not code** — so a
38
+ backtest and a live run over the same spec produce identical signals, across
39
+ every Wickra language binding. The same engine, fed live instead of historical
40
+ bars, becomes the live bot: **backtest ≡ live, by construction.**
41
+
42
+ The strategy spec can reference **495 `wickra-core` indicators** by name — every
43
+ backtestable scalar, candle, multi-output, pairwise, derivatives, order-book,
44
+ trade, trade-quote and cross-section indicator, with multi-output fields
45
+ addressed as `"name.field"` (`macd.signal`, `bb.upper`, `adx.plus_di`, …). The
46
+ registry is generated directly from the wickra-core sources, so it stays in
47
+ lock-step with the kernel.
48
+
49
+ ```bash
50
+ pip install wickra-backtest
51
+ ```
52
+
53
+ ```python
54
+ import wickra_backtest as wbt
55
+
56
+ spec = { # a strategy is data, not code
57
+ "symbol": "BTCUSDT", "timeframe": "1h",
58
+ "indicators": {"fast": {"type": "Ema", "params": [12]},
59
+ "slow": {"type": "Ema", "params": [26]}},
60
+ "entry": {"cross_above": ["fast", "slow"]},
61
+ "exit": {"cross_below": ["fast", "slow"]},
62
+ "sizing": {"type": "fixed_fraction", "fraction": 0.95},
63
+ }
64
+
65
+ # Backtest: the whole series at once.
66
+ report = wbt.run(opens, highs, lows, closes, spec=spec)
67
+ print(report["metrics"]["return_pct"], report["metrics"]["sharpe"])
68
+
69
+ # Live: the same spec, the same engine, one bar at a time. Point `step` at a
70
+ # socket instead of an array and nothing else changes.
71
+ with wbt.StreamingBacktest(spec=spec) as live:
72
+ for bar in feed:
73
+ live.step(bar.open, bar.high, bar.low, bar.close)
74
+ print(live.num_trades, live.latest_equity())
75
+ report = live.finish()
76
+ ```
77
+
78
+ The two reports are byte-identical. That is the whole claim, and a shared
79
+ [golden corpus](golden/) holds every one of the ten bindings to it.
80
+
81
+ What it does differently:
82
+
83
+ - **O(1) per tick** — years of tick data in seconds, not hours (no recompute-on-every-tick).
84
+ - **Backtest = live, value-identical across 10 languages** — no reimplementation drift, pinned by a shared golden corpus for the OHLCV path *and* every microstructure feed.
85
+ - **Microstructure backtesting** — replay the order book, trades, perpetual funding and open interest as strategy inputs, not just OHLCV. Most Python backtesters have no place to put them.
86
+ - **Realistic execution** — long/short, market/limit/stop orders, leverage and position caps, five sizing models, intrabar stop-loss / take-profit / trailing stops, maker/taker fees, three slippage models, perpetual funding, liquidation and execution latency.
87
+ - **Polyglot** — the same `StrategySpec` runs from Rust, Python, Node.js, WASM, C, C++, C#, Go, Java and R.
88
+
89
+ | Backtester | Languages | Engine | Strategy is | Book / funding inputs | Latest release |
90
+ |------------|-----------|--------|-------------|----------------------|----------------|
91
+ | **★&nbsp;wickra-backtest** | **Rust · Python · Node.js · WASM · C · C++ · C# · Go · Java · R** | **event-driven, O(1)/bar** | **data (a JSON spec)** | **yes** | unreleased |
92
+ | nautilus_trader | Rust · Python | event-driven | code | yes | 2026-08 |
93
+ | vectorbt | Python | vectorised | code | — | 2026-07 |
94
+ | backtesting.py | Python | vectorised | code | — | 2026-07 |
95
+ | zipline-reloaded | Python | event-driven | code | — | 2025-07 |
96
+ | backtrader | Python | event-driven | code | — | 2023-04 |
97
+
98
+ Release dates are the latest published version on PyPI, checked when this table
99
+ was written; "—" means the feed is not a first-class strategy input, not that the
100
+ library is bad at what it does. nautilus_trader is the closest comparison and is
101
+ ahead of this project in places — it is a full trading platform with live venue
102
+ adapters, and it has shipped for years. The distinction here is narrower and
103
+ worth stating plainly: a strategy is **data rather than code**, so the same spec
104
+ runs unchanged from ten languages and a shared golden corpus pins every one of
105
+ them to the same report, byte for byte. No other engine in this table offers that
106
+ because none of them needs to.
107
+
108
+ ## Status
109
+
110
+ **Alpha / work in progress.** The engine, the data-driven `StrategySpec`, the
111
+ full execution and cost model, the microstructure feeds and all ten language
112
+ bindings are implemented and tested; a shared [golden corpus](golden/) pins the
113
+ cross-language equality byte-for-byte. Not yet released to any registry.
114
+
115
+ ## Documentation
116
+
117
+ - **[Strategy spec reference](docs/STRATEGY_SPEC.md)** — the full DSL: operands,
118
+ conditions, sizing, costs, slippage, risk, execution and the report shape.
119
+ - **[Cookbook](docs/COOKBOOK.md)** — six ready-to-run strategies (RSI mean
120
+ reversion, MACD trend, Bollinger breakout, Donchian breakout, funding carry,
121
+ order-book imbalance), each validated against the engine.
122
+ - **[Microstructure guide](docs/MICROSTRUCTURE.md)** — backtesting on the order
123
+ book, trades, perpetual funding and market breadth (the differentiator).
124
+ - **[Architecture](ARCHITECTURE.md)** — crates, data flow and design decisions.
125
+ - **[Benchmarks](BENCHMARKS.md)** — throughput methodology and caveats.
126
+ - **[Examples](examples/)** — runnable specs and a sample dataset.
127
+ - The JSON Schema for the spec is at
128
+ [`schema/strategy_spec.schema.json`](schema/strategy_spec.schema.json) and is
129
+ printed by `wkbt schema`.
130
+
131
+ ## Quickstart
132
+
133
+ A strategy is **data** — a JSON spec. Run one over a candle file with the `wkbt` CLI:
134
+
135
+ ```bash
136
+ cargo run --bin wkbt -- run --data examples/sample.csv --spec examples/ema-cross.json
137
+ ```
138
+
139
+ ```text
140
+ bars 80
141
+ trades 4
142
+ return -2.68%
143
+ pnl -268.21
144
+ sharpe -0.186
145
+ max dd 2.68%
146
+ win rate 0.0%
147
+ fees 37.50
148
+ ```
149
+
150
+ A spec declares named indicators and entry/exit rules over them:
151
+
152
+ ```json
153
+ {
154
+ "symbol": "BTCUSDT", "timeframe": "1h",
155
+ "indicators": { "ema_fast": { "type": "Ema", "params": [5] },
156
+ "ema_slow": { "type": "Ema", "params": [15] } },
157
+ "entry": { "cross_above": ["ema_fast", "ema_slow"] },
158
+ "exit": { "cross_below": ["ema_fast", "ema_slow"] },
159
+ "sizing": { "type": "fixed_fraction", "fraction": 0.95 },
160
+ "risk": { "trailing_stop_pct": 5.0 }
161
+ }
162
+ ```
163
+
164
+ See the [cookbook](docs/COOKBOOK.md) and [`examples/`](examples/) for complete
165
+ strategies, and the [spec reference](docs/STRATEGY_SPEC.md) for the full grammar.
166
+
167
+ From Rust, the same thing is `wickra_backtest::run(&spec, &candles)`. For live
168
+ use, `StreamingBacktest::new(&spec, capital)` then `step(candle)` per bar feeds
169
+ the **same engine** one bar at a time — backtest and live are one code path. A
170
+ single `run_json` request bundles candles, the spec and any feeds, and is the
171
+ uniform entry point every binding wraps.
172
+
173
+ ## Run the same spec in any language
174
+
175
+ Every binding takes the same OHLCV arrays (or a `run_json` request) and JSON spec
176
+ and returns the same report — byte-identical (a dict in Python). Each has a
177
+ quickstart:
178
+
179
+ | Binding | Install | Example |
180
+ |---------|---------|---------|
181
+ | Rust | `cargo add wickra-backtest` | [`examples/rust`](examples/rust/src/main.rs) |
182
+ | Python (PyO3) | `pip install wickra-backtest` | [`examples/python/backtest.py`](examples/python/backtest.py) |
183
+ | Node.js (napi-rs) | `npm install wickra-backtest` | [`examples/node/backtest.js`](examples/node/backtest.js) |
184
+ | Browser / WASM | `npm install wickra-backtest-wasm` | [`examples/wasm/backtest.cjs`](examples/wasm/backtest.cjs) |
185
+ | C / C++ (C ABI) | header + library, see [`bindings/c`](bindings/c/README.md) | [`examples/c/streaming.c`](examples/c/streaming.c) · [`cpp_smoke.cpp`](examples/c/cpp_smoke.cpp) |
186
+ | C# (C ABI) | `dotnet add package Wickra.Backtest`, see [`bindings/csharp`](bindings/csharp/README.md) | [`examples/csharp`](examples/csharp/Program.cs) |
187
+ | Go (cgo, C ABI) | `go get github.com/wickra-lib/wickra-backtest-go`, see [`bindings/go`](bindings/go/README.md) | [`examples/go`](examples/go/backtest.go) |
188
+ | Java (FFM, C ABI) | Maven Central `org.wickra:wickra-backtest`, see [`bindings/java`](bindings/java/README.md) | [`examples/java`](examples/java/src/main/java/org/wickra/backtest/examples/Backtest.java) |
189
+ | R (`.Call`, C ABI) | `R CMD INSTALL bindings/r`, see [`bindings/r`](bindings/r/README.md) | [`examples/r/backtest.R`](examples/r/backtest.R) |
190
+
191
+ Every example does the same thing in its own language: read the shared sample
192
+ data, run the series both ways, and fail if the two reports differ.
193
+ [`examples/README.md`](examples/README.md) is the cross-language index.
194
+
195
+ The C, C++, C#, Go, Java and R bindings all call through the same C ABI hub; the
196
+ [golden corpus](golden/) asserts every language produces the same report, for
197
+ both the plain OHLCV path and the order-book / trade / derivatives /
198
+ cross-section feed paths.
199
+
200
+ ## Performance
201
+
202
+ O(1) per bar — about **1.7M bars/second** on one core (a year of 1-minute bars in
203
+ ~0.3 s). See [BENCHMARKS.md](BENCHMARKS.md) for the methodology and caveats.
204
+
205
+ ## Project layout
206
+
207
+ ```
208
+ wickra-backtest/
209
+ ├── crates/
210
+ │ ├── wickra-backtest-core/ engine: spec DSL, registry, rules, execution, portfolio, metrics, report
211
+ │ ├── wickra-backtest-data/ loaders (CSV / JSON / JSONL / Parquet) + resampling + Renko/Kagi/PnF
212
+ │ ├── wickra-backtest/ facade crate (re-exports the engine + runners)
213
+ │ ├── wickra-backtest-cli/ the `wkbt` command-line backtester
214
+ │ └── wickra-backtest-bench/ criterion throughput benchmarks
215
+ ├── bindings/
216
+ │ ├── python/ PyO3 + maturin ├── csharp/ P/Invoke over the C ABI
217
+ │ ├── node/ napi-rs ├── go/ cgo over the C ABI
218
+ │ ├── wasm/ wasm-bindgen ├── java/ FFM over the C ABI
219
+ │ ├── c/ C ABI (cdylib/staticlib + generated header)
220
+ │ └── r/ .Call over the C ABI
221
+ ├── golden/ shared cross-language parity corpus (cases + feed requests)
222
+ ├── schema/ generated JSON Schema for the strategy spec
223
+ ├── examples/ runnable strategies + a sample dataset
224
+ ├── docs/ strategy spec reference + cookbook
225
+ └── fuzz/ cargo-fuzz targets (nightly)
226
+ ```
227
+
228
+ ## Building from source
229
+
230
+ ```bash
231
+ # Rust core + tests + lints
232
+ cargo test --workspace --all-features
233
+ cargo clippy --workspace --all-targets --all-features -- -D warnings
234
+ cargo bench -p wickra-backtest-bench
235
+
236
+ # Python binding (requires a Rust toolchain + maturin)
237
+ cd bindings/python && maturin develop --release && pytest
238
+
239
+ # Node binding (requires @napi-rs/cli)
240
+ cd bindings/node && npm install && npm run build && npm test
241
+
242
+ # WASM binding (requires wasm-pack)
243
+ cd bindings/wasm && wasm-pack build --target nodejs --out-dir pkg && node --test tests/
244
+
245
+ # C ABI (cdylib + staticlib + generated header)
246
+ cargo build -p wickra-backtest-c --release
247
+
248
+ # C# binding (requires the .NET 8 SDK; links the C ABI above)
249
+ dotnet test bindings/csharp/Wickra.Backtest.Tests/Wickra.Backtest.Tests.csproj
250
+
251
+ # Go binding (requires a C compiler for cgo; links the C ABI above)
252
+ cd bindings/go && go test ./...
253
+
254
+ # Java binding (requires JDK 22+ and Maven; links the C ABI above)
255
+ mvn -f bindings/java test
256
+
257
+ # R binding (requires a C toolchain / Rtools; links the C ABI above)
258
+ WKBT_INC="$PWD/bindings/c/include" WKBT_LIB="$PWD/target/debug" R CMD INSTALL bindings/r
259
+ ```
260
+
261
+ The Go, Java and R bindings load the C ABI shared library at run time; put
262
+ `target/debug` (or `target/release`) on the library path. Fuzzing requires a
263
+ nightly toolchain — see [`fuzz/`](fuzz/); the same never-panic invariants are
264
+ covered on stable by the property tests.
265
+
266
+ ## Testing
267
+
268
+ Every layer is covered; the commands are in
269
+ [Building from source](#building-from-source).
270
+
271
+ - `wickra-backtest-core` — 113 unit tests: hand-computed round trips (entry at
272
+ the next open, exit on the signal after it), every sizing model, the cost and
273
+ slippage models, intrabar stops and liquidation, funding, the rule evaluator,
274
+ bounded history, and the feed requirements a spec declares. Plus five
275
+ integration suites: property tests, the shipped example specs, and the three
276
+ golden runners (batch, streaming, and the microstructure requests).
277
+ - `wickra-backtest-data` — 17 unit tests over CSV, JSONL, JSON-array and Binance
278
+ kline decoding, plus the resamplers.
279
+ - `bindings/c` — 12 Rust tests driving the ABI itself, including the streaming
280
+ handle's lifecycle and every error path, so a null or finished handle is
281
+ proven to be reported rather than dereferenced.
282
+ - `bindings/python` — 21 pytest cases: smoke, streaming, golden parity,
283
+ completeness of the module and class surface, and the feed path.
284
+ - `bindings/node` — 19 `node --test` cases, same shape.
285
+ - `bindings/wasm` — 8 `node --test` cases against the built package.
286
+ - `bindings/csharp` — 15 xUnit cases. `bindings/java` — 15 JUnit cases.
287
+ `bindings/go` — 15 `go test` cases. `bindings/r` — 3 script suites.
288
+ - `fuzz/` — five targets covering the whole untrusted-input surface: the spec
289
+ parser, the JSON request, the engine loop, the fill model and the data loader.
290
+
291
+ On top of those, **all ten languages** replay a shared, language-neutral golden
292
+ corpus — four OHLCV cases and five microstructure requests in `golden/` — and
293
+ assert equality with the Rust reference report. Since the streaming work, each
294
+ also replays the corpus **one bar at a time** and asserts the same report, so
295
+ the claim that a backtest and a live loop agree is pinned per language rather
296
+ than argued.
297
+
298
+ > **What "parity" means here, precisely.** The reports are compared
299
+ > **byte for byte**, not to a tolerance. That is possible because every binding
300
+ > calls the same Rust engine — the arithmetic is not reimplemented anywhere — and
301
+ > because the indicators the corpus names use only IEEE-754 arithmetic, which
302
+ > every conforming platform rounds identically. It is not free, though: a spec
303
+ > can name any indicator in the core, and some of those call a transcendental
304
+ > from the platform's math library (`ln`, `atan`, `exp` and friends). No
305
+ > mainstream libm rounds those correctly, and implementations differ in the last
306
+ > bit — the sibling indicator library measured a one-ulp difference on 24 of 67
307
+ > bars for a single indicator. A golden case built on one of those would have to
308
+ > compare to a relative tolerance instead. None currently does, and that is a
309
+ > property of the corpus worth keeping deliberately rather than by accident.
310
+
311
+ ## Requirements
312
+
313
+ The minimum supported version per language. The same engine kernel runs behind
314
+ every binding; the C-ABI bindings that compile on install — Go (cgo) and R
315
+ (`.Call`) — also need a C compiler, and Java runs with
316
+ `--enable-native-access=ALL-UNNAMED`.
317
+
318
+ | Language | Package | Minimum supported |
319
+ |----------|-------------------------------------------|----------------------------|
320
+ | Rust | crates.io · `wickra-backtest` | 1.86 (MSRV) |
321
+ | Python | PyPI · `wickra-backtest` (abi3 wheel) | 3.9 (tested through 3.13) |
322
+ | Node.js | npm · `wickra-backtest` (N-API 8) | 22 (tested on 22 · 24 LTS) |
323
+ | WASM | npm · `wickra-backtest-wasm` | any modern JS engine |
324
+ | C | `wickra_backtest.h` + library (releases) | C99 compiler |
325
+ | C++ | the C ABI + optional `wickra_backtest.hpp` | C++14 compiler |
326
+ | C# | NuGet · `Wickra.Backtest` | .NET 8 (`net8.0`) |
327
+ | Go | module · `wickra-lib/wickra-backtest-go` | Go 1.23 (cgo) |
328
+ | Java | Maven Central · `org.wickra:wickra-backtest` | Java 22 (FFM / Panama) |
329
+ | R | r-universe · `wickrabacktest` | R ≥ 2.10 (Rtools on Win.) |
330
+
331
+ ## Ecosystem
332
+
333
+ Part of the [Wickra](https://github.com/wickra-lib/wickra) family — each one a
334
+ data-driven core with a CLI and the same ten-language binding surface:
335
+
336
+ - [**wickra**](https://github.com/wickra-lib/wickra) — the core library: 514 O(1) streaming indicators across ten languages
337
+ - [**wickra-exchange**](https://github.com/wickra-lib/wickra-exchange) — unified market-data + execution across ten crypto exchanges
338
+ - [**wickra-backtest**](https://github.com/wickra-lib/wickra-backtest) — event-driven backtester over the Wickra core
339
+ - [**wickra-terminal**](https://github.com/wickra-lib/wickra-terminal) — the trading terminal: a TUI and a browser renderer over the stack
340
+ - [**wickra-screener**](https://github.com/wickra-lib/wickra-screener) — parallel multi-symbol screening over 514 streaming indicators
341
+ - [**wickra-xray**](https://github.com/wickra-lib/wickra-xray) — market-microstructure explorer: footprint, order-book heatmap, liquidation map, funding/OI divergence
342
+ - [**wickra-radar**](https://github.com/wickra-lib/wickra-radar) — perp-universe alert radar: OI delta, funding flip, book imbalance, liquidation clusters, OI/price divergence
343
+ - [**wickra-copilot**](https://github.com/wickra-lib/wickra-copilot) — local market copilot grounded in real order-book, liquidation and funding microstructure
344
+ - [**wickra-shazam**](https://github.com/wickra-lib/wickra-shazam) — match an asset's current microstructure fingerprint against its entire history
345
+
346
+ This project's own site is [backtest.wickra.org](https://backtest.wickra.org) and
347
+ its in-browser demo [backtest-live.wickra.org](https://backtest-live.wickra.org).
348
+ The indicator core it is built on documents itself at
349
+ [docs.wickra.org](https://docs.wickra.org).
350
+
351
+ ## Contributing
352
+
353
+ Contributions are welcome — issues, bug reports, ideas and pull requests all land
354
+ at <https://github.com/wickra-lib/wickra-backtest>. See
355
+ [CONTRIBUTING.md](CONTRIBUTING.md) for the orientation: the engine lives in
356
+ `crates/wickra-backtest-core`, every binding under `bindings/<lang>` keeps the
357
+ golden-corpus parity invariant, and `cargo fmt --all` +
358
+ `cargo clippy --workspace --all-targets --all-features -- -D warnings` are CI
359
+ gates. For larger changes, open an issue first.
360
+
361
+ ## Security
362
+
363
+ Found a security issue? **Please don't open a public issue.** Report it privately
364
+ via the repository's *Security* tab (*"Report a vulnerability"*) or email
365
+ **support@wickra.org**. Full policy: [SECURITY.md](SECURITY.md).
366
+
367
+ ## License
368
+
369
+ Licensed under either of
370
+
371
+ - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
372
+ <http://www.apache.org/licenses/LICENSE-2.0>)
373
+ - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
374
+
375
+ at your option. Use it, fork it, modify it, redistribute it — commercially or
376
+ not — file issues, send pull requests; all welcome.
377
+
378
+ ### Contribution
379
+
380
+ Unless you explicitly state otherwise, any contribution intentionally submitted
381
+ for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
382
+ dual licensed as above, without any additional terms or conditions.
383
+
384
+ ## Disclaimer
385
+
386
+ Not a trading system. Backtest results are deterministic transforms of the input
387
+ data — they are not financial advice and are not indicative of future
388
+ performance. Any use in a live trading context is at your own risk. The software
389
+ is provided **as is**, without warranty of any kind; see the license files for
390
+ the full terms.
391
+
392
+ ---
393
+
394
+ <p align="center">
395
+ Built on <a href="https://github.com/wickra-lib/wickra">Wickra</a>. If it saved you time, ⭐ the repo.
396
+ </p>
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "wickra-backtest-wasm",
3
+ "type": "module",
4
+ "collaborators": [
5
+ "kingchenc <support@wickra.org>"
6
+ ],
7
+ "description": "WebAssembly bindings for the wickra-backtest streaming backtester — backtest in the browser.",
8
+ "version": "0.1.0",
9
+ "license": "MIT OR Apache-2.0",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/wickra-lib/wickra-backtest"
13
+ },
14
+ "files": [
15
+ "wickra_backtest_wasm_bg.wasm",
16
+ "wickra_backtest_wasm.js",
17
+ "wickra_backtest_wasm_bg.js",
18
+ "wickra_backtest_wasm.d.ts",
19
+ "LICENSE-MIT",
20
+ "LICENSE-APACHE"
21
+ ],
22
+ "main": "wickra_backtest_wasm.js",
23
+ "homepage": "https://github.com/wickra-lib/wickra-backtest",
24
+ "types": "wickra_backtest_wasm.d.ts",
25
+ "sideEffects": [
26
+ "./wickra_backtest_wasm.js",
27
+ "./snippets/*"
28
+ ],
29
+ "author": "kingchenc <support@wickra.org>",
30
+ "bugs": {
31
+ "url": "https://github.com/wickra-lib/wickra-backtest/issues"
32
+ }
33
+ }
@@ -0,0 +1,80 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /**
5
+ * A backtest driven one bar at a time.
6
+ *
7
+ * `run` needs the whole series up front; this drives the same engine bar by
8
+ * bar, so a live loop and a backtest are the same code path -- point it at a
9
+ * websocket instead of an array and every value it reports was produced the way
10
+ * the backtest produced it.
11
+ *
12
+ * The method names match the Node binding deliberately. Both are consumed from
13
+ * JavaScript, and a developer moving between the npm package and this one
14
+ * should not have to relearn the surface; the reporting methods carry the
15
+ * `Json` suffix because, as in Node, they return JSON strings to parse.
16
+ *
17
+ * The handle owns its spec, so it carries no borrow across steps. `finishJson`
18
+ * consumes the run; `close` releases one without a report. Either leaves the
19
+ * object inert, and wasm-bindgen's generated `free` reclaims the memory.
20
+ */
21
+ export class StreamingBacktest {
22
+ free(): void;
23
+ [Symbol.dispose](): void;
24
+ /**
25
+ * Drop the run without producing a report. Idempotent.
26
+ */
27
+ close(): void;
28
+ /**
29
+ * The equity curve so far, as a JSON array.
30
+ */
31
+ equityJson(): string;
32
+ /**
33
+ * Close any open position and return the report JSON. Ends the run.
34
+ */
35
+ finishJson(): string;
36
+ /**
37
+ * The most recent equity point as JSON, or `null` before the first bar.
38
+ */
39
+ latestEquityJson(): string;
40
+ /**
41
+ * Build from a JSON strategy spec and starting capital.
42
+ */
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
+ /**
50
+ * Advance by one bar described as a request document:
51
+ * `{"candle": {...}, "feeds": {...}}`, where `feeds` optionally carries this
52
+ * bar's `reference` / `deriv` / `orderbook` / `trades` / `cross_section`.
53
+ * This is the only form that can drive a strategy reading a side feed.
54
+ */
55
+ stepJson(step_json: string): void;
56
+ /**
57
+ * Whether the run has been finished or closed.
58
+ */
59
+ readonly isFinished: boolean;
60
+ /**
61
+ * The number of closed trades so far.
62
+ */
63
+ readonly numTrades: number;
64
+ }
65
+
66
+ /**
67
+ * Run a backtest. Returns the report as a JSON string.
68
+ */
69
+ export function run(open: Float64Array, high: Float64Array, low: Float64Array, close: Float64Array, volume: Float64Array, time: Float64Array, spec_json: string, capital: number): string;
70
+
71
+ /**
72
+ * Run a backtest from a single JSON request bundling candles, spec and optional
73
+ * feeds. Returns the report as a JSON string.
74
+ */
75
+ export function run_json(request_json: string): string;
76
+
77
+ /**
78
+ * The crate version.
79
+ */
80
+ export function version(): string;
@@ -0,0 +1,9 @@
1
+ /* @ts-self-types="./wickra_backtest_wasm.d.ts" */
2
+ import * as wasm from "./wickra_backtest_wasm_bg.wasm";
3
+ import { __wbg_set_wasm } from "./wickra_backtest_wasm_bg.js";
4
+
5
+ __wbg_set_wasm(wasm);
6
+
7
+ export {
8
+ StreamingBacktest, run, run_json, version
9
+ } from "./wickra_backtest_wasm_bg.js";
@@ -0,0 +1,470 @@
1
+ /**
2
+ * A backtest driven one bar at a time.
3
+ *
4
+ * `run` needs the whole series up front; this drives the same engine bar by
5
+ * bar, so a live loop and a backtest are the same code path -- point it at a
6
+ * websocket instead of an array and every value it reports was produced the way
7
+ * the backtest produced it.
8
+ *
9
+ * The method names match the Node binding deliberately. Both are consumed from
10
+ * JavaScript, and a developer moving between the npm package and this one
11
+ * should not have to relearn the surface; the reporting methods carry the
12
+ * `Json` suffix because, as in Node, they return JSON strings to parse.
13
+ *
14
+ * The handle owns its spec, so it carries no borrow across steps. `finishJson`
15
+ * consumes the run; `close` releases one without a report. Either leaves the
16
+ * object inert, and wasm-bindgen's generated `free` reclaims the memory.
17
+ */
18
+ export class StreamingBacktest {
19
+ __destroy_into_raw() {
20
+ const ptr = this.__wbg_ptr;
21
+ this.__wbg_ptr = 0;
22
+ StreamingBacktestFinalization.unregister(this);
23
+ return ptr;
24
+ }
25
+ free() {
26
+ const ptr = this.__destroy_into_raw();
27
+ wasm.__wbg_streamingbacktest_free(ptr, 0);
28
+ }
29
+ /**
30
+ * Drop the run without producing a report. Idempotent.
31
+ */
32
+ close() {
33
+ wasm.streamingbacktest_close(this.__wbg_ptr);
34
+ }
35
+ /**
36
+ * The equity curve so far, as a JSON array.
37
+ * @returns {string}
38
+ */
39
+ equityJson() {
40
+ let deferred2_0;
41
+ let deferred2_1;
42
+ try {
43
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
44
+ wasm.streamingbacktest_equityJson(retptr, this.__wbg_ptr);
45
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
46
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
47
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
48
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
49
+ var ptr1 = r0;
50
+ var len1 = r1;
51
+ if (r3) {
52
+ ptr1 = 0; len1 = 0;
53
+ throw takeObject(r2);
54
+ }
55
+ deferred2_0 = ptr1;
56
+ deferred2_1 = len1;
57
+ return getStringFromWasm0(ptr1, len1);
58
+ } finally {
59
+ wasm.__wbindgen_add_to_stack_pointer(16);
60
+ wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
61
+ }
62
+ }
63
+ /**
64
+ * Close any open position and return the report JSON. Ends the run.
65
+ * @returns {string}
66
+ */
67
+ finishJson() {
68
+ let deferred2_0;
69
+ let deferred2_1;
70
+ try {
71
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
72
+ wasm.streamingbacktest_finishJson(retptr, this.__wbg_ptr);
73
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
74
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
75
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
76
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
77
+ var ptr1 = r0;
78
+ var len1 = r1;
79
+ if (r3) {
80
+ ptr1 = 0; len1 = 0;
81
+ throw takeObject(r2);
82
+ }
83
+ deferred2_0 = ptr1;
84
+ deferred2_1 = len1;
85
+ return getStringFromWasm0(ptr1, len1);
86
+ } finally {
87
+ wasm.__wbindgen_add_to_stack_pointer(16);
88
+ wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
89
+ }
90
+ }
91
+ /**
92
+ * Whether the run has been finished or closed.
93
+ * @returns {boolean}
94
+ */
95
+ get isFinished() {
96
+ const ret = wasm.streamingbacktest_isFinished(this.__wbg_ptr);
97
+ return ret !== 0;
98
+ }
99
+ /**
100
+ * The most recent equity point as JSON, or `null` before the first bar.
101
+ * @returns {string}
102
+ */
103
+ latestEquityJson() {
104
+ let deferred2_0;
105
+ let deferred2_1;
106
+ try {
107
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
108
+ wasm.streamingbacktest_latestEquityJson(retptr, this.__wbg_ptr);
109
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
110
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
111
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
112
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
113
+ var ptr1 = r0;
114
+ var len1 = r1;
115
+ if (r3) {
116
+ ptr1 = 0; len1 = 0;
117
+ throw takeObject(r2);
118
+ }
119
+ deferred2_0 = ptr1;
120
+ deferred2_1 = len1;
121
+ return getStringFromWasm0(ptr1, len1);
122
+ } finally {
123
+ wasm.__wbindgen_add_to_stack_pointer(16);
124
+ wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
125
+ }
126
+ }
127
+ /**
128
+ * Build from a JSON strategy spec and starting capital.
129
+ * @param {string} spec_json
130
+ * @param {number} capital
131
+ */
132
+ constructor(spec_json, capital) {
133
+ try {
134
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
135
+ const ptr0 = passStringToWasm0(spec_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
136
+ const len0 = WASM_VECTOR_LEN;
137
+ wasm.streamingbacktest_new(retptr, ptr0, len0, capital);
138
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
139
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
140
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
141
+ if (r2) {
142
+ throw takeObject(r1);
143
+ }
144
+ this.__wbg_ptr = r0;
145
+ StreamingBacktestFinalization.register(this, this.__wbg_ptr, this);
146
+ return this;
147
+ } finally {
148
+ wasm.__wbindgen_add_to_stack_pointer(16);
149
+ }
150
+ }
151
+ /**
152
+ * The number of closed trades so far.
153
+ * @returns {number}
154
+ */
155
+ get numTrades() {
156
+ try {
157
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
158
+ wasm.streamingbacktest_numTrades(retptr, this.__wbg_ptr);
159
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
160
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
161
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
162
+ if (r2) {
163
+ throw takeObject(r1);
164
+ }
165
+ return r0 >>> 0;
166
+ } finally {
167
+ wasm.__wbindgen_add_to_stack_pointer(16);
168
+ }
169
+ }
170
+ /**
171
+ * Advance by one OHLCV bar. `volume` defaults to 0 and `time` to the number
172
+ * of bars fed so far.
173
+ * @param {number} open
174
+ * @param {number} high
175
+ * @param {number} low
176
+ * @param {number} close
177
+ * @param {number | null} [volume]
178
+ * @param {number | null} [time]
179
+ */
180
+ step(open, high, low, close, volume, time) {
181
+ try {
182
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
183
+ wasm.streamingbacktest_step(retptr, this.__wbg_ptr, open, high, low, close, !isLikeNone(volume), isLikeNone(volume) ? 0 : volume, !isLikeNone(time), isLikeNone(time) ? 0 : time);
184
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
185
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
186
+ if (r1) {
187
+ throw takeObject(r0);
188
+ }
189
+ } finally {
190
+ wasm.__wbindgen_add_to_stack_pointer(16);
191
+ }
192
+ }
193
+ /**
194
+ * Advance by one bar described as a request document:
195
+ * `{"candle": {...}, "feeds": {...}}`, where `feeds` optionally carries this
196
+ * bar's `reference` / `deriv` / `orderbook` / `trades` / `cross_section`.
197
+ * This is the only form that can drive a strategy reading a side feed.
198
+ * @param {string} step_json
199
+ */
200
+ stepJson(step_json) {
201
+ try {
202
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
203
+ const ptr0 = passStringToWasm0(step_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
204
+ const len0 = WASM_VECTOR_LEN;
205
+ wasm.streamingbacktest_stepJson(retptr, this.__wbg_ptr, ptr0, len0);
206
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
207
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
208
+ if (r1) {
209
+ throw takeObject(r0);
210
+ }
211
+ } finally {
212
+ wasm.__wbindgen_add_to_stack_pointer(16);
213
+ }
214
+ }
215
+ }
216
+ if (Symbol.dispose) StreamingBacktest.prototype[Symbol.dispose] = StreamingBacktest.prototype.free;
217
+
218
+ /**
219
+ * Run a backtest. Returns the report as a JSON string.
220
+ * @param {Float64Array} open
221
+ * @param {Float64Array} high
222
+ * @param {Float64Array} low
223
+ * @param {Float64Array} close
224
+ * @param {Float64Array} volume
225
+ * @param {Float64Array} time
226
+ * @param {string} spec_json
227
+ * @param {number} capital
228
+ * @returns {string}
229
+ */
230
+ export function run(open, high, low, close, volume, time, spec_json, capital) {
231
+ let deferred9_0;
232
+ let deferred9_1;
233
+ try {
234
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
235
+ const ptr0 = passArrayF64ToWasm0(open, wasm.__wbindgen_export);
236
+ const len0 = WASM_VECTOR_LEN;
237
+ const ptr1 = passArrayF64ToWasm0(high, wasm.__wbindgen_export);
238
+ const len1 = WASM_VECTOR_LEN;
239
+ const ptr2 = passArrayF64ToWasm0(low, wasm.__wbindgen_export);
240
+ const len2 = WASM_VECTOR_LEN;
241
+ const ptr3 = passArrayF64ToWasm0(close, wasm.__wbindgen_export);
242
+ const len3 = WASM_VECTOR_LEN;
243
+ const ptr4 = passArrayF64ToWasm0(volume, wasm.__wbindgen_export);
244
+ const len4 = WASM_VECTOR_LEN;
245
+ const ptr5 = passArrayF64ToWasm0(time, wasm.__wbindgen_export);
246
+ const len5 = WASM_VECTOR_LEN;
247
+ const ptr6 = passStringToWasm0(spec_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
248
+ const len6 = WASM_VECTOR_LEN;
249
+ wasm.run(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6, capital);
250
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
251
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
252
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
253
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
254
+ var ptr8 = r0;
255
+ var len8 = r1;
256
+ if (r3) {
257
+ ptr8 = 0; len8 = 0;
258
+ throw takeObject(r2);
259
+ }
260
+ deferred9_0 = ptr8;
261
+ deferred9_1 = len8;
262
+ return getStringFromWasm0(ptr8, len8);
263
+ } finally {
264
+ wasm.__wbindgen_add_to_stack_pointer(16);
265
+ wasm.__wbindgen_export3(deferred9_0, deferred9_1, 1);
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Run a backtest from a single JSON request bundling candles, spec and optional
271
+ * feeds. Returns the report as a JSON string.
272
+ * @param {string} request_json
273
+ * @returns {string}
274
+ */
275
+ export function run_json(request_json) {
276
+ let deferred3_0;
277
+ let deferred3_1;
278
+ try {
279
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
280
+ const ptr0 = passStringToWasm0(request_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
281
+ const len0 = WASM_VECTOR_LEN;
282
+ wasm.run_json(retptr, ptr0, len0);
283
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
284
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
285
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
286
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
287
+ var ptr2 = r0;
288
+ var len2 = r1;
289
+ if (r3) {
290
+ ptr2 = 0; len2 = 0;
291
+ throw takeObject(r2);
292
+ }
293
+ deferred3_0 = ptr2;
294
+ deferred3_1 = len2;
295
+ return getStringFromWasm0(ptr2, len2);
296
+ } finally {
297
+ wasm.__wbindgen_add_to_stack_pointer(16);
298
+ wasm.__wbindgen_export3(deferred3_0, deferred3_1, 1);
299
+ }
300
+ }
301
+
302
+ /**
303
+ * The crate version.
304
+ * @returns {string}
305
+ */
306
+ export function version() {
307
+ let deferred1_0;
308
+ let deferred1_1;
309
+ try {
310
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
311
+ wasm.version(retptr);
312
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
313
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
314
+ deferred1_0 = r0;
315
+ deferred1_1 = r1;
316
+ return getStringFromWasm0(r0, r1);
317
+ } finally {
318
+ wasm.__wbindgen_add_to_stack_pointer(16);
319
+ wasm.__wbindgen_export3(deferred1_0, deferred1_1, 1);
320
+ }
321
+ }
322
+ export function __wbg_Error_92b29b0548f8b746(arg0, arg1) {
323
+ const ret = Error(getStringFromWasm0(arg0, arg1));
324
+ return addHeapObject(ret);
325
+ }
326
+ export function __wbg___wbindgen_throw_344f42d3211c4765(arg0, arg1) {
327
+ throw new Error(getStringFromWasm0(arg0, arg1));
328
+ }
329
+ const StreamingBacktestFinalization = (typeof FinalizationRegistry === 'undefined')
330
+ ? { register: () => {}, unregister: () => {} }
331
+ : new FinalizationRegistry(ptr => wasm.__wbg_streamingbacktest_free(ptr, 1));
332
+
333
+ function addHeapObject(obj) {
334
+ if (heap_next === heap.length) heap.push(heap.length + 1);
335
+ const idx = heap_next;
336
+ heap_next = heap[idx];
337
+
338
+ heap[idx] = obj;
339
+ return idx;
340
+ }
341
+
342
+ function dropObject(idx) {
343
+ if (idx < 1028) return;
344
+ heap[idx] = heap_next;
345
+ heap_next = idx;
346
+ }
347
+
348
+ let cachedDataViewMemory0 = null;
349
+ function getDataViewMemory0() {
350
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
351
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
352
+ }
353
+ return cachedDataViewMemory0;
354
+ }
355
+
356
+ let cachedFloat64ArrayMemory0 = null;
357
+ function getFloat64ArrayMemory0() {
358
+ if (cachedFloat64ArrayMemory0 === null || cachedFloat64ArrayMemory0.byteLength === 0) {
359
+ cachedFloat64ArrayMemory0 = new Float64Array(wasm.memory.buffer);
360
+ }
361
+ return cachedFloat64ArrayMemory0;
362
+ }
363
+
364
+ function getStringFromWasm0(ptr, len) {
365
+ return decodeText(ptr >>> 0, len);
366
+ }
367
+
368
+ let cachedUint8ArrayMemory0 = null;
369
+ function getUint8ArrayMemory0() {
370
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
371
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
372
+ }
373
+ return cachedUint8ArrayMemory0;
374
+ }
375
+
376
+ function getObject(idx) { return heap[idx]; }
377
+
378
+ let heap = new Array(1024).fill(undefined);
379
+ heap.push(undefined, null, true, false);
380
+
381
+ let heap_next = heap.length;
382
+
383
+ function isLikeNone(x) {
384
+ return x === undefined || x === null;
385
+ }
386
+
387
+ function passArrayF64ToWasm0(arg, malloc) {
388
+ const ptr = malloc(arg.length * 8, 8) >>> 0;
389
+ getFloat64ArrayMemory0().set(arg, ptr / 8);
390
+ WASM_VECTOR_LEN = arg.length;
391
+ return ptr;
392
+ }
393
+
394
+ function passStringToWasm0(arg, malloc, realloc) {
395
+ if (realloc === undefined) {
396
+ const buf = cachedTextEncoder.encode(arg);
397
+ const ptr = malloc(buf.length, 1) >>> 0;
398
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
399
+ WASM_VECTOR_LEN = buf.length;
400
+ return ptr;
401
+ }
402
+
403
+ let len = arg.length;
404
+ let ptr = malloc(len, 1) >>> 0;
405
+
406
+ const mem = getUint8ArrayMemory0();
407
+
408
+ let offset = 0;
409
+
410
+ for (; offset < len; offset++) {
411
+ const code = arg.charCodeAt(offset);
412
+ if (code > 0x7F) break;
413
+ mem[ptr + offset] = code;
414
+ }
415
+ if (offset !== len) {
416
+ if (offset !== 0) {
417
+ arg = arg.slice(offset);
418
+ }
419
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
420
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
421
+ const ret = cachedTextEncoder.encodeInto(arg, view);
422
+
423
+ offset += ret.written;
424
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
425
+ }
426
+
427
+ WASM_VECTOR_LEN = offset;
428
+ return ptr;
429
+ }
430
+
431
+ function takeObject(idx) {
432
+ const ret = getObject(idx);
433
+ dropObject(idx);
434
+ return ret;
435
+ }
436
+
437
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
438
+ cachedTextDecoder.decode();
439
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
440
+ let numBytesDecoded = 0;
441
+ function decodeText(ptr, len) {
442
+ numBytesDecoded += len;
443
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
444
+ cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
445
+ cachedTextDecoder.decode();
446
+ numBytesDecoded = len;
447
+ }
448
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
449
+ }
450
+
451
+ const cachedTextEncoder = new TextEncoder();
452
+
453
+ if (!('encodeInto' in cachedTextEncoder)) {
454
+ cachedTextEncoder.encodeInto = function (arg, view) {
455
+ const buf = cachedTextEncoder.encode(arg);
456
+ view.set(buf);
457
+ return {
458
+ read: arg.length,
459
+ written: buf.length
460
+ };
461
+ };
462
+ }
463
+
464
+ let WASM_VECTOR_LEN = 0;
465
+
466
+
467
+ let wasm;
468
+ export function __wbg_set_wasm(val) {
469
+ wasm = val;
470
+ }
Binary file