backtest-kit 2.3.2 → 2.3.3

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 CHANGED
@@ -9,6 +9,7 @@
9
9
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/tripolskypetr/backtest-kit)
10
10
  [![npm](https://img.shields.io/npm/v/backtest-kit.svg?style=flat-square)](https://npmjs.org/package/backtest-kit)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)]()
12
+ [![Build](https://github.com/tripolskypetr/backtest-kit/actions/workflows/webpack.yml/badge.svg)](https://github.com/tripolskypetr/backtest-kit/actions/workflows/webpack.yml)
12
13
 
13
14
  Build reliable trading systems: backtest on historical data, deploy live bots with recovery, and optimize strategies using LLMs like Ollama.
14
15
 
@@ -226,25 +227,41 @@ temporal time context to your strategies.
226
227
  - Adapter must return exactly `limit` candles
227
228
  - Sequential timestamps: `since + i * stepMs` for i = 0..limit-1
228
229
 
230
+ **How `since` is calculated from `when`:**
231
+ - `when` = current execution context time (from AsyncLocalStorage)
232
+ - `alignedWhen` = `Math.floor(when / stepMs) * stepMs` (aligned down to interval boundary)
233
+ - `since` = `alignedWhen - limit * stepMs` (go back `limit` candles from aligned when)
234
+
235
+ **Boundary semantics (inclusive/exclusive):**
236
+ - `since` is always **inclusive** — first candle has `timestamp === since`
237
+ - Exactly `limit` candles are returned
238
+ - Last candle has `timestamp === since + (limit - 1) * stepMs` — **inclusive**
239
+ - For `getCandles`: `alignedWhen` is **exclusive** — candle at that timestamp is NOT included (it's a pending/incomplete candle)
240
+ - For `getRawCandles`: `eDate` is **exclusive** — candle at that timestamp is NOT included (it's a pending/incomplete candle)
241
+ - For `getNextCandles`: `alignedWhen` is **inclusive** — first candle starts at `alignedWhen` (it's the current candle for backtest, already closed in historical data)
242
+
229
243
  - `getCandles(symbol, interval, limit)` - Returns exactly `limit` candles
230
244
  - Aligns `when` down to interval boundary
231
245
  - Calculates `since = alignedWhen - limit * stepMs`
232
- - First candle.timestamp === since
233
- - Example: `getCandles("BTCUSDT", "1m", 100)` returns 100 candles ending at aligned when
246
+ - **since — inclusive**, first candle.timestamp === since
247
+ - **alignedWhen exclusive**, candle at alignedWhen is NOT returned
248
+ - Range: `[since, alignedWhen)` — half-open interval
249
+ - Example: `getCandles("BTCUSDT", "1m", 100)` returns 100 candles ending before aligned when
234
250
 
235
251
  - `getNextCandles(symbol, interval, limit)` - Returns exactly `limit` candles (backtest only)
236
252
  - Aligns `when` down to interval boundary
237
253
  - `since = alignedWhen` (starts from aligned when, going forward)
238
- - First candle.timestamp === since
254
+ - **since — inclusive**, first candle.timestamp === since
255
+ - Range: `[alignedWhen, alignedWhen + limit * stepMs)` — half-open interval
239
256
  - Throws error in live mode to prevent look-ahead bias
240
- - Example: `getNextCandles("BTCUSDT", "1m", 10)` returns next 10 candles after aligned when
257
+ - Example: `getNextCandles("BTCUSDT", "1m", 10)` returns next 10 candles starting from aligned when
241
258
 
242
259
  - `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
260
+ - `(limit)` - since = alignedWhen - limit * stepMs, range `[since, alignedWhen)`
261
+ - `(limit, sDate)` - since = align(sDate), returns `limit` candles forward, range `[since, since + limit * stepMs)`
262
+ - `(limit, undefined, eDate)` - since = align(eDate) - limit * stepMs, **eDate — exclusive**, range `[since, eDate)`
263
+ - `(undefined, sDate, eDate)` - since = align(sDate), limit calculated from range, **sDate — inclusive, eDate — exclusive**, range `[sDate, eDate)`
264
+ - `(limit, sDate, eDate)` - since = align(sDate), returns `limit` candles, **sDate — inclusive**
248
265
  - All combinations respect look-ahead bias protection (eDate/endTime <= when)
249
266
 
250
267
  **Persistent Cache:**
@@ -293,6 +310,8 @@ since = alignedWhen - 4 * stepMs
293
310
  // [3] timestamp = 1704066300000 (23:45)
294
311
  ```
295
312
 
313
+ **Pending candle exclusion:** The candle at `00:00:00` (alignedWhen) is NOT included in the result. At `when=00:12:00`, this candle covers the period `[00:00, 00:15)` and is still open (pending). Pending candles have incomplete OHLCV data that would distort technical indicators. Only fully closed candles are returned.
314
+
296
315
  **Validation is applied consistently across:**
297
316
  - ✅ `getCandles()` - validates first timestamp and count
298
317
  - ✅ `getNextCandles()` - validates first timestamp and count