@tradejs/core 3.1.29-beta.280 → 3.1.29-beta.281

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.
@@ -11299,6 +11299,79 @@ var randomUuid = () => {
11299
11299
  var createIdGenerator = (generateUuid = randomUuid) => (length = 12) => generateUuid().slice(-length);
11300
11300
  var uuid = createIdGenerator();
11301
11301
 
11302
+ // src/utils/strategyHelpers/closedCandles.ts
11303
+ var SOURCE_MS = 15 * 6e4;
11304
+ var MAX_ROWS = 1536;
11305
+ var createClosedCandlesReader = ({
11306
+ connector,
11307
+ symbol,
11308
+ interval,
11309
+ env,
11310
+ getDecisionPriceContext
11311
+ }) => async (request) => {
11312
+ const executionMinutes = Number(interval);
11313
+ if (!Number.isInteger(executionMinutes) || executionMinutes <= 0) {
11314
+ throw new Error(
11315
+ "getClosedCandles requires a fixed minute execution interval"
11316
+ );
11317
+ }
11318
+ const { candle } = getDecisionPriceContext();
11319
+ const asOf = candle.timestamp + executionMinutes * 6e4;
11320
+ const { start, end, limit } = request;
11321
+ if (request.interval !== "15" || !Number.isSafeInteger(asOf) || !Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || start % SOURCE_MS !== 0 || end % SOURCE_MS !== 0 || end <= start || end > asOf || !Number.isInteger(limit) || limit < 1 || limit > MAX_ROWS || (end - start) / SOURCE_MS > limit) {
11322
+ throw new Error(
11323
+ "Invalid or non-causal getClosedCandles window (maximum 1536 slots)"
11324
+ );
11325
+ }
11326
+ const rows = await connector.kline({
11327
+ symbol,
11328
+ interval: "15",
11329
+ start,
11330
+ // Connector end is inclusive; the public window is half-open.
11331
+ end: end - 1,
11332
+ cacheOnly: env === "BACKTEST" || env === "PARITY",
11333
+ silent: true
11334
+ });
11335
+ const byTimestamp = /* @__PURE__ */ new Map();
11336
+ for (const row of rows) {
11337
+ const timestamp = row.timestamp;
11338
+ if (timestamp < start || timestamp >= end || timestamp + SOURCE_MS > asOf)
11339
+ continue;
11340
+ if (!Number.isSafeInteger(timestamp) || timestamp % SOURCE_MS !== 0 || ![
11341
+ row.open,
11342
+ row.high,
11343
+ row.low,
11344
+ row.close,
11345
+ row.volume,
11346
+ row.turnover
11347
+ ].every(Number.isFinite) || row.high < row.low || row.open < row.low || row.open > row.high || row.close < row.low || row.close > row.high || row.volume < 0 || row.turnover < 0 || byTimestamp.has(timestamp)) {
11348
+ throw new Error("Invalid or duplicate getClosedCandles source row");
11349
+ }
11350
+ byTimestamp.set(timestamp, { ...row });
11351
+ }
11352
+ const candles = [];
11353
+ const missingRanges = [];
11354
+ for (let timestamp = start; timestamp < end; timestamp += SOURCE_MS) {
11355
+ const row = byTimestamp.get(timestamp);
11356
+ if (row) candles.push(row);
11357
+ else {
11358
+ const last = missingRanges[missingRanges.length - 1];
11359
+ if (last?.end === timestamp) last.end += SOURCE_MS;
11360
+ else
11361
+ missingRanges.push({ start: timestamp, end: timestamp + SOURCE_MS });
11362
+ }
11363
+ }
11364
+ return {
11365
+ interval: "15",
11366
+ start,
11367
+ end,
11368
+ asOf,
11369
+ candles,
11370
+ complete: missingRanges.length === 0,
11371
+ missingRanges
11372
+ };
11373
+ };
11374
+
11302
11375
  // src/utils/strategyHelpers/signalBuilders.ts
11303
11376
  var COMPACT_MTF_CANDLE_LIMIT = 3;
11304
11377
  var isRecordLike = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -12150,7 +12223,15 @@ var createStrategyAPI = ({
12150
12223
  };
12151
12224
  };
12152
12225
  const getDecisionPriceContext = async () => resolveDecisionPriceContext();
12226
+ const getClosedCandles = createClosedCandlesReader({
12227
+ connector,
12228
+ symbol,
12229
+ interval,
12230
+ env,
12231
+ getDecisionPriceContext: resolveDecisionPriceContext
12232
+ });
12153
12233
  return {
12234
+ getClosedCandles,
12154
12235
  skip: (code) => ({ kind: "skip", code }),
12155
12236
  entry: async ({
12156
12237
  code,
@@ -560,6 +560,79 @@ var createStrategyStateControllerFactory = ({
560
560
  };
561
561
  };
562
562
 
563
+ // src/utils/strategyHelpers/closedCandles.ts
564
+ var SOURCE_MS = 15 * 6e4;
565
+ var MAX_ROWS = 1536;
566
+ var createClosedCandlesReader = ({
567
+ connector,
568
+ symbol,
569
+ interval,
570
+ env,
571
+ getDecisionPriceContext
572
+ }) => async (request) => {
573
+ const executionMinutes = Number(interval);
574
+ if (!Number.isInteger(executionMinutes) || executionMinutes <= 0) {
575
+ throw new Error(
576
+ "getClosedCandles requires a fixed minute execution interval"
577
+ );
578
+ }
579
+ const { candle } = getDecisionPriceContext();
580
+ const asOf = candle.timestamp + executionMinutes * 6e4;
581
+ const { start, end, limit } = request;
582
+ if (request.interval !== "15" || !Number.isSafeInteger(asOf) || !Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || start % SOURCE_MS !== 0 || end % SOURCE_MS !== 0 || end <= start || end > asOf || !Number.isInteger(limit) || limit < 1 || limit > MAX_ROWS || (end - start) / SOURCE_MS > limit) {
583
+ throw new Error(
584
+ "Invalid or non-causal getClosedCandles window (maximum 1536 slots)"
585
+ );
586
+ }
587
+ const rows = await connector.kline({
588
+ symbol,
589
+ interval: "15",
590
+ start,
591
+ // Connector end is inclusive; the public window is half-open.
592
+ end: end - 1,
593
+ cacheOnly: env === "BACKTEST" || env === "PARITY",
594
+ silent: true
595
+ });
596
+ const byTimestamp = /* @__PURE__ */ new Map();
597
+ for (const row of rows) {
598
+ const timestamp = row.timestamp;
599
+ if (timestamp < start || timestamp >= end || timestamp + SOURCE_MS > asOf)
600
+ continue;
601
+ if (!Number.isSafeInteger(timestamp) || timestamp % SOURCE_MS !== 0 || ![
602
+ row.open,
603
+ row.high,
604
+ row.low,
605
+ row.close,
606
+ row.volume,
607
+ row.turnover
608
+ ].every(Number.isFinite) || row.high < row.low || row.open < row.low || row.open > row.high || row.close < row.low || row.close > row.high || row.volume < 0 || row.turnover < 0 || byTimestamp.has(timestamp)) {
609
+ throw new Error("Invalid or duplicate getClosedCandles source row");
610
+ }
611
+ byTimestamp.set(timestamp, { ...row });
612
+ }
613
+ const candles = [];
614
+ const missingRanges = [];
615
+ for (let timestamp = start; timestamp < end; timestamp += SOURCE_MS) {
616
+ const row = byTimestamp.get(timestamp);
617
+ if (row) candles.push(row);
618
+ else {
619
+ const last = missingRanges[missingRanges.length - 1];
620
+ if (last?.end === timestamp) last.end += SOURCE_MS;
621
+ else
622
+ missingRanges.push({ start: timestamp, end: timestamp + SOURCE_MS });
623
+ }
624
+ }
625
+ return {
626
+ interval: "15",
627
+ start,
628
+ end,
629
+ asOf,
630
+ candles,
631
+ complete: missingRanges.length === 0,
632
+ missingRanges
633
+ };
634
+ };
635
+
563
636
  // src/utils/strategyHelpers/signalBuilders.ts
564
637
  var COMPACT_MTF_CANDLE_LIMIT = 3;
565
638
  var isRecordLike = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -1411,7 +1484,15 @@ var createStrategyAPI = ({
1411
1484
  };
1412
1485
  };
1413
1486
  const getDecisionPriceContext = async () => resolveDecisionPriceContext();
1487
+ const getClosedCandles = createClosedCandlesReader({
1488
+ connector,
1489
+ symbol,
1490
+ interval,
1491
+ env,
1492
+ getDecisionPriceContext: resolveDecisionPriceContext
1493
+ });
1414
1494
  return {
1495
+ getClosedCandles,
1415
1496
  skip: (code) => ({ kind: "skip", code }),
1416
1497
  entry: async ({
1417
1498
  code,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/core",
3
- "version": "3.1.29-beta.280",
3
+ "version": "3.1.29-beta.281",
4
4
  "description": "MIT-licensed browser-safe API for TradeJS config, strategy authoring, figures, and shared helpers.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -138,7 +138,7 @@
138
138
  }
139
139
  },
140
140
  "dependencies": {
141
- "@tradejs/types": "^3.1.29-beta.280",
141
+ "@tradejs/types": "^3.1.29-beta.281",
142
142
  "date-fns": "^3.6.0",
143
143
  "fast-technical-indicators": "^1.1.4",
144
144
  "klinecharts": "10.0.0-alpha9",