backtest-kit 2.2.26 → 2.3.2
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 +98 -3
- package/build/index.cjs +462 -142
- package/build/index.mjs +463 -144
- package/package.json +3 -2
- package/types.d.ts +64 -6
package/README.md
CHANGED
|
@@ -205,14 +205,109 @@ Backtest Kit is **not a data-processing library** - it is a **time execution eng
|
|
|
205
205
|
|
|
206
206
|
### 🔍 How getCandles Works
|
|
207
207
|
|
|
208
|
-
backtest-kit uses Node.js `AsyncLocalStorage` to automatically provide
|
|
209
|
-
temporal time context to your strategies.
|
|
208
|
+
backtest-kit uses Node.js `AsyncLocalStorage` to automatically provide
|
|
209
|
+
temporal time context to your strategies.
|
|
210
|
+
|
|
211
|
+
<details>
|
|
212
|
+
<summary>
|
|
213
|
+
The Math
|
|
214
|
+
</summary>
|
|
215
|
+
|
|
216
|
+
For a candle with:
|
|
217
|
+
- `timestamp` = candle open time (openTime)
|
|
218
|
+
- `stepMs` = interval duration (e.g., 60000ms for "1m")
|
|
219
|
+
- Candle close time = `timestamp + stepMs`
|
|
220
|
+
|
|
221
|
+
**Alignment:** All timestamps are aligned down to interval boundary.
|
|
222
|
+
For example, for 15m interval: 00:17 → 00:15, 00:44 → 00:30
|
|
223
|
+
|
|
224
|
+
**Adapter contract:**
|
|
225
|
+
- First candle.timestamp must equal aligned `since`
|
|
226
|
+
- Adapter must return exactly `limit` candles
|
|
227
|
+
- Sequential timestamps: `since + i * stepMs` for i = 0..limit-1
|
|
228
|
+
|
|
229
|
+
- `getCandles(symbol, interval, limit)` - Returns exactly `limit` candles
|
|
230
|
+
- Aligns `when` down to interval boundary
|
|
231
|
+
- Calculates `since = alignedWhen - limit * stepMs`
|
|
232
|
+
- First candle.timestamp === since
|
|
233
|
+
- Example: `getCandles("BTCUSDT", "1m", 100)` returns 100 candles ending at aligned when
|
|
234
|
+
|
|
235
|
+
- `getNextCandles(symbol, interval, limit)` - Returns exactly `limit` candles (backtest only)
|
|
236
|
+
- Aligns `when` down to interval boundary
|
|
237
|
+
- `since = alignedWhen` (starts from aligned when, going forward)
|
|
238
|
+
- First candle.timestamp === since
|
|
239
|
+
- Throws error in live mode to prevent look-ahead bias
|
|
240
|
+
- Example: `getNextCandles("BTCUSDT", "1m", 10)` returns next 10 candles after aligned when
|
|
241
|
+
|
|
242
|
+
- `getRawCandles(symbol, interval, limit?, sDate?, eDate?)` - Flexible parameter combinations:
|
|
243
|
+
- `(limit)` - since = alignedWhen - limit * stepMs
|
|
244
|
+
- `(limit, sDate)` - since = align(sDate), returns `limit` candles forward
|
|
245
|
+
- `(limit, undefined, eDate)` - since = align(eDate) - limit * stepMs
|
|
246
|
+
- `(undefined, sDate, eDate)` - since = align(sDate), limit calculated from range
|
|
247
|
+
- `(limit, sDate, eDate)` - since = align(sDate), returns `limit` candles
|
|
248
|
+
- All combinations respect look-ahead bias protection (eDate/endTime <= when)
|
|
249
|
+
|
|
250
|
+
**Persistent Cache:**
|
|
251
|
+
- Cache lookup calculates expected timestamps: `since + i * stepMs` for i = 0..limit-1
|
|
252
|
+
- Returns all candles if found, null if any missing (cache miss)
|
|
253
|
+
- Cache and runtime use identical timestamp calculation logic
|
|
254
|
+
|
|
255
|
+
</details>
|
|
256
|
+
|
|
257
|
+
#### Candle Timestamp Convention:
|
|
258
|
+
|
|
259
|
+
According to this `timestamp` of a candle in backtest-kit is exactly the `openTime`, not ~~`closeTime`~~
|
|
260
|
+
|
|
261
|
+
**Key principles:**
|
|
262
|
+
- All timestamps are aligned down to interval boundary
|
|
263
|
+
- First candle.timestamp must equal aligned `since`
|
|
264
|
+
- Adapter must return exactly `limit` candles
|
|
265
|
+
- Sequential timestamps: `since + i * stepMs`
|
|
266
|
+
|
|
267
|
+
### 🔬 Technical Details: Timestamp Alignment
|
|
268
|
+
|
|
269
|
+
**Why align timestamps to interval boundaries?**
|
|
270
|
+
|
|
271
|
+
Because candle APIs return data starting from exact interval boundaries:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// 15-minute interval example:
|
|
275
|
+
when = 1704067920000 // 00:12:00
|
|
276
|
+
step = 15 // 15 minutes
|
|
277
|
+
stepMs = 15 * 60000 // 900000ms
|
|
278
|
+
|
|
279
|
+
// Alignment: round down to nearest interval boundary
|
|
280
|
+
alignedWhen = Math.floor(when / stepMs) * stepMs
|
|
281
|
+
// = Math.floor(1704067920000 / 900000) * 900000
|
|
282
|
+
// = 1704067200000 (00:00:00)
|
|
283
|
+
|
|
284
|
+
// Calculate since for 4 candles backwards:
|
|
285
|
+
since = alignedWhen - 4 * stepMs
|
|
286
|
+
// = 1704067200000 - 4 * 900000
|
|
287
|
+
// = 1704063600000 (23:00:00 previous day)
|
|
288
|
+
|
|
289
|
+
// Expected candles:
|
|
290
|
+
// [0] timestamp = 1704063600000 (23:00)
|
|
291
|
+
// [1] timestamp = 1704064500000 (23:15)
|
|
292
|
+
// [2] timestamp = 1704065400000 (23:30)
|
|
293
|
+
// [3] timestamp = 1704066300000 (23:45)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Validation is applied consistently across:**
|
|
297
|
+
- ✅ `getCandles()` - validates first timestamp and count
|
|
298
|
+
- ✅ `getNextCandles()` - validates first timestamp and count
|
|
299
|
+
- ✅ `getRawCandles()` - validates first timestamp and count
|
|
300
|
+
- ✅ Cache read - calculates exact expected timestamps
|
|
301
|
+
- ✅ Cache write - stores validated candles
|
|
302
|
+
|
|
303
|
+
**Result:** Deterministic candle retrieval with exact timestamp matching.
|
|
210
304
|
|
|
211
305
|
### 💭 What this means:
|
|
212
306
|
- `getCandles()` always returns data UP TO the current backtest timestamp using `async_hooks`
|
|
213
307
|
- Multi-timeframe data is automatically synchronized
|
|
214
|
-
- **Impossible to introduce look-ahead bias**
|
|
308
|
+
- **Impossible to introduce look-ahead bias** - all time boundaries are enforced
|
|
215
309
|
- Same code works in both backtest and live modes
|
|
310
|
+
- Boundary semantics prevent edge cases in signal generation
|
|
216
311
|
|
|
217
312
|
|
|
218
313
|
## 🧠 Two Ways to Run the Engine
|