@tradejs/core 3.1.29-beta.280 → 3.1.29-beta.282
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/dist/strategies.js +81 -0
- package/dist/strategies.mjs +81 -0
- package/package.json +2 -2
package/dist/strategies.js
CHANGED
|
@@ -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,
|
package/dist/strategies.mjs
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "3.1.29-beta.282",
|
|
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.
|
|
141
|
+
"@tradejs/types": "^3.1.29-beta.282",
|
|
142
142
|
"date-fns": "^3.6.0",
|
|
143
143
|
"fast-technical-indicators": "^1.1.4",
|
|
144
144
|
"klinecharts": "10.0.0-alpha9",
|