backtest-kit 2.2.26 → 2.3.1
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 +86 -3
- package/build/index.cjs +241 -28
- package/build/index.mjs +241 -29
- package/package.json +1 -1
- package/types.d.ts +40 -4
package/README.md
CHANGED
|
@@ -205,14 +205,97 @@ 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
|
|
218
|
+
- `stepMs` = interval duration (e.g., 60000ms for "1m")
|
|
219
|
+
- Candle close time = `timestamp + stepMs`
|
|
220
|
+
|
|
221
|
+
The candle is included if: `timestamp + stepMs < upperBoundary`
|
|
222
|
+
|
|
223
|
+
- `getCandles(symbol, interval, limit)` - Returns data in range `(when - limit*interval, when)`
|
|
224
|
+
- Fetches historical candles backwards from execution context time
|
|
225
|
+
- Only fully closed candles are included (candle must close before `when`)
|
|
226
|
+
- Lower bound: `candle.timestamp > sinceTimestamp` (exclusive)
|
|
227
|
+
- Upper bound: `candle.timestamp + stepMs < when` (exclusive)
|
|
228
|
+
- Example: `getCandles("BTCUSDT", "1m", 100)` returns 100 candles ending before current time
|
|
229
|
+
|
|
230
|
+
- `getNextCandles(symbol, interval, limit)` - Returns data in range `(when, when + limit*interval)`
|
|
231
|
+
- Fetches future candles forwards from execution context time (backtest only)
|
|
232
|
+
- Only fully closed candles are included
|
|
233
|
+
- Lower bound: `candle.timestamp > when` (exclusive)
|
|
234
|
+
- Upper bound: `candle.timestamp + stepMs < endTime` (exclusive)
|
|
235
|
+
- Throws error in live mode to prevent look-ahead bias
|
|
236
|
+
- Example: `getNextCandles("BTCUSDT", "1m", 10)` returns next 10 candles after current time
|
|
237
|
+
|
|
238
|
+
- `getRawCandles(symbol, interval, limit?, sDate?, eDate?)` - Flexible parameter combinations:
|
|
239
|
+
- `(limit)` - Returns data in range `(now - limit*interval, now)`
|
|
240
|
+
- `(limit, sDate)` - Returns data in range `(sDate, sDate + limit*interval)`
|
|
241
|
+
- `(limit, undefined, eDate)` - Returns data in range `(eDate - limit*interval, eDate)`
|
|
242
|
+
- `(undefined, sDate, eDate)` - Returns data in range `(sDate, eDate)`, limit calculated from range
|
|
243
|
+
- `(limit, sDate, eDate)` - Returns data in range `(sDate, eDate)`, limit used only for fetch size
|
|
244
|
+
- All combinations use: `candle.timestamp > sDate && candle.timestamp + stepMs < eDate`
|
|
245
|
+
- All combinations respect exclusive boundaries and look-ahead bias protection
|
|
246
|
+
|
|
247
|
+
**Persistent Cache:**
|
|
248
|
+
- Candle cache uses identical boundary semantics: `timestamp > sinceTimestamp && timestamp + stepMs < untilTimestamp`
|
|
249
|
+
- Cache and runtime filters are synchronized to prevent inconsistencies
|
|
250
|
+
- Cache returns only candles that match the requested time range exactly
|
|
251
|
+
|
|
252
|
+
</details>
|
|
253
|
+
|
|
254
|
+
#### Boundary Semantics:
|
|
255
|
+
|
|
256
|
+
All methods use **strict exclusive boundaries** - candles at exact boundary times are excluded. This prevents accidental inclusion of boundary conditions in backtest logic and ensures consistent behavior across cache and runtime.
|
|
257
|
+
|
|
258
|
+
According to this `timestamp` of a candle in backtest-kit is exactly the `openTime`, not ~~`closeTime`~~
|
|
259
|
+
|
|
260
|
+
**Key principle:** A candle is included only if it **fully closed** before the upper boundary.
|
|
261
|
+
|
|
262
|
+
### 🔬 Technical Details: The `+ stepMs` Check
|
|
263
|
+
|
|
264
|
+
**Why check `candle.timestamp + stepMs < upperBoundary` instead of just `candle.timestamp < upperBoundary`?**
|
|
265
|
+
|
|
266
|
+
Because a candle's **timestamp is when it opens**, not when it closes:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// 1-minute candle example:
|
|
270
|
+
timestamp = 1000 // Candle opens at 1000ms
|
|
271
|
+
stepMs = 60000 // Duration: 60 seconds
|
|
272
|
+
// Candle closes at: 1000 + 60000 = 61000ms
|
|
273
|
+
|
|
274
|
+
// Without + stepMs (WRONG):
|
|
275
|
+
candle.timestamp < 61000
|
|
276
|
+
1000 < 61000 // TRUE - includes candle that hasn't finished yet!
|
|
277
|
+
|
|
278
|
+
// With + stepMs (CORRECT):
|
|
279
|
+
candle.timestamp + stepMs < 61000
|
|
280
|
+
1000 + 60000 < 61000
|
|
281
|
+
61000 < 61000 // FALSE - correctly excludes unclosed candle
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**This check is applied consistently across:**
|
|
285
|
+
- ✅ `getCandles()` filtering
|
|
286
|
+
- ✅ `getNextCandles()` filtering
|
|
287
|
+
- ✅ `getRawCandles()` filtering (all parameter combinations)
|
|
288
|
+
- ✅ Cache read operations
|
|
289
|
+
- ✅ Cache write operations
|
|
290
|
+
|
|
291
|
+
**Result:** Zero chance of including incomplete or "forming" candles in your strategy logic.
|
|
210
292
|
|
|
211
293
|
### 💭 What this means:
|
|
212
294
|
- `getCandles()` always returns data UP TO the current backtest timestamp using `async_hooks`
|
|
213
295
|
- Multi-timeframe data is automatically synchronized
|
|
214
|
-
- **Impossible to introduce look-ahead bias**
|
|
296
|
+
- **Impossible to introduce look-ahead bias** - all time boundaries are enforced
|
|
215
297
|
- Same code works in both backtest and live modes
|
|
298
|
+
- Boundary semantics prevent edge cases in signal generation
|
|
216
299
|
|
|
217
300
|
|
|
218
301
|
## 🧠 Two Ways to Run the Engine
|