backtest-kit 9.0.0 → 9.0.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/build/index.cjs +429 -208
- package/build/index.mjs +429 -209
- package/package.json +1 -1
- package/types.d.ts +144 -45
package/build/index.cjs
CHANGED
|
@@ -938,7 +938,7 @@ const LOGGER_SERVICE$7 = new LoggerService();
|
|
|
938
938
|
/** Symbol key for the singleshot waitForInit function on PersistBase instances. */
|
|
939
939
|
const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
|
|
940
940
|
// Calculate step in milliseconds for candle close time validation
|
|
941
|
-
const INTERVAL_MINUTES$
|
|
941
|
+
const INTERVAL_MINUTES$a = {
|
|
942
942
|
"1m": 1,
|
|
943
943
|
"3m": 3,
|
|
944
944
|
"5m": 5,
|
|
@@ -951,7 +951,7 @@ const INTERVAL_MINUTES$9 = {
|
|
|
951
951
|
"8h": 480,
|
|
952
952
|
"1d": 1440,
|
|
953
953
|
};
|
|
954
|
-
const MS_PER_MINUTE$
|
|
954
|
+
const MS_PER_MINUTE$8 = 60000;
|
|
955
955
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
|
|
956
956
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
|
|
957
957
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistSignalUtils.writeSignalData";
|
|
@@ -2276,7 +2276,7 @@ class PersistCandleInstance {
|
|
|
2276
2276
|
* @returns Promise resolving to candles in order, or null on cache miss
|
|
2277
2277
|
*/
|
|
2278
2278
|
async readCandlesData(limit, sinceTimestamp, _untilTimestamp) {
|
|
2279
|
-
const stepMs = INTERVAL_MINUTES$
|
|
2279
|
+
const stepMs = INTERVAL_MINUTES$a[this.interval] * MS_PER_MINUTE$8;
|
|
2280
2280
|
const cachedCandles = [];
|
|
2281
2281
|
for (let i = 0; i < limit; i++) {
|
|
2282
2282
|
const expectedTimestamp = sinceTimestamp + i * stepMs;
|
|
@@ -2311,7 +2311,7 @@ class PersistCandleInstance {
|
|
|
2311
2311
|
* @returns Promise that resolves when all writes are complete
|
|
2312
2312
|
*/
|
|
2313
2313
|
async writeCandlesData(candles) {
|
|
2314
|
-
const stepMs = INTERVAL_MINUTES$
|
|
2314
|
+
const stepMs = INTERVAL_MINUTES$a[this.interval] * MS_PER_MINUTE$8;
|
|
2315
2315
|
const now = Date.now();
|
|
2316
2316
|
for (const candle of candles) {
|
|
2317
2317
|
const candleCloseTime = candle.timestamp + stepMs;
|
|
@@ -3197,12 +3197,12 @@ class PersistMeasureUtils {
|
|
|
3197
3197
|
* @param key - Cache key within the bucket
|
|
3198
3198
|
* @returns Promise that resolves when write is complete
|
|
3199
3199
|
*/
|
|
3200
|
-
this.writeMeasureData = async (data, bucket, key) => {
|
|
3200
|
+
this.writeMeasureData = async (data, bucket, key, when) => {
|
|
3201
3201
|
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
|
|
3202
3202
|
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
3203
3203
|
const instance = this.getMeasureStorage(bucket);
|
|
3204
3204
|
await instance.waitForInit(isInitial);
|
|
3205
|
-
return instance.writeMeasureData(data, key);
|
|
3205
|
+
return instance.writeMeasureData(data, key, when);
|
|
3206
3206
|
};
|
|
3207
3207
|
/**
|
|
3208
3208
|
* Soft-deletes a measure entry in the given bucket by setting `removed: true`.
|
|
@@ -3438,12 +3438,12 @@ class PersistIntervalUtils {
|
|
|
3438
3438
|
* @param key - Marker key within the bucket
|
|
3439
3439
|
* @returns Promise that resolves when write is complete
|
|
3440
3440
|
*/
|
|
3441
|
-
this.writeIntervalData = async (data, bucket, key) => {
|
|
3441
|
+
this.writeIntervalData = async (data, bucket, key, when) => {
|
|
3442
3442
|
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
|
|
3443
3443
|
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
3444
3444
|
const instance = this.getIntervalStorage(bucket);
|
|
3445
3445
|
await instance.waitForInit(isInitial);
|
|
3446
|
-
return instance.writeIntervalData(data, key);
|
|
3446
|
+
return instance.writeIntervalData(data, key, when);
|
|
3447
3447
|
};
|
|
3448
3448
|
/**
|
|
3449
3449
|
* Soft-deletes a marker in the given bucket by setting `removed: true`.
|
|
@@ -4629,8 +4629,8 @@ class CandleUtils {
|
|
|
4629
4629
|
}
|
|
4630
4630
|
const Candle = new CandleUtils();
|
|
4631
4631
|
|
|
4632
|
-
const MS_PER_MINUTE$
|
|
4633
|
-
const INTERVAL_MINUTES$
|
|
4632
|
+
const MS_PER_MINUTE$7 = 60000;
|
|
4633
|
+
const INTERVAL_MINUTES$9 = {
|
|
4634
4634
|
"1m": 1,
|
|
4635
4635
|
"3m": 3,
|
|
4636
4636
|
"5m": 5,
|
|
@@ -4661,7 +4661,7 @@ const INTERVAL_MINUTES$8 = {
|
|
|
4661
4661
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
4662
4662
|
*/
|
|
4663
4663
|
const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
|
|
4664
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
4664
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$7;
|
|
4665
4665
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
4666
4666
|
};
|
|
4667
4667
|
/**
|
|
@@ -4815,9 +4815,9 @@ const WRITE_CANDLES_CACHE_FN$1 = functoolsKit.trycatch(functoolsKit.queued(async
|
|
|
4815
4815
|
* @returns Promise resolving to array of candle data
|
|
4816
4816
|
*/
|
|
4817
4817
|
const GET_CANDLES_FN$1 = async (dto, since, self) => {
|
|
4818
|
-
const step = INTERVAL_MINUTES$
|
|
4818
|
+
const step = INTERVAL_MINUTES$9[dto.interval];
|
|
4819
4819
|
const sinceTimestamp = since.getTime();
|
|
4820
|
-
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$
|
|
4820
|
+
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$7;
|
|
4821
4821
|
await Candle.acquireLock(`ClientExchange GET_CANDLES_FN symbol=${dto.symbol} interval=${dto.interval} limit=${dto.limit}`);
|
|
4822
4822
|
try {
|
|
4823
4823
|
// Try to read from cache first
|
|
@@ -4931,11 +4931,11 @@ class ClientExchange {
|
|
|
4931
4931
|
interval,
|
|
4932
4932
|
limit,
|
|
4933
4933
|
});
|
|
4934
|
-
const step = INTERVAL_MINUTES$
|
|
4934
|
+
const step = INTERVAL_MINUTES$9[interval];
|
|
4935
4935
|
if (!step) {
|
|
4936
4936
|
throw new Error(`ClientExchange unknown interval=${interval}`);
|
|
4937
4937
|
}
|
|
4938
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
4938
|
+
const stepMs = step * MS_PER_MINUTE$7;
|
|
4939
4939
|
// Align when down to interval boundary
|
|
4940
4940
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
4941
4941
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
@@ -5012,11 +5012,11 @@ class ClientExchange {
|
|
|
5012
5012
|
if (!this.params.execution.context.backtest) {
|
|
5013
5013
|
throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
|
|
5014
5014
|
}
|
|
5015
|
-
const step = INTERVAL_MINUTES$
|
|
5015
|
+
const step = INTERVAL_MINUTES$9[interval];
|
|
5016
5016
|
if (!step) {
|
|
5017
5017
|
throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
|
|
5018
5018
|
}
|
|
5019
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
5019
|
+
const stepMs = step * MS_PER_MINUTE$7;
|
|
5020
5020
|
const now = Date.now();
|
|
5021
5021
|
// Align when down to interval boundary
|
|
5022
5022
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
@@ -5201,11 +5201,11 @@ class ClientExchange {
|
|
|
5201
5201
|
sDate,
|
|
5202
5202
|
eDate,
|
|
5203
5203
|
});
|
|
5204
|
-
const step = INTERVAL_MINUTES$
|
|
5204
|
+
const step = INTERVAL_MINUTES$9[interval];
|
|
5205
5205
|
if (!step) {
|
|
5206
5206
|
throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
|
|
5207
5207
|
}
|
|
5208
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
5208
|
+
const stepMs = step * MS_PER_MINUTE$7;
|
|
5209
5209
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
5210
5210
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
5211
5211
|
let sinceTimestamp;
|
|
@@ -5334,7 +5334,7 @@ class ClientExchange {
|
|
|
5334
5334
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
5335
5335
|
const to = new Date(alignedTo);
|
|
5336
5336
|
const from = new Date(alignedTo -
|
|
5337
|
-
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
5337
|
+
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$7);
|
|
5338
5338
|
return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
|
|
5339
5339
|
}
|
|
5340
5340
|
/**
|
|
@@ -5363,7 +5363,7 @@ class ClientExchange {
|
|
|
5363
5363
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
5364
5364
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
5365
5365
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, 1);
|
|
5366
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
5366
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$7 - MS_PER_MINUTE$7;
|
|
5367
5367
|
// No limit: fetch a single window and return as-is
|
|
5368
5368
|
if (limit === undefined) {
|
|
5369
5369
|
const to = new Date(alignedTo);
|
|
@@ -6317,7 +6317,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
|
|
|
6317
6317
|
}
|
|
6318
6318
|
};
|
|
6319
6319
|
|
|
6320
|
-
const INTERVAL_MINUTES$
|
|
6320
|
+
const INTERVAL_MINUTES$8 = {
|
|
6321
6321
|
"1m": 1,
|
|
6322
6322
|
"3m": 3,
|
|
6323
6323
|
"5m": 5,
|
|
@@ -6738,7 +6738,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
6738
6738
|
}
|
|
6739
6739
|
const currentTime = self.params.execution.context.when.getTime();
|
|
6740
6740
|
{
|
|
6741
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
6741
|
+
const intervalMinutes = INTERVAL_MINUTES$8[self.params.interval];
|
|
6742
6742
|
const intervalMs = intervalMinutes * 60 * 1000;
|
|
6743
6743
|
const alignedTime = Math.floor(currentTime / intervalMs) * intervalMs;
|
|
6744
6744
|
// Проверяем что наступил новый интервал (по aligned timestamp)
|
|
@@ -13733,7 +13733,7 @@ class StrategyConnectionService {
|
|
|
13733
13733
|
* Maps FrameInterval to minutes for timestamp calculation.
|
|
13734
13734
|
* Used to generate timeframe arrays with proper spacing.
|
|
13735
13735
|
*/
|
|
13736
|
-
const INTERVAL_MINUTES$
|
|
13736
|
+
const INTERVAL_MINUTES$7 = {
|
|
13737
13737
|
"1m": 1,
|
|
13738
13738
|
"3m": 3,
|
|
13739
13739
|
"5m": 5,
|
|
@@ -13787,7 +13787,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
13787
13787
|
symbol,
|
|
13788
13788
|
});
|
|
13789
13789
|
const { interval, startDate, endDate } = self.params;
|
|
13790
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
13790
|
+
const intervalMinutes = INTERVAL_MINUTES$7[interval];
|
|
13791
13791
|
if (!intervalMinutes) {
|
|
13792
13792
|
throw new Error(`ClientFrame unknown interval: ${interval}`);
|
|
13793
13793
|
}
|
|
@@ -14152,8 +14152,8 @@ const get = (object, path) => {
|
|
|
14152
14152
|
return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
|
|
14153
14153
|
};
|
|
14154
14154
|
|
|
14155
|
-
const MS_PER_MINUTE$
|
|
14156
|
-
const INTERVAL_MINUTES$
|
|
14155
|
+
const MS_PER_MINUTE$6 = 60000;
|
|
14156
|
+
const INTERVAL_MINUTES$6 = {
|
|
14157
14157
|
"1m": 1,
|
|
14158
14158
|
"3m": 3,
|
|
14159
14159
|
"5m": 5,
|
|
@@ -14184,11 +14184,11 @@ const INTERVAL_MINUTES$5 = {
|
|
|
14184
14184
|
* @returns New Date aligned down to interval boundary
|
|
14185
14185
|
*/
|
|
14186
14186
|
const alignToInterval = (date, interval) => {
|
|
14187
|
-
const minutes = INTERVAL_MINUTES$
|
|
14187
|
+
const minutes = INTERVAL_MINUTES$6[interval];
|
|
14188
14188
|
if (minutes === undefined) {
|
|
14189
14189
|
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
14190
14190
|
}
|
|
14191
|
-
const intervalMs = minutes * MS_PER_MINUTE$
|
|
14191
|
+
const intervalMs = minutes * MS_PER_MINUTE$6;
|
|
14192
14192
|
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
14193
14193
|
};
|
|
14194
14194
|
|
|
@@ -19670,8 +19670,8 @@ class BacktestLogicPrivateService {
|
|
|
19670
19670
|
}
|
|
19671
19671
|
|
|
19672
19672
|
const EMITTER_CHECK_INTERVAL = 5000;
|
|
19673
|
-
const MS_PER_MINUTE$
|
|
19674
|
-
const INTERVAL_MINUTES$
|
|
19673
|
+
const MS_PER_MINUTE$5 = 60000;
|
|
19674
|
+
const INTERVAL_MINUTES$5 = {
|
|
19675
19675
|
"1m": 1,
|
|
19676
19676
|
"3m": 3,
|
|
19677
19677
|
"5m": 5,
|
|
@@ -19686,7 +19686,7 @@ const INTERVAL_MINUTES$4 = {
|
|
|
19686
19686
|
};
|
|
19687
19687
|
const createEmitter = functoolsKit.memoize(([interval]) => `${interval}`, (interval) => {
|
|
19688
19688
|
const tickSubject = new functoolsKit.Subject();
|
|
19689
|
-
const intervalMs = INTERVAL_MINUTES$
|
|
19689
|
+
const intervalMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$5;
|
|
19690
19690
|
{
|
|
19691
19691
|
let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
19692
19692
|
functoolsKit.Source.fromInterval(EMITTER_CHECK_INTERVAL)
|
|
@@ -35192,7 +35192,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
|
35192
35192
|
const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
|
|
35193
35193
|
const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
|
|
35194
35194
|
const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
|
|
35195
|
-
const MS_PER_MINUTE$
|
|
35195
|
+
const MS_PER_MINUTE$4 = 60000;
|
|
35196
35196
|
/**
|
|
35197
35197
|
* Gets current timestamp from execution context if available.
|
|
35198
35198
|
* Returns current Date() if no execution context exists (non-trading GUI).
|
|
@@ -35259,7 +35259,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
|
|
|
35259
35259
|
const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
|
|
35260
35260
|
throw new Error(`getAggregatedTrades is not implemented for this exchange`);
|
|
35261
35261
|
};
|
|
35262
|
-
const INTERVAL_MINUTES$
|
|
35262
|
+
const INTERVAL_MINUTES$4 = {
|
|
35263
35263
|
"1m": 1,
|
|
35264
35264
|
"3m": 3,
|
|
35265
35265
|
"5m": 5,
|
|
@@ -35290,7 +35290,7 @@ const INTERVAL_MINUTES$3 = {
|
|
|
35290
35290
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
35291
35291
|
*/
|
|
35292
35292
|
const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
|
|
35293
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
35293
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$4;
|
|
35294
35294
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
35295
35295
|
};
|
|
35296
35296
|
/**
|
|
@@ -35430,11 +35430,11 @@ class ExchangeInstance {
|
|
|
35430
35430
|
limit,
|
|
35431
35431
|
});
|
|
35432
35432
|
const getCandles = this._methods.getCandles;
|
|
35433
|
-
const step = INTERVAL_MINUTES$
|
|
35433
|
+
const step = INTERVAL_MINUTES$4[interval];
|
|
35434
35434
|
if (!step) {
|
|
35435
35435
|
throw new Error(`ExchangeInstance unknown interval=${interval}`);
|
|
35436
35436
|
}
|
|
35437
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
35437
|
+
const stepMs = step * MS_PER_MINUTE$4;
|
|
35438
35438
|
// Align when down to interval boundary
|
|
35439
35439
|
const when = await GET_TIMESTAMP_FN();
|
|
35440
35440
|
const whenTimestamp = when.getTime();
|
|
@@ -35648,7 +35648,7 @@ class ExchangeInstance {
|
|
|
35648
35648
|
const when = await GET_TIMESTAMP_FN();
|
|
35649
35649
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
35650
35650
|
const to = new Date(alignedTo);
|
|
35651
|
-
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
35651
|
+
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$4);
|
|
35652
35652
|
const isBacktest = await GET_BACKTEST_FN();
|
|
35653
35653
|
return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
|
|
35654
35654
|
};
|
|
@@ -35680,7 +35680,7 @@ class ExchangeInstance {
|
|
|
35680
35680
|
const when = await GET_TIMESTAMP_FN();
|
|
35681
35681
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
35682
35682
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
|
|
35683
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
35683
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$4 - MS_PER_MINUTE$4;
|
|
35684
35684
|
const isBacktest = await GET_BACKTEST_FN();
|
|
35685
35685
|
// No limit: fetch a single window and return as-is
|
|
35686
35686
|
if (limit === undefined) {
|
|
@@ -35743,11 +35743,11 @@ class ExchangeInstance {
|
|
|
35743
35743
|
sDate,
|
|
35744
35744
|
eDate,
|
|
35745
35745
|
});
|
|
35746
|
-
const step = INTERVAL_MINUTES$
|
|
35746
|
+
const step = INTERVAL_MINUTES$4[interval];
|
|
35747
35747
|
if (!step) {
|
|
35748
35748
|
throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
|
|
35749
35749
|
}
|
|
35750
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
35750
|
+
const stepMs = step * MS_PER_MINUTE$4;
|
|
35751
35751
|
const when = await GET_TIMESTAMP_FN();
|
|
35752
35752
|
const nowTimestamp = when.getTime();
|
|
35753
35753
|
const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
|
|
@@ -36059,8 +36059,8 @@ const Exchange = new ExchangeUtils();
|
|
|
36059
36059
|
|
|
36060
36060
|
const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
|
|
36061
36061
|
const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
|
|
36062
|
-
const MS_PER_MINUTE$
|
|
36063
|
-
const INTERVAL_MINUTES$
|
|
36062
|
+
const MS_PER_MINUTE$3 = 60000;
|
|
36063
|
+
const INTERVAL_MINUTES$3 = {
|
|
36064
36064
|
"1m": 1,
|
|
36065
36065
|
"3m": 3,
|
|
36066
36066
|
"5m": 5,
|
|
@@ -36074,7 +36074,7 @@ const INTERVAL_MINUTES$2 = {
|
|
|
36074
36074
|
"1d": 1440,
|
|
36075
36075
|
};
|
|
36076
36076
|
const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
|
|
36077
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
36077
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
|
|
36078
36078
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
36079
36079
|
};
|
|
36080
36080
|
const BAR_LENGTH = 30;
|
|
@@ -36099,11 +36099,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
|
|
|
36099
36099
|
async function checkCandles(params) {
|
|
36100
36100
|
const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
|
|
36101
36101
|
backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
|
|
36102
|
-
const step = INTERVAL_MINUTES$
|
|
36102
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
36103
36103
|
if (!step) {
|
|
36104
36104
|
throw new Error(`checkCandles: unsupported interval=${interval}`);
|
|
36105
36105
|
}
|
|
36106
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
36106
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
36107
36107
|
const dir = path.join(baseDir, exchangeName, symbol, interval);
|
|
36108
36108
|
const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
36109
36109
|
const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -36173,11 +36173,11 @@ async function warmCandles(params) {
|
|
|
36173
36173
|
from,
|
|
36174
36174
|
to,
|
|
36175
36175
|
});
|
|
36176
|
-
const step = INTERVAL_MINUTES$
|
|
36176
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
36177
36177
|
if (!step) {
|
|
36178
36178
|
throw new Error(`warmCandles: unsupported interval=${interval}`);
|
|
36179
36179
|
}
|
|
36180
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
36180
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
36181
36181
|
const instance = new ExchangeInstance(exchangeName);
|
|
36182
36182
|
const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
36183
36183
|
const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -48524,14 +48524,17 @@ class RecentPersistBacktestUtils {
|
|
|
48524
48524
|
};
|
|
48525
48525
|
/**
|
|
48526
48526
|
* Retrieves the latest persisted signal for the given context.
|
|
48527
|
+
* Returns null if the stored signal's `timestamp` is greater than the requested `when`
|
|
48528
|
+
* (look-ahead bias protection).
|
|
48527
48529
|
* @param symbol - Trading pair symbol
|
|
48528
48530
|
* @param strategyName - Strategy identifier
|
|
48529
48531
|
* @param exchangeName - Exchange identifier
|
|
48530
48532
|
* @param frameName - Frame identifier
|
|
48531
48533
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48532
|
-
* @
|
|
48534
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48535
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48533
48536
|
*/
|
|
48534
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48537
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48535
48538
|
backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
48536
48539
|
symbol,
|
|
48537
48540
|
strategyName,
|
|
@@ -48539,20 +48542,26 @@ class RecentPersistBacktestUtils {
|
|
|
48539
48542
|
frameName,
|
|
48540
48543
|
backtest: backtest$1,
|
|
48541
48544
|
});
|
|
48542
|
-
|
|
48545
|
+
const signal = await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48546
|
+
if (!signal || signal.timestamp > when.getTime()) {
|
|
48547
|
+
return null;
|
|
48548
|
+
}
|
|
48549
|
+
return signal;
|
|
48543
48550
|
};
|
|
48544
48551
|
/**
|
|
48545
48552
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48546
|
-
*
|
|
48553
|
+
* `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
|
|
48554
|
+
* the requested one is treated as not yet visible.
|
|
48555
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48547
48556
|
* @param symbol - Trading pair symbol
|
|
48548
48557
|
* @param strategyName - Strategy identifier
|
|
48549
48558
|
* @param exchangeName - Exchange identifier
|
|
48550
48559
|
* @param frameName - Frame identifier
|
|
48551
48560
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48552
|
-
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
48561
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48553
48562
|
*/
|
|
48554
48563
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
48555
|
-
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
48564
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
|
|
48556
48565
|
if (!signal) {
|
|
48557
48566
|
return null;
|
|
48558
48567
|
}
|
|
@@ -48588,30 +48597,39 @@ class RecentMemoryBacktestUtils {
|
|
|
48588
48597
|
};
|
|
48589
48598
|
/**
|
|
48590
48599
|
* Retrieves the latest in-memory signal for the given context.
|
|
48600
|
+
* Returns null if the stored signal's `timestamp` is greater than the requested `when`
|
|
48601
|
+
* (look-ahead bias protection).
|
|
48591
48602
|
* @param symbol - Trading pair symbol
|
|
48592
48603
|
* @param strategyName - Strategy identifier
|
|
48593
48604
|
* @param exchangeName - Exchange identifier
|
|
48594
48605
|
* @param frameName - Frame identifier
|
|
48595
48606
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48596
|
-
* @
|
|
48607
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48608
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48597
48609
|
*/
|
|
48598
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48610
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48599
48611
|
const key = CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48600
48612
|
backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
|
|
48601
|
-
|
|
48613
|
+
const signal = this._signals.get(key) ?? null;
|
|
48614
|
+
if (!signal || signal.timestamp > when.getTime()) {
|
|
48615
|
+
return null;
|
|
48616
|
+
}
|
|
48617
|
+
return signal;
|
|
48602
48618
|
};
|
|
48603
48619
|
/**
|
|
48604
48620
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48605
|
-
*
|
|
48621
|
+
* `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
|
|
48622
|
+
* the requested one is treated as not yet visible.
|
|
48623
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48606
48624
|
* @param symbol - Trading pair symbol
|
|
48607
48625
|
* @param strategyName - Strategy identifier
|
|
48608
48626
|
* @param exchangeName - Exchange identifier
|
|
48609
48627
|
* @param frameName - Frame identifier
|
|
48610
48628
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48611
|
-
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
48629
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48612
48630
|
*/
|
|
48613
48631
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
48614
|
-
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
48632
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
|
|
48615
48633
|
if (!signal) {
|
|
48616
48634
|
return null;
|
|
48617
48635
|
}
|
|
@@ -48643,14 +48661,17 @@ class RecentPersistLiveUtils {
|
|
|
48643
48661
|
};
|
|
48644
48662
|
/**
|
|
48645
48663
|
* Retrieves the latest persisted signal for the given context.
|
|
48664
|
+
* Returns null if the stored signal's `timestamp` is greater than the requested `when`
|
|
48665
|
+
* (look-ahead bias protection).
|
|
48646
48666
|
* @param symbol - Trading pair symbol
|
|
48647
48667
|
* @param strategyName - Strategy identifier
|
|
48648
48668
|
* @param exchangeName - Exchange identifier
|
|
48649
48669
|
* @param frameName - Frame identifier
|
|
48650
48670
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48651
|
-
* @
|
|
48671
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48672
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48652
48673
|
*/
|
|
48653
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48674
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48654
48675
|
backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
48655
48676
|
symbol,
|
|
48656
48677
|
strategyName,
|
|
@@ -48658,20 +48679,26 @@ class RecentPersistLiveUtils {
|
|
|
48658
48679
|
frameName,
|
|
48659
48680
|
backtest: backtest$1,
|
|
48660
48681
|
});
|
|
48661
|
-
|
|
48682
|
+
const signal = await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48683
|
+
if (!signal || signal.timestamp > when.getTime()) {
|
|
48684
|
+
return null;
|
|
48685
|
+
}
|
|
48686
|
+
return signal;
|
|
48662
48687
|
};
|
|
48663
48688
|
/**
|
|
48664
48689
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48665
|
-
*
|
|
48690
|
+
* `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
|
|
48691
|
+
* the requested one is treated as not yet visible.
|
|
48692
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48666
48693
|
* @param symbol - Trading pair symbol
|
|
48667
48694
|
* @param strategyName - Strategy identifier
|
|
48668
48695
|
* @param exchangeName - Exchange identifier
|
|
48669
48696
|
* @param frameName - Frame identifier
|
|
48670
48697
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48671
|
-
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
48698
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48672
48699
|
*/
|
|
48673
48700
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
48674
|
-
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
48701
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
|
|
48675
48702
|
if (!signal) {
|
|
48676
48703
|
return null;
|
|
48677
48704
|
}
|
|
@@ -48707,30 +48734,39 @@ class RecentMemoryLiveUtils {
|
|
|
48707
48734
|
};
|
|
48708
48735
|
/**
|
|
48709
48736
|
* Retrieves the latest in-memory signal for the given context.
|
|
48737
|
+
* Returns null if the stored signal's `timestamp` is greater than the requested `when`
|
|
48738
|
+
* (look-ahead bias protection).
|
|
48710
48739
|
* @param symbol - Trading pair symbol
|
|
48711
48740
|
* @param strategyName - Strategy identifier
|
|
48712
48741
|
* @param exchangeName - Exchange identifier
|
|
48713
48742
|
* @param frameName - Frame identifier
|
|
48714
48743
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48715
|
-
* @
|
|
48744
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48745
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48716
48746
|
*/
|
|
48717
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48747
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48718
48748
|
const key = CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48719
48749
|
backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
|
|
48720
|
-
|
|
48750
|
+
const signal = this._signals.get(key) ?? null;
|
|
48751
|
+
if (!signal || signal.timestamp > when.getTime()) {
|
|
48752
|
+
return null;
|
|
48753
|
+
}
|
|
48754
|
+
return signal;
|
|
48721
48755
|
};
|
|
48722
48756
|
/**
|
|
48723
48757
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48724
|
-
*
|
|
48758
|
+
* `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
|
|
48759
|
+
* the requested one is treated as not yet visible.
|
|
48760
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48725
48761
|
* @param symbol - Trading pair symbol
|
|
48726
48762
|
* @param strategyName - Strategy identifier
|
|
48727
48763
|
* @param exchangeName - Exchange identifier
|
|
48728
48764
|
* @param frameName - Frame identifier
|
|
48729
48765
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48730
|
-
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
48766
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48731
48767
|
*/
|
|
48732
48768
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
48733
|
-
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
48769
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
|
|
48734
48770
|
if (!signal) {
|
|
48735
48771
|
return null;
|
|
48736
48772
|
}
|
|
@@ -48770,9 +48806,10 @@ class RecentBacktestAdapter {
|
|
|
48770
48806
|
* @param exchangeName - Exchange identifier
|
|
48771
48807
|
* @param frameName - Frame identifier
|
|
48772
48808
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48773
|
-
* @
|
|
48809
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48810
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48774
48811
|
*/
|
|
48775
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48812
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48776
48813
|
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
48777
48814
|
symbol,
|
|
48778
48815
|
strategyName,
|
|
@@ -48780,18 +48817,20 @@ class RecentBacktestAdapter {
|
|
|
48780
48817
|
frameName,
|
|
48781
48818
|
backtest: backtest$1,
|
|
48782
48819
|
});
|
|
48783
|
-
return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48820
|
+
return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, when);
|
|
48784
48821
|
};
|
|
48785
48822
|
/**
|
|
48786
48823
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48787
|
-
* Proxies call to the underlying storage adapter.
|
|
48788
|
-
*
|
|
48824
|
+
* Proxies call to the underlying storage adapter. `timestamp` doubles as the
|
|
48825
|
+
* look-ahead cutoff — a signal whose `timestamp` exceeds the requested one is
|
|
48826
|
+
* treated as not yet visible.
|
|
48827
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48789
48828
|
* @param symbol - Trading pair symbol
|
|
48790
48829
|
* @param strategyName - Strategy identifier
|
|
48791
48830
|
* @param exchangeName - Exchange identifier
|
|
48792
48831
|
* @param frameName - Frame identifier
|
|
48793
48832
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48794
|
-
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
48833
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48795
48834
|
*/
|
|
48796
48835
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48797
48836
|
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
@@ -48802,7 +48841,7 @@ class RecentBacktestAdapter {
|
|
|
48802
48841
|
backtest: backtest$1,
|
|
48803
48842
|
timestamp,
|
|
48804
48843
|
});
|
|
48805
|
-
const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48844
|
+
const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, new Date(timestamp));
|
|
48806
48845
|
if (!signal) {
|
|
48807
48846
|
return null;
|
|
48808
48847
|
}
|
|
@@ -48874,9 +48913,10 @@ class RecentLiveAdapter {
|
|
|
48874
48913
|
* @param exchangeName - Exchange identifier
|
|
48875
48914
|
* @param frameName - Frame identifier
|
|
48876
48915
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48877
|
-
* @
|
|
48916
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48917
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48878
48918
|
*/
|
|
48879
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48919
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48880
48920
|
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
48881
48921
|
symbol,
|
|
48882
48922
|
strategyName,
|
|
@@ -48884,18 +48924,20 @@ class RecentLiveAdapter {
|
|
|
48884
48924
|
frameName,
|
|
48885
48925
|
backtest: backtest$1,
|
|
48886
48926
|
});
|
|
48887
|
-
return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48927
|
+
return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, when);
|
|
48888
48928
|
};
|
|
48889
48929
|
/**
|
|
48890
48930
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48891
|
-
* Proxies call to the underlying storage adapter.
|
|
48892
|
-
*
|
|
48931
|
+
* Proxies call to the underlying storage adapter. `timestamp` doubles as the
|
|
48932
|
+
* look-ahead cutoff — a signal whose `timestamp` exceeds the requested one is
|
|
48933
|
+
* treated as not yet visible.
|
|
48934
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48893
48935
|
* @param symbol - Trading pair symbol
|
|
48894
48936
|
* @param strategyName - Strategy identifier
|
|
48895
48937
|
* @param exchangeName - Exchange identifier
|
|
48896
48938
|
* @param frameName - Frame identifier
|
|
48897
48939
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48898
|
-
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
48940
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48899
48941
|
*/
|
|
48900
48942
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48901
48943
|
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
@@ -48906,7 +48948,7 @@ class RecentLiveAdapter {
|
|
|
48906
48948
|
backtest: backtest$1,
|
|
48907
48949
|
timestamp,
|
|
48908
48950
|
});
|
|
48909
|
-
const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48951
|
+
const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, new Date(timestamp));
|
|
48910
48952
|
if (!signal) {
|
|
48911
48953
|
return null;
|
|
48912
48954
|
}
|
|
@@ -48996,14 +49038,16 @@ class RecentAdapter {
|
|
|
48996
49038
|
/**
|
|
48997
49039
|
* Retrieves the latest active signal for the given symbol and context.
|
|
48998
49040
|
* Searches backtest storage first, then live storage.
|
|
49041
|
+
* Returns null if the stored signal's `timestamp` is greater than the requested `when`
|
|
49042
|
+
* (look-ahead bias protection).
|
|
48999
49043
|
*
|
|
49000
49044
|
* @param symbol - Trading pair symbol
|
|
49001
49045
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
49002
|
-
* @param
|
|
49003
|
-
* @returns The latest signal or null if not found
|
|
49046
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
49047
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
49004
49048
|
* @throws Error if RecentAdapter is not enabled
|
|
49005
49049
|
*/
|
|
49006
|
-
this.getLatestSignal = async (symbol, context) => {
|
|
49050
|
+
this.getLatestSignal = async (symbol, context, when) => {
|
|
49007
49051
|
backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
49008
49052
|
symbol,
|
|
49009
49053
|
context,
|
|
@@ -49012,10 +49056,10 @@ class RecentAdapter {
|
|
|
49012
49056
|
throw new Error("RecentAdapter is not enabled. Call enable() first.");
|
|
49013
49057
|
}
|
|
49014
49058
|
let result = null;
|
|
49015
|
-
if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
|
|
49059
|
+
if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true, when)) {
|
|
49016
49060
|
return result;
|
|
49017
49061
|
}
|
|
49018
|
-
if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
|
|
49062
|
+
if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false, when)) {
|
|
49019
49063
|
return result;
|
|
49020
49064
|
}
|
|
49021
49065
|
return null;
|
|
@@ -49023,13 +49067,16 @@ class RecentAdapter {
|
|
|
49023
49067
|
/**
|
|
49024
49068
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
49025
49069
|
* Searches backtest storage first, then live storage.
|
|
49026
|
-
*
|
|
49070
|
+
* `when` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
|
|
49071
|
+
* `when.getTime()` is treated as not yet visible — and as the "now" against
|
|
49072
|
+
* which elapsed minutes are computed.
|
|
49027
49073
|
* @param symbol - Trading pair symbol
|
|
49028
49074
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
49029
|
-
* @
|
|
49075
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead cutoff + "now")
|
|
49076
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
49030
49077
|
* @throws Error if RecentAdapter is not enabled
|
|
49031
49078
|
*/
|
|
49032
|
-
this.getMinutesSinceLatestSignalCreated = async (symbol, context) => {
|
|
49079
|
+
this.getMinutesSinceLatestSignalCreated = async (symbol, context, when) => {
|
|
49033
49080
|
backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL, {
|
|
49034
49081
|
symbol,
|
|
49035
49082
|
context,
|
|
@@ -49038,13 +49085,11 @@ class RecentAdapter {
|
|
|
49038
49085
|
throw new Error("RecentAdapter is not enabled. Call enable() first.");
|
|
49039
49086
|
}
|
|
49040
49087
|
let signal = null;
|
|
49041
|
-
if (signal = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
|
|
49042
|
-
|
|
49043
|
-
return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
|
|
49088
|
+
if (signal = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true, when)) {
|
|
49089
|
+
return Math.floor((when.getTime() - signal.timestamp) / (1000 * 60));
|
|
49044
49090
|
}
|
|
49045
|
-
if (signal = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
|
|
49046
|
-
|
|
49047
|
-
return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
|
|
49091
|
+
if (signal = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false, when)) {
|
|
49092
|
+
return Math.floor((when.getTime() - signal.timestamp) / (1000 * 60));
|
|
49048
49093
|
}
|
|
49049
49094
|
return null;
|
|
49050
49095
|
};
|
|
@@ -49109,41 +49154,54 @@ class StateLocalInstance {
|
|
|
49109
49154
|
this.initialValue = initialValue;
|
|
49110
49155
|
this.signalId = signalId;
|
|
49111
49156
|
this.bucketName = bucketName;
|
|
49157
|
+
this._when = 0;
|
|
49112
49158
|
/**
|
|
49113
49159
|
* Initializes _value from initialValue - local state needs no async setup.
|
|
49114
49160
|
* @returns Promise that resolves immediately
|
|
49115
49161
|
*/
|
|
49116
49162
|
this.waitForInit = functoolsKit.singleshot(async (_initial) => {
|
|
49117
49163
|
this._value = this.initialValue;
|
|
49164
|
+
this._when = 0;
|
|
49118
49165
|
});
|
|
49119
49166
|
/**
|
|
49120
49167
|
* Update the in-memory state value.
|
|
49168
|
+
* Records `when` so future reads with a smaller `when` see `initialValue`.
|
|
49169
|
+
* The dispatch updater receives the look-ahead-guarded current value.
|
|
49121
49170
|
* @param dispatch - New value or updater function receiving current value
|
|
49171
|
+
* @param when - Logical timestamp this value belongs to
|
|
49122
49172
|
* @returns Updated state value
|
|
49123
49173
|
*/
|
|
49124
|
-
this.setState = functoolsKit.queued(async (dispatch) => {
|
|
49174
|
+
this.setState = functoolsKit.queued(async (dispatch, when) => {
|
|
49125
49175
|
backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_SET, {
|
|
49126
49176
|
signalId: this.signalId,
|
|
49127
49177
|
bucketName: this.bucketName,
|
|
49128
49178
|
});
|
|
49129
49179
|
if (typeof dispatch === "function") {
|
|
49130
|
-
this.
|
|
49180
|
+
const prev = this._when > when.getTime() ? this.initialValue : this._value;
|
|
49181
|
+
this._value = await dispatch(prev);
|
|
49131
49182
|
}
|
|
49132
49183
|
else {
|
|
49133
49184
|
this._value = dispatch;
|
|
49134
49185
|
}
|
|
49186
|
+
this._when = when.getTime();
|
|
49135
49187
|
return this._value;
|
|
49136
49188
|
});
|
|
49137
49189
|
}
|
|
49138
49190
|
/**
|
|
49139
49191
|
* Read the current in-memory state value.
|
|
49192
|
+
* Returns `initialValue` when the stored `when` is greater than the requested `when`
|
|
49193
|
+
* (look-ahead bias protection).
|
|
49194
|
+
* @param when - Logical timestamp at which the read is happening
|
|
49140
49195
|
* @returns Current state value
|
|
49141
49196
|
*/
|
|
49142
|
-
async getState() {
|
|
49197
|
+
async getState(when) {
|
|
49143
49198
|
backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_GET, {
|
|
49144
49199
|
signalId: this.signalId,
|
|
49145
49200
|
bucketName: this.bucketName,
|
|
49146
49201
|
});
|
|
49202
|
+
if (this._when > when.getTime()) {
|
|
49203
|
+
return this.initialValue;
|
|
49204
|
+
}
|
|
49147
49205
|
return this._value;
|
|
49148
49206
|
}
|
|
49149
49207
|
/** Releases resources held by this instance. */
|
|
@@ -49177,14 +49235,14 @@ class StateDummyInstance {
|
|
|
49177
49235
|
* No-op read - always returns initialValue.
|
|
49178
49236
|
* @returns initialValue
|
|
49179
49237
|
*/
|
|
49180
|
-
async getState() {
|
|
49238
|
+
async getState(_when) {
|
|
49181
49239
|
return this.initialValue;
|
|
49182
49240
|
}
|
|
49183
49241
|
/**
|
|
49184
49242
|
* No-op write - discards the value and returns initialValue.
|
|
49185
49243
|
* @returns initialValue
|
|
49186
49244
|
*/
|
|
49187
|
-
async setState(_dispatch) {
|
|
49245
|
+
async setState(_dispatch, _when) {
|
|
49188
49246
|
return this.initialValue;
|
|
49189
49247
|
}
|
|
49190
49248
|
/** No-op. */
|
|
@@ -49210,6 +49268,7 @@ class StatePersistInstance {
|
|
|
49210
49268
|
this.initialValue = initialValue;
|
|
49211
49269
|
this.signalId = signalId;
|
|
49212
49270
|
this.bucketName = bucketName;
|
|
49271
|
+
this._when = 0;
|
|
49213
49272
|
/**
|
|
49214
49273
|
* Initialize persistence storage and restore state from disk.
|
|
49215
49274
|
* @param initial - Whether this is the first initialization
|
|
@@ -49224,40 +49283,54 @@ class StatePersistInstance {
|
|
|
49224
49283
|
const data = await PersistStateAdapter.readStateData(this.signalId, this.bucketName);
|
|
49225
49284
|
if (data) {
|
|
49226
49285
|
this._value = data.data;
|
|
49286
|
+
this._when = data.when;
|
|
49227
49287
|
return;
|
|
49228
49288
|
}
|
|
49229
49289
|
this._value = this.initialValue;
|
|
49290
|
+
this._when = 0;
|
|
49230
49291
|
});
|
|
49231
49292
|
/**
|
|
49232
49293
|
* Update state and persist to disk atomically.
|
|
49294
|
+
* A write with a smaller `when` overwrites an existing record — that lets a
|
|
49295
|
+
* restarted backtest reset live-written state without breaking live.
|
|
49296
|
+
* The dispatch updater receives the look-ahead-guarded current value.
|
|
49233
49297
|
* @param dispatch - New value or updater function receiving current value
|
|
49298
|
+
* @param when - Logical timestamp this value belongs to
|
|
49234
49299
|
* @returns Updated state value
|
|
49235
49300
|
*/
|
|
49236
|
-
this.setState = functoolsKit.queued(async (dispatch) => {
|
|
49301
|
+
this.setState = functoolsKit.queued(async (dispatch, when) => {
|
|
49237
49302
|
backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_SET, {
|
|
49238
49303
|
signalId: this.signalId,
|
|
49239
49304
|
bucketName: this.bucketName,
|
|
49240
49305
|
});
|
|
49241
49306
|
if (typeof dispatch === "function") {
|
|
49242
|
-
this.
|
|
49307
|
+
const prev = this._when > when.getTime() ? this.initialValue : this._value;
|
|
49308
|
+
this._value = await dispatch(prev);
|
|
49243
49309
|
}
|
|
49244
49310
|
else {
|
|
49245
49311
|
this._value = dispatch;
|
|
49246
49312
|
}
|
|
49313
|
+
this._when = when.getTime();
|
|
49247
49314
|
const id = CREATE_KEY_FN$6(this.signalId, this.bucketName);
|
|
49248
|
-
await PersistStateAdapter.writeStateData({ id, data: this._value }, this.signalId, this.bucketName);
|
|
49315
|
+
await PersistStateAdapter.writeStateData({ id, data: this._value, when: this._when }, this.signalId, this.bucketName);
|
|
49249
49316
|
return this._value;
|
|
49250
49317
|
});
|
|
49251
49318
|
}
|
|
49252
49319
|
/**
|
|
49253
49320
|
* Read the current persisted state value.
|
|
49321
|
+
* Returns `initialValue` when the stored `when` is greater than the requested `when`
|
|
49322
|
+
* (look-ahead bias protection).
|
|
49323
|
+
* @param when - Logical timestamp at which the read is happening
|
|
49254
49324
|
* @returns Current state value
|
|
49255
49325
|
*/
|
|
49256
|
-
async getState() {
|
|
49326
|
+
async getState(when) {
|
|
49257
49327
|
backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_GET, {
|
|
49258
49328
|
signalId: this.signalId,
|
|
49259
49329
|
bucketName: this.bucketName,
|
|
49260
49330
|
});
|
|
49331
|
+
if (this._when > when.getTime()) {
|
|
49332
|
+
return this.initialValue;
|
|
49333
|
+
}
|
|
49261
49334
|
return this._value;
|
|
49262
49335
|
}
|
|
49263
49336
|
/** Releases resources held by this instance. */
|
|
@@ -49310,6 +49383,7 @@ class StateBacktestAdapter {
|
|
|
49310
49383
|
* @param dto.signalId - Signal identifier
|
|
49311
49384
|
* @param dto.bucketName - Bucket name
|
|
49312
49385
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49386
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
49313
49387
|
* @returns Current state value
|
|
49314
49388
|
*/
|
|
49315
49389
|
this.getState = async (dto) => {
|
|
@@ -49321,7 +49395,7 @@ class StateBacktestAdapter {
|
|
|
49321
49395
|
const isInitial = !this.getInstance.has(key);
|
|
49322
49396
|
const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
|
|
49323
49397
|
await instance.waitForInit(isInitial);
|
|
49324
|
-
return await instance.getState();
|
|
49398
|
+
return await instance.getState(dto.when);
|
|
49325
49399
|
};
|
|
49326
49400
|
/**
|
|
49327
49401
|
* Update the state value for a signal.
|
|
@@ -49329,6 +49403,7 @@ class StateBacktestAdapter {
|
|
|
49329
49403
|
* @param dto.signalId - Signal identifier
|
|
49330
49404
|
* @param dto.bucketName - Bucket name
|
|
49331
49405
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49406
|
+
* @param dto.when - Logical timestamp this value belongs to
|
|
49332
49407
|
* @returns Updated state value
|
|
49333
49408
|
*/
|
|
49334
49409
|
this.setState = async (dispatch, dto) => {
|
|
@@ -49340,7 +49415,7 @@ class StateBacktestAdapter {
|
|
|
49340
49415
|
const isInitial = !this.getInstance.has(key);
|
|
49341
49416
|
const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
|
|
49342
49417
|
await instance.waitForInit(isInitial);
|
|
49343
|
-
return await instance.setState(dispatch);
|
|
49418
|
+
return await instance.setState(dispatch, dto.when);
|
|
49344
49419
|
};
|
|
49345
49420
|
/**
|
|
49346
49421
|
* Switches to in-memory adapter (default).
|
|
@@ -49425,6 +49500,7 @@ class StateLiveAdapter {
|
|
|
49425
49500
|
* @param dto.signalId - Signal identifier
|
|
49426
49501
|
* @param dto.bucketName - Bucket name
|
|
49427
49502
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49503
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
49428
49504
|
* @returns Current state value
|
|
49429
49505
|
*/
|
|
49430
49506
|
this.getState = async (dto) => {
|
|
@@ -49436,7 +49512,7 @@ class StateLiveAdapter {
|
|
|
49436
49512
|
const isInitial = !this.getInstance.has(key);
|
|
49437
49513
|
const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
|
|
49438
49514
|
await instance.waitForInit(isInitial);
|
|
49439
|
-
return await instance.getState();
|
|
49515
|
+
return await instance.getState(dto.when);
|
|
49440
49516
|
};
|
|
49441
49517
|
/**
|
|
49442
49518
|
* Update the state value for a signal.
|
|
@@ -49444,6 +49520,7 @@ class StateLiveAdapter {
|
|
|
49444
49520
|
* @param dto.signalId - Signal identifier
|
|
49445
49521
|
* @param dto.bucketName - Bucket name
|
|
49446
49522
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49523
|
+
* @param dto.when - Logical timestamp this value belongs to
|
|
49447
49524
|
* @returns Updated state value
|
|
49448
49525
|
*/
|
|
49449
49526
|
this.setState = async (dispatch, dto) => {
|
|
@@ -49455,7 +49532,7 @@ class StateLiveAdapter {
|
|
|
49455
49532
|
const isInitial = !this.getInstance.has(key);
|
|
49456
49533
|
const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
|
|
49457
49534
|
await instance.waitForInit(isInitial);
|
|
49458
|
-
return await instance.setState(dispatch);
|
|
49535
|
+
return await instance.setState(dispatch, dto.when);
|
|
49459
49536
|
};
|
|
49460
49537
|
/**
|
|
49461
49538
|
* Switches to in-memory adapter.
|
|
@@ -49552,6 +49629,7 @@ class StateAdapter {
|
|
|
49552
49629
|
* @param dto.bucketName - Bucket name
|
|
49553
49630
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49554
49631
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
49632
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
49555
49633
|
* @returns Current state value
|
|
49556
49634
|
* @throws Error if adapter is not enabled
|
|
49557
49635
|
*/
|
|
@@ -49577,6 +49655,7 @@ class StateAdapter {
|
|
|
49577
49655
|
* @param dto.bucketName - Bucket name
|
|
49578
49656
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49579
49657
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
49658
|
+
* @param dto.when - Logical timestamp this value belongs to
|
|
49580
49659
|
* @returns Updated state value
|
|
49581
49660
|
* @throws Error if adapter is not enabled
|
|
49582
49661
|
*/
|
|
@@ -49650,8 +49729,9 @@ async function getLatestSignal(symbol) {
|
|
|
49650
49729
|
if (!MethodContextService.hasContext()) {
|
|
49651
49730
|
throw new Error("getLatestSignal requires a method context");
|
|
49652
49731
|
}
|
|
49732
|
+
const { when } = backtest.executionContextService.context;
|
|
49653
49733
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
49654
|
-
return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
|
|
49734
|
+
return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName }, when);
|
|
49655
49735
|
}
|
|
49656
49736
|
/**
|
|
49657
49737
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
@@ -49686,8 +49766,9 @@ async function getMinutesSinceLatestSignalCreated(symbol) {
|
|
|
49686
49766
|
if (!MethodContextService.hasContext()) {
|
|
49687
49767
|
throw new Error("getMinutesSinceLatestSignalCreated requires a method context");
|
|
49688
49768
|
}
|
|
49769
|
+
const { when } = backtest.executionContextService.context;
|
|
49689
49770
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
49690
|
-
return await Recent.getMinutesSinceLatestSignalCreated(symbol, { exchangeName, frameName, strategyName });
|
|
49771
|
+
return await Recent.getMinutesSinceLatestSignalCreated(symbol, { exchangeName, frameName, strategyName }, when);
|
|
49691
49772
|
}
|
|
49692
49773
|
/**
|
|
49693
49774
|
* Reads the state value scoped to the current active signal.
|
|
@@ -49732,7 +49813,7 @@ async function getSignalState(symbol, dto) {
|
|
|
49732
49813
|
if (!MethodContextService.hasContext()) {
|
|
49733
49814
|
throw new Error("getSignalState requires a method context");
|
|
49734
49815
|
}
|
|
49735
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
49816
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
49736
49817
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
49737
49818
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
49738
49819
|
let signal;
|
|
@@ -49742,6 +49823,7 @@ async function getSignalState(symbol, dto) {
|
|
|
49742
49823
|
bucketName,
|
|
49743
49824
|
initialValue,
|
|
49744
49825
|
backtest: isBacktest,
|
|
49826
|
+
when,
|
|
49745
49827
|
});
|
|
49746
49828
|
}
|
|
49747
49829
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -49750,6 +49832,7 @@ async function getSignalState(symbol, dto) {
|
|
|
49750
49832
|
bucketName,
|
|
49751
49833
|
initialValue,
|
|
49752
49834
|
backtest: isBacktest,
|
|
49835
|
+
when,
|
|
49753
49836
|
});
|
|
49754
49837
|
}
|
|
49755
49838
|
throw new Error(`getSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
|
|
@@ -49801,7 +49884,7 @@ async function setSignalState(symbol, dispatch, dto) {
|
|
|
49801
49884
|
if (!MethodContextService.hasContext()) {
|
|
49802
49885
|
throw new Error("setSignalState requires a method context");
|
|
49803
49886
|
}
|
|
49804
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
49887
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
49805
49888
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
49806
49889
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
49807
49890
|
let signal;
|
|
@@ -49811,6 +49894,7 @@ async function setSignalState(symbol, dispatch, dto) {
|
|
|
49811
49894
|
bucketName,
|
|
49812
49895
|
initialValue,
|
|
49813
49896
|
backtest: isBacktest,
|
|
49897
|
+
when,
|
|
49814
49898
|
});
|
|
49815
49899
|
}
|
|
49816
49900
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -49819,6 +49903,7 @@ async function setSignalState(symbol, dispatch, dto) {
|
|
|
49819
49903
|
bucketName,
|
|
49820
49904
|
initialValue,
|
|
49821
49905
|
backtest: isBacktest,
|
|
49906
|
+
when,
|
|
49822
49907
|
});
|
|
49823
49908
|
}
|
|
49824
49909
|
throw new Error(`setSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
|
|
@@ -49872,36 +49957,47 @@ class SessionLocalInstance {
|
|
|
49872
49957
|
this.frameName = frameName;
|
|
49873
49958
|
this.backtest = backtest$1;
|
|
49874
49959
|
this._data = null;
|
|
49960
|
+
this._when = 0;
|
|
49875
49961
|
/**
|
|
49876
49962
|
* Initializes _data to null — local session needs no async setup.
|
|
49877
49963
|
* @returns Promise that resolves immediately
|
|
49878
49964
|
*/
|
|
49879
49965
|
this.waitForInit = functoolsKit.singleshot(async (_initial) => {
|
|
49880
49966
|
this._data = null;
|
|
49967
|
+
this._when = 0;
|
|
49881
49968
|
});
|
|
49882
49969
|
/**
|
|
49883
49970
|
* Read the current in-memory session value.
|
|
49884
|
-
*
|
|
49971
|
+
* Returns null if the stored `when` is greater than the requested `when`
|
|
49972
|
+
* (look-ahead bias protection).
|
|
49973
|
+
* @param when - Logical timestamp at which the read is happening
|
|
49974
|
+
* @returns Current session value, or null
|
|
49885
49975
|
*/
|
|
49886
|
-
this.getData = async () => {
|
|
49976
|
+
this.getData = async (when) => {
|
|
49887
49977
|
backtest.loggerService.debug(SESSION_LOCAL_INSTANCE_METHOD_NAME_GET, {
|
|
49888
49978
|
strategyName: this.strategyName,
|
|
49889
49979
|
exchangeName: this.exchangeName,
|
|
49890
49980
|
frameName: this.frameName,
|
|
49891
49981
|
});
|
|
49982
|
+
if (this._when > when.getTime()) {
|
|
49983
|
+
return null;
|
|
49984
|
+
}
|
|
49892
49985
|
return this._data;
|
|
49893
49986
|
};
|
|
49894
49987
|
/**
|
|
49895
49988
|
* Update the in-memory session value.
|
|
49989
|
+
* Records `when` so future reads with a smaller `when` see no value.
|
|
49896
49990
|
* @param value - New value or null to clear
|
|
49991
|
+
* @param when - Logical timestamp this value belongs to
|
|
49897
49992
|
*/
|
|
49898
|
-
this.setData = async (value) => {
|
|
49993
|
+
this.setData = async (value, when) => {
|
|
49899
49994
|
backtest.loggerService.debug(SESSION_LOCAL_INSTANCE_METHOD_NAME_SET, {
|
|
49900
49995
|
strategyName: this.strategyName,
|
|
49901
49996
|
exchangeName: this.exchangeName,
|
|
49902
49997
|
frameName: this.frameName,
|
|
49903
49998
|
});
|
|
49904
49999
|
this._data = value;
|
|
50000
|
+
this._when = when.getTime();
|
|
49905
50001
|
};
|
|
49906
50002
|
}
|
|
49907
50003
|
/** Releases resources held by this instance. */
|
|
@@ -49937,13 +50033,13 @@ class SessionDummyInstance {
|
|
|
49937
50033
|
* No-op read — always returns null.
|
|
49938
50034
|
* @returns null
|
|
49939
50035
|
*/
|
|
49940
|
-
this.getData = async () => {
|
|
50036
|
+
this.getData = async (_when) => {
|
|
49941
50037
|
return null;
|
|
49942
50038
|
};
|
|
49943
50039
|
/**
|
|
49944
50040
|
* No-op write — discards the value.
|
|
49945
50041
|
*/
|
|
49946
|
-
this.setData = async (_value) => {
|
|
50042
|
+
this.setData = async (_value, _when) => {
|
|
49947
50043
|
};
|
|
49948
50044
|
}
|
|
49949
50045
|
/** No-op. */
|
|
@@ -49969,6 +50065,7 @@ class SessionPersistInstance {
|
|
|
49969
50065
|
this.frameName = frameName;
|
|
49970
50066
|
this.backtest = backtest$1;
|
|
49971
50067
|
this._data = null;
|
|
50068
|
+
this._when = 0;
|
|
49972
50069
|
/**
|
|
49973
50070
|
* Initialize persistence storage and restore session from disk.
|
|
49974
50071
|
* @param initial - Whether this is the first initialization
|
|
@@ -49984,35 +50081,47 @@ class SessionPersistInstance {
|
|
|
49984
50081
|
const data = await PersistSessionAdapter.readSessionData(this.strategyName, this.exchangeName, this.frameName);
|
|
49985
50082
|
if (data) {
|
|
49986
50083
|
this._data = data.data;
|
|
50084
|
+
this._when = data.when;
|
|
49987
50085
|
return;
|
|
49988
50086
|
}
|
|
49989
50087
|
this._data = null;
|
|
50088
|
+
this._when = 0;
|
|
49990
50089
|
});
|
|
49991
50090
|
/**
|
|
49992
50091
|
* Read the current persisted session value.
|
|
49993
|
-
*
|
|
50092
|
+
* Returns null if the stored `when` is greater than the requested `when`
|
|
50093
|
+
* (look-ahead bias protection).
|
|
50094
|
+
* @param when - Logical timestamp at which the read is happening
|
|
50095
|
+
* @returns Current session value, or null
|
|
49994
50096
|
*/
|
|
49995
|
-
this.getData = async () => {
|
|
50097
|
+
this.getData = async (when) => {
|
|
49996
50098
|
backtest.loggerService.debug(SESSION_PERSIST_INSTANCE_METHOD_NAME_GET, {
|
|
49997
50099
|
strategyName: this.strategyName,
|
|
49998
50100
|
exchangeName: this.exchangeName,
|
|
49999
50101
|
frameName: this.frameName,
|
|
50000
50102
|
});
|
|
50103
|
+
if (this._when > when.getTime()) {
|
|
50104
|
+
return null;
|
|
50105
|
+
}
|
|
50001
50106
|
return this._data;
|
|
50002
50107
|
};
|
|
50003
50108
|
/**
|
|
50004
50109
|
* Update session value and persist to disk atomically.
|
|
50110
|
+
* A write with a smaller `when` overwrites an existing record — that lets
|
|
50111
|
+
* a restarted backtest reset live-written state without breaking live.
|
|
50005
50112
|
* @param value - New value or null to clear
|
|
50113
|
+
* @param when - Logical timestamp this value belongs to
|
|
50006
50114
|
*/
|
|
50007
|
-
this.setData = async (value) => {
|
|
50115
|
+
this.setData = async (value, when) => {
|
|
50008
50116
|
backtest.loggerService.debug(SESSION_PERSIST_INSTANCE_METHOD_NAME_SET, {
|
|
50009
50117
|
strategyName: this.strategyName,
|
|
50010
50118
|
exchangeName: this.exchangeName,
|
|
50011
50119
|
frameName: this.frameName,
|
|
50012
50120
|
});
|
|
50013
50121
|
this._data = value;
|
|
50122
|
+
this._when = when.getTime();
|
|
50014
50123
|
const id = CREATE_KEY_FN$5(this.symbol, this.strategyName, this.exchangeName, this.frameName, this.backtest);
|
|
50015
|
-
await PersistSessionAdapter.writeSessionData({ id, data: value }, this.strategyName, this.exchangeName, this.frameName);
|
|
50124
|
+
await PersistSessionAdapter.writeSessionData({ id, data: value, when: this._when }, this.strategyName, this.exchangeName, this.frameName);
|
|
50016
50125
|
};
|
|
50017
50126
|
}
|
|
50018
50127
|
/** Releases resources held by this instance. */
|
|
@@ -50045,9 +50154,10 @@ class SessionBacktestAdapter {
|
|
|
50045
50154
|
* @param context.strategyName - Strategy identifier
|
|
50046
50155
|
* @param context.exchangeName - Exchange identifier
|
|
50047
50156
|
* @param context.frameName - Frame identifier
|
|
50048
|
-
* @
|
|
50157
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50158
|
+
* @returns Current session value, or null if not set / look-ahead
|
|
50049
50159
|
*/
|
|
50050
|
-
this.getData = async (symbol, context) => {
|
|
50160
|
+
this.getData = async (symbol, context, when) => {
|
|
50051
50161
|
backtest.loggerService.debug(SESSION_BACKTEST_ADAPTER_METHOD_NAME_GET, {
|
|
50052
50162
|
strategyName: context.strategyName,
|
|
50053
50163
|
exchangeName: context.exchangeName,
|
|
@@ -50057,7 +50167,7 @@ class SessionBacktestAdapter {
|
|
|
50057
50167
|
const isInitial = !this.getInstance.has(key);
|
|
50058
50168
|
const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, true);
|
|
50059
50169
|
await instance.waitForInit(isInitial);
|
|
50060
|
-
return await instance.getData();
|
|
50170
|
+
return await instance.getData(when);
|
|
50061
50171
|
};
|
|
50062
50172
|
/**
|
|
50063
50173
|
* Update the session value for a backtest run.
|
|
@@ -50066,8 +50176,9 @@ class SessionBacktestAdapter {
|
|
|
50066
50176
|
* @param context.strategyName - Strategy identifier
|
|
50067
50177
|
* @param context.exchangeName - Exchange identifier
|
|
50068
50178
|
* @param context.frameName - Frame identifier
|
|
50179
|
+
* @param when - Logical timestamp this value belongs to
|
|
50069
50180
|
*/
|
|
50070
|
-
this.setData = async (symbol, value, context) => {
|
|
50181
|
+
this.setData = async (symbol, value, context, when) => {
|
|
50071
50182
|
backtest.loggerService.debug(SESSION_BACKTEST_ADAPTER_METHOD_NAME_SET, {
|
|
50072
50183
|
strategyName: context.strategyName,
|
|
50073
50184
|
exchangeName: context.exchangeName,
|
|
@@ -50077,7 +50188,7 @@ class SessionBacktestAdapter {
|
|
|
50077
50188
|
const isInitial = !this.getInstance.has(key);
|
|
50078
50189
|
const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, true);
|
|
50079
50190
|
await instance.waitForInit(isInitial);
|
|
50080
|
-
return await instance.setData(value);
|
|
50191
|
+
return await instance.setData(value, when);
|
|
50081
50192
|
};
|
|
50082
50193
|
/**
|
|
50083
50194
|
* Switches to in-memory adapter (default).
|
|
@@ -50141,9 +50252,10 @@ class SessionLiveAdapter {
|
|
|
50141
50252
|
* @param context.strategyName - Strategy identifier
|
|
50142
50253
|
* @param context.exchangeName - Exchange identifier
|
|
50143
50254
|
* @param context.frameName - Frame identifier
|
|
50144
|
-
* @
|
|
50255
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50256
|
+
* @returns Current session value, or null if not set / look-ahead
|
|
50145
50257
|
*/
|
|
50146
|
-
this.getData = async (symbol, context) => {
|
|
50258
|
+
this.getData = async (symbol, context, when) => {
|
|
50147
50259
|
backtest.loggerService.debug(SESSION_LIVE_ADAPTER_METHOD_NAME_GET, {
|
|
50148
50260
|
strategyName: context.strategyName,
|
|
50149
50261
|
exchangeName: context.exchangeName,
|
|
@@ -50153,7 +50265,7 @@ class SessionLiveAdapter {
|
|
|
50153
50265
|
const isInitial = !this.getInstance.has(key);
|
|
50154
50266
|
const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, false);
|
|
50155
50267
|
await instance.waitForInit(isInitial);
|
|
50156
|
-
return await instance.getData();
|
|
50268
|
+
return await instance.getData(when);
|
|
50157
50269
|
};
|
|
50158
50270
|
/**
|
|
50159
50271
|
* Update the session value for a live run.
|
|
@@ -50162,8 +50274,9 @@ class SessionLiveAdapter {
|
|
|
50162
50274
|
* @param context.strategyName - Strategy identifier
|
|
50163
50275
|
* @param context.exchangeName - Exchange identifier
|
|
50164
50276
|
* @param context.frameName - Frame identifier
|
|
50277
|
+
* @param when - Logical timestamp this value belongs to
|
|
50165
50278
|
*/
|
|
50166
|
-
this.setData = async (symbol, value, context) => {
|
|
50279
|
+
this.setData = async (symbol, value, context, when) => {
|
|
50167
50280
|
backtest.loggerService.debug(SESSION_LIVE_ADAPTER_METHOD_NAME_SET, {
|
|
50168
50281
|
strategyName: context.strategyName,
|
|
50169
50282
|
exchangeName: context.exchangeName,
|
|
@@ -50173,7 +50286,7 @@ class SessionLiveAdapter {
|
|
|
50173
50286
|
const isInitial = !this.getInstance.has(key);
|
|
50174
50287
|
const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, false);
|
|
50175
50288
|
await instance.waitForInit(isInitial);
|
|
50176
|
-
return await instance.setData(value);
|
|
50289
|
+
return await instance.setData(value, when);
|
|
50177
50290
|
};
|
|
50178
50291
|
/**
|
|
50179
50292
|
* Switches to in-memory adapter.
|
|
@@ -50233,9 +50346,10 @@ class SessionAdapter {
|
|
|
50233
50346
|
* @param context.exchangeName - Exchange identifier
|
|
50234
50347
|
* @param context.frameName - Frame identifier
|
|
50235
50348
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
50236
|
-
* @
|
|
50349
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50350
|
+
* @returns Current session value, or null if not set / look-ahead
|
|
50237
50351
|
*/
|
|
50238
|
-
this.getData = async (symbol, context, backtest$1) => {
|
|
50352
|
+
this.getData = async (symbol, context, backtest$1, when) => {
|
|
50239
50353
|
backtest.loggerService.debug(SESSION_ADAPTER_METHOD_NAME_GET, {
|
|
50240
50354
|
strategyName: context.strategyName,
|
|
50241
50355
|
exchangeName: context.exchangeName,
|
|
@@ -50243,9 +50357,9 @@ class SessionAdapter {
|
|
|
50243
50357
|
backtest: backtest$1,
|
|
50244
50358
|
});
|
|
50245
50359
|
if (backtest$1) {
|
|
50246
|
-
return await SessionBacktest.getData(symbol, context);
|
|
50360
|
+
return await SessionBacktest.getData(symbol, context, when);
|
|
50247
50361
|
}
|
|
50248
|
-
return await SessionLive.getData(symbol, context);
|
|
50362
|
+
return await SessionLive.getData(symbol, context, when);
|
|
50249
50363
|
};
|
|
50250
50364
|
/**
|
|
50251
50365
|
* Update the session value for a signal.
|
|
@@ -50256,8 +50370,9 @@ class SessionAdapter {
|
|
|
50256
50370
|
* @param context.exchangeName - Exchange identifier
|
|
50257
50371
|
* @param context.frameName - Frame identifier
|
|
50258
50372
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
50373
|
+
* @param when - Logical timestamp this value belongs to
|
|
50259
50374
|
*/
|
|
50260
|
-
this.setData = async (symbol, value, context, backtest$1) => {
|
|
50375
|
+
this.setData = async (symbol, value, context, backtest$1, when) => {
|
|
50261
50376
|
backtest.loggerService.debug(SESSION_ADAPTER_METHOD_NAME_SET, {
|
|
50262
50377
|
strategyName: context.strategyName,
|
|
50263
50378
|
exchangeName: context.exchangeName,
|
|
@@ -50265,9 +50380,9 @@ class SessionAdapter {
|
|
|
50265
50380
|
backtest: backtest$1,
|
|
50266
50381
|
});
|
|
50267
50382
|
if (backtest$1) {
|
|
50268
|
-
return await SessionBacktest.setData(symbol, value, context);
|
|
50383
|
+
return await SessionBacktest.setData(symbol, value, context, when);
|
|
50269
50384
|
}
|
|
50270
|
-
return await SessionLive.setData(symbol, value, context);
|
|
50385
|
+
return await SessionLive.setData(symbol, value, context, when);
|
|
50271
50386
|
};
|
|
50272
50387
|
}
|
|
50273
50388
|
}
|
|
@@ -50319,9 +50434,9 @@ async function getSessionData(symbol) {
|
|
|
50319
50434
|
if (!MethodContextService.hasContext()) {
|
|
50320
50435
|
throw new Error("getSession requires a method context");
|
|
50321
50436
|
}
|
|
50322
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
50437
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
50323
50438
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
50324
|
-
return await Session.getData(symbol, { exchangeName, frameName, strategyName }, isBacktest);
|
|
50439
|
+
return await Session.getData(symbol, { exchangeName, frameName, strategyName }, isBacktest, when);
|
|
50325
50440
|
}
|
|
50326
50441
|
/**
|
|
50327
50442
|
* Writes a session value scoped to the current (symbol, strategy, exchange, frame) context.
|
|
@@ -50353,9 +50468,9 @@ async function setSessionData(symbol, value) {
|
|
|
50353
50468
|
if (!MethodContextService.hasContext()) {
|
|
50354
50469
|
throw new Error("setSession requires a method context");
|
|
50355
50470
|
}
|
|
50356
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
50471
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
50357
50472
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
50358
|
-
await Session.setData(symbol, value, { exchangeName, frameName, strategyName }, isBacktest);
|
|
50473
|
+
await Session.setData(symbol, value, { exchangeName, frameName, strategyName }, isBacktest, when);
|
|
50359
50474
|
}
|
|
50360
50475
|
|
|
50361
50476
|
const CREATE_SIGNAL_STATE_METHOD_NAME = "state.createSignalState";
|
|
@@ -50366,7 +50481,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
|
|
|
50366
50481
|
if (!MethodContextService.hasContext()) {
|
|
50367
50482
|
throw new Error("createSignalState requires a method context");
|
|
50368
50483
|
}
|
|
50369
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
50484
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
50370
50485
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
50371
50486
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
50372
50487
|
let signal;
|
|
@@ -50376,6 +50491,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
|
|
|
50376
50491
|
bucketName: params.bucketName,
|
|
50377
50492
|
initialValue: params.initialValue,
|
|
50378
50493
|
signalId: signal.id,
|
|
50494
|
+
when,
|
|
50379
50495
|
});
|
|
50380
50496
|
}
|
|
50381
50497
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -50384,6 +50500,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
|
|
|
50384
50500
|
bucketName: params.bucketName,
|
|
50385
50501
|
initialValue: params.initialValue,
|
|
50386
50502
|
signalId: signal.id,
|
|
50503
|
+
when,
|
|
50387
50504
|
});
|
|
50388
50505
|
}
|
|
50389
50506
|
throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
|
|
@@ -50395,7 +50512,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
|
|
|
50395
50512
|
if (!MethodContextService.hasContext()) {
|
|
50396
50513
|
throw new Error("createSignalState requires a method context");
|
|
50397
50514
|
}
|
|
50398
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
50515
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
50399
50516
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
50400
50517
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
50401
50518
|
let signal;
|
|
@@ -50405,6 +50522,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
|
|
|
50405
50522
|
bucketName: params.bucketName,
|
|
50406
50523
|
initialValue: params.initialValue,
|
|
50407
50524
|
signalId: signal.id,
|
|
50525
|
+
when,
|
|
50408
50526
|
});
|
|
50409
50527
|
}
|
|
50410
50528
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -50413,6 +50531,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
|
|
|
50413
50531
|
bucketName: params.bucketName,
|
|
50414
50532
|
initialValue: params.initialValue,
|
|
50415
50533
|
signalId: signal.id,
|
|
50534
|
+
when,
|
|
50416
50535
|
});
|
|
50417
50536
|
}
|
|
50418
50537
|
throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
|
|
@@ -50495,7 +50614,7 @@ const createSearchIndex = () => {
|
|
|
50495
50614
|
df.set(term, count);
|
|
50496
50615
|
});
|
|
50497
50616
|
};
|
|
50498
|
-
const upsert = ({ id, content, index = JSON.stringify(content), priority = Date.now(), }) => {
|
|
50617
|
+
const upsert = ({ id, content, when, index = JSON.stringify(content), priority = Date.now(), }) => {
|
|
50499
50618
|
const existing = docs.get(id);
|
|
50500
50619
|
{
|
|
50501
50620
|
existing && subtractDf(existing.tf);
|
|
@@ -50504,12 +50623,21 @@ const createSearchIndex = () => {
|
|
|
50504
50623
|
const tf = new Map();
|
|
50505
50624
|
for (const t of tokens)
|
|
50506
50625
|
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
50507
|
-
docs.set(id, { tf, len: tokens.length, content, priority });
|
|
50626
|
+
docs.set(id, { tf, len: tokens.length, content, priority, when });
|
|
50508
50627
|
{
|
|
50509
50628
|
addDf(tf);
|
|
50510
50629
|
}
|
|
50511
50630
|
};
|
|
50512
|
-
|
|
50631
|
+
/**
|
|
50632
|
+
* Read a document by id. Returns undefined when the document was written
|
|
50633
|
+
* with a `when` greater than the requested `when` (look-ahead guard).
|
|
50634
|
+
*/
|
|
50635
|
+
const read = (id, when) => {
|
|
50636
|
+
const doc = docs.get(id);
|
|
50637
|
+
if (!doc || doc.when > when)
|
|
50638
|
+
return undefined;
|
|
50639
|
+
return doc.content;
|
|
50640
|
+
};
|
|
50513
50641
|
const remove = (id) => {
|
|
50514
50642
|
{
|
|
50515
50643
|
const existing = docs.get(id);
|
|
@@ -50517,16 +50645,30 @@ const createSearchIndex = () => {
|
|
|
50517
50645
|
}
|
|
50518
50646
|
docs.delete(id);
|
|
50519
50647
|
};
|
|
50520
|
-
|
|
50648
|
+
/**
|
|
50649
|
+
* List documents whose `when` is less than or equal to the requested `when`
|
|
50650
|
+
* (look-ahead guard), sorted by priority.
|
|
50651
|
+
*/
|
|
50652
|
+
const list = (when) => Array.from(docs.entries())
|
|
50653
|
+
.filter(([, doc]) => doc.when <= when)
|
|
50521
50654
|
.sort(([, a], [, b]) => a.priority - b.priority)
|
|
50522
50655
|
.map(([id, { content }]) => ({ id, content }));
|
|
50523
|
-
|
|
50656
|
+
/**
|
|
50657
|
+
* BM25 search over documents whose `when` is less than or equal to the
|
|
50658
|
+
* requested `when` (look-ahead guard).
|
|
50659
|
+
*
|
|
50660
|
+
* Document frequency (df) is computed across the whole index — the time-cut
|
|
50661
|
+
* is applied only to the candidate set, so scores stay comparable across
|
|
50662
|
+
* different `when` values.
|
|
50663
|
+
*/
|
|
50664
|
+
const search = (query, when, settings = DEFAULT_SETTINGS) => {
|
|
50524
50665
|
const terms = tokenize(query);
|
|
50525
50666
|
if (!terms.length || !docs.size)
|
|
50526
50667
|
return [];
|
|
50527
50668
|
const N = docs.size;
|
|
50528
50669
|
const avgLen = [...docs.values()].reduce((s, d) => s + d.len, 0) / N;
|
|
50529
50670
|
return [...docs.entries()]
|
|
50671
|
+
.filter(([, doc]) => doc.when <= when)
|
|
50530
50672
|
.map(([id, doc]) => {
|
|
50531
50673
|
let score = 0;
|
|
50532
50674
|
for (const term of terms) {
|
|
@@ -50623,8 +50765,9 @@ class MemoryLocalInstance {
|
|
|
50623
50765
|
* @param memoryId - Unique entry identifier
|
|
50624
50766
|
* @param value - Value to store and index
|
|
50625
50767
|
* @param description - BM25 index string
|
|
50768
|
+
* @param when - Logical timestamp this entry belongs to (look-ahead guard)
|
|
50626
50769
|
*/
|
|
50627
|
-
async writeMemory(memoryId, value, description) {
|
|
50770
|
+
async writeMemory(memoryId, value, description, when) {
|
|
50628
50771
|
backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_WRITE, {
|
|
50629
50772
|
signalId: this.signalId,
|
|
50630
50773
|
bucketName: this.bucketName,
|
|
@@ -50635,21 +50778,24 @@ class MemoryLocalInstance {
|
|
|
50635
50778
|
content: value,
|
|
50636
50779
|
index: description,
|
|
50637
50780
|
priority: Date.now(),
|
|
50781
|
+
when: when.getTime(),
|
|
50638
50782
|
});
|
|
50639
50783
|
}
|
|
50640
50784
|
/**
|
|
50641
50785
|
* Read a single entry from the in-memory index.
|
|
50786
|
+
* Behaves as not-found if the stored `when` is greater than the requested `when`.
|
|
50642
50787
|
* @param memoryId - Unique entry identifier
|
|
50788
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50643
50789
|
* @returns Parsed entry value
|
|
50644
|
-
* @throws Error if entry not found
|
|
50790
|
+
* @throws Error if entry not found (or shadowed by look-ahead)
|
|
50645
50791
|
*/
|
|
50646
|
-
async readMemory(memoryId) {
|
|
50792
|
+
async readMemory(memoryId, when) {
|
|
50647
50793
|
backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_READ, {
|
|
50648
50794
|
signalId: this.signalId,
|
|
50649
50795
|
bucketName: this.bucketName,
|
|
50650
50796
|
memoryId,
|
|
50651
50797
|
});
|
|
50652
|
-
const value = this._index.read(memoryId);
|
|
50798
|
+
const value = this._index.read(memoryId, when.getTime());
|
|
50653
50799
|
if (!value) {
|
|
50654
50800
|
throw new Error(`MemoryLocalInstance value not found memoryId=${memoryId}`);
|
|
50655
50801
|
}
|
|
@@ -50657,33 +50803,38 @@ class MemoryLocalInstance {
|
|
|
50657
50803
|
}
|
|
50658
50804
|
/**
|
|
50659
50805
|
* Search entries using BM25 full-text scoring.
|
|
50806
|
+
* Filters out entries whose `when` is greater than the requested `when`.
|
|
50660
50807
|
* @param query - Search query string
|
|
50808
|
+
* @param when - Logical timestamp at which the search is happening (look-ahead guard)
|
|
50661
50809
|
* @returns Matching entries sorted by relevance score
|
|
50662
50810
|
*/
|
|
50663
|
-
async searchMemory(query, settings) {
|
|
50811
|
+
async searchMemory(query, when, settings) {
|
|
50664
50812
|
backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_SEARCH, {
|
|
50665
50813
|
signalId: this.signalId,
|
|
50666
50814
|
bucketName: this.bucketName,
|
|
50667
50815
|
query,
|
|
50668
50816
|
});
|
|
50669
|
-
return this._index.search(query, settings).map(SEARCH_MEMORY_FN);
|
|
50817
|
+
return this._index.search(query, when.getTime(), settings).map(SEARCH_MEMORY_FN);
|
|
50670
50818
|
}
|
|
50671
50819
|
/**
|
|
50672
50820
|
* List all entries stored in the in-memory index.
|
|
50821
|
+
* Filters out entries whose `when` is greater than the requested `when`.
|
|
50822
|
+
* @param when - Logical timestamp at which the list is happening (look-ahead guard)
|
|
50673
50823
|
* @returns Array of all stored entries
|
|
50674
50824
|
*/
|
|
50675
|
-
async listMemory() {
|
|
50825
|
+
async listMemory(when) {
|
|
50676
50826
|
backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_LIST, {
|
|
50677
50827
|
signalId: this.signalId,
|
|
50678
50828
|
bucketName: this.bucketName,
|
|
50679
50829
|
});
|
|
50680
|
-
return this._index.list().map(LIST_MEMORY_FN);
|
|
50830
|
+
return this._index.list(when.getTime()).map(LIST_MEMORY_FN);
|
|
50681
50831
|
}
|
|
50682
50832
|
/**
|
|
50683
50833
|
* Remove an entry from the in-memory index.
|
|
50684
50834
|
* @param memoryId - Unique entry identifier
|
|
50835
|
+
* @param when - Logical timestamp (kept for API consistency; removal is by UUID)
|
|
50685
50836
|
*/
|
|
50686
|
-
async removeMemory(memoryId) {
|
|
50837
|
+
async removeMemory(memoryId, _when) {
|
|
50687
50838
|
backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_REMOVE, {
|
|
50688
50839
|
signalId: this.signalId,
|
|
50689
50840
|
bucketName: this.bucketName,
|
|
@@ -50729,12 +50880,13 @@ class MemoryPersistInstance {
|
|
|
50729
50880
|
initial,
|
|
50730
50881
|
});
|
|
50731
50882
|
await PersistMemoryAdapter.waitForInit(this.signalId, this.bucketName, initial);
|
|
50732
|
-
for await (const { memoryId, data: { data, index, priority } } of PersistMemoryAdapter.listMemoryData(this.signalId, this.bucketName)) {
|
|
50883
|
+
for await (const { memoryId, data: { data, index, priority, when } } of PersistMemoryAdapter.listMemoryData(this.signalId, this.bucketName)) {
|
|
50733
50884
|
this._index.upsert({
|
|
50734
50885
|
id: memoryId,
|
|
50735
50886
|
content: data,
|
|
50736
50887
|
index,
|
|
50737
50888
|
priority,
|
|
50889
|
+
when,
|
|
50738
50890
|
});
|
|
50739
50891
|
}
|
|
50740
50892
|
}
|
|
@@ -50743,69 +50895,79 @@ class MemoryPersistInstance {
|
|
|
50743
50895
|
* @param memoryId - Unique entry identifier
|
|
50744
50896
|
* @param value - Value to persist and index
|
|
50745
50897
|
* @param index - BM25 index string; defaults to JSON.stringify(value)
|
|
50898
|
+
* @param when - Logical timestamp this entry belongs to (look-ahead guard)
|
|
50746
50899
|
*/
|
|
50747
|
-
async writeMemory(memoryId, value, index
|
|
50900
|
+
async writeMemory(memoryId, value, index, when) {
|
|
50748
50901
|
backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_WRITE, {
|
|
50749
50902
|
signalId: this.signalId,
|
|
50750
50903
|
bucketName: this.bucketName,
|
|
50751
50904
|
memoryId,
|
|
50752
50905
|
});
|
|
50753
50906
|
const priority = Date.now();
|
|
50754
|
-
|
|
50907
|
+
const whenMs = when.getTime();
|
|
50908
|
+
await PersistMemoryAdapter.writeMemoryData({ data: value, priority, removed: false, index, when: whenMs }, this.signalId, this.bucketName, memoryId);
|
|
50755
50909
|
this._index.upsert({
|
|
50756
50910
|
id: memoryId,
|
|
50757
50911
|
content: value,
|
|
50758
50912
|
index,
|
|
50759
50913
|
priority,
|
|
50914
|
+
when: whenMs,
|
|
50760
50915
|
});
|
|
50761
50916
|
}
|
|
50762
50917
|
/**
|
|
50763
50918
|
* Read a single entry from disk.
|
|
50919
|
+
* Behaves as not-found if the stored `when` is greater than the requested `when`.
|
|
50764
50920
|
* @param memoryId - Unique entry identifier
|
|
50921
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50765
50922
|
* @returns Entry value
|
|
50766
|
-
* @throws Error if entry not found
|
|
50923
|
+
* @throws Error if entry not found (or shadowed by look-ahead)
|
|
50767
50924
|
*/
|
|
50768
|
-
async readMemory(memoryId) {
|
|
50925
|
+
async readMemory(memoryId, when) {
|
|
50769
50926
|
backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_READ, {
|
|
50770
50927
|
signalId: this.signalId,
|
|
50771
50928
|
bucketName: this.bucketName,
|
|
50772
50929
|
memoryId,
|
|
50773
50930
|
});
|
|
50774
50931
|
const data = await PersistMemoryAdapter.readMemoryData(this.signalId, this.bucketName, memoryId);
|
|
50775
|
-
if (!data) {
|
|
50932
|
+
if (!data || data.when > when.getTime()) {
|
|
50776
50933
|
throw new Error(`MemoryPersistInstance value not found memoryId=${memoryId}`);
|
|
50777
50934
|
}
|
|
50778
50935
|
return data.data;
|
|
50779
50936
|
}
|
|
50780
50937
|
/**
|
|
50781
50938
|
* Search entries using BM25 index rebuilt from disk on init.
|
|
50939
|
+
* Filters out entries whose `when` is greater than the requested `when`.
|
|
50782
50940
|
* @param query - Search query string
|
|
50941
|
+
* @param when - Logical timestamp at which the search is happening (look-ahead guard)
|
|
50783
50942
|
* @returns Matching entries sorted by relevance score
|
|
50784
50943
|
*/
|
|
50785
|
-
async searchMemory(query, settings) {
|
|
50944
|
+
async searchMemory(query, when, settings) {
|
|
50786
50945
|
backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_SEARCH, {
|
|
50787
50946
|
signalId: this.signalId,
|
|
50788
50947
|
bucketName: this.bucketName,
|
|
50789
50948
|
query,
|
|
50790
50949
|
});
|
|
50791
|
-
return this._index.search(query, settings).map(SEARCH_MEMORY_FN);
|
|
50950
|
+
return this._index.search(query, when.getTime(), settings).map(SEARCH_MEMORY_FN);
|
|
50792
50951
|
}
|
|
50793
50952
|
/**
|
|
50794
50953
|
* List all entries from the in-memory index (populated from disk on init).
|
|
50954
|
+
* Filters out entries whose `when` is greater than the requested `when`.
|
|
50955
|
+
* @param when - Logical timestamp at which the list is happening (look-ahead guard)
|
|
50795
50956
|
* @returns Array of all stored entries
|
|
50796
50957
|
*/
|
|
50797
|
-
async listMemory() {
|
|
50958
|
+
async listMemory(when) {
|
|
50798
50959
|
backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_LIST, {
|
|
50799
50960
|
signalId: this.signalId,
|
|
50800
50961
|
bucketName: this.bucketName,
|
|
50801
50962
|
});
|
|
50802
|
-
return this._index.list().map(LIST_MEMORY_FN);
|
|
50963
|
+
return this._index.list(when.getTime()).map(LIST_MEMORY_FN);
|
|
50803
50964
|
}
|
|
50804
50965
|
/**
|
|
50805
50966
|
* Remove an entry from disk and from the BM25 index.
|
|
50806
50967
|
* @param memoryId - Unique entry identifier
|
|
50968
|
+
* @param when - Logical timestamp (kept for API consistency; removal is by UUID)
|
|
50807
50969
|
*/
|
|
50808
|
-
async removeMemory(memoryId) {
|
|
50970
|
+
async removeMemory(memoryId, _when) {
|
|
50809
50971
|
backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_REMOVE, {
|
|
50810
50972
|
signalId: this.signalId,
|
|
50811
50973
|
bucketName: this.bucketName,
|
|
@@ -50842,34 +51004,34 @@ class MemoryDummyInstance {
|
|
|
50842
51004
|
* No-op write - discards the value.
|
|
50843
51005
|
* @returns Promise that resolves immediately
|
|
50844
51006
|
*/
|
|
50845
|
-
async writeMemory() {
|
|
51007
|
+
async writeMemory(_memoryId, _value, _description, _when) {
|
|
50846
51008
|
}
|
|
50847
51009
|
/**
|
|
50848
51010
|
* No-op read - always throws.
|
|
50849
51011
|
* @throws Error always
|
|
50850
51012
|
*/
|
|
50851
|
-
async readMemory(_memoryId) {
|
|
51013
|
+
async readMemory(_memoryId, _when) {
|
|
50852
51014
|
throw new Error("MemoryDummyInstance: readMemory not supported");
|
|
50853
51015
|
}
|
|
50854
51016
|
/**
|
|
50855
51017
|
* No-op search - returns empty array.
|
|
50856
51018
|
* @returns Empty array
|
|
50857
51019
|
*/
|
|
50858
|
-
async searchMemory() {
|
|
51020
|
+
async searchMemory(_query, _when, _settings) {
|
|
50859
51021
|
return [];
|
|
50860
51022
|
}
|
|
50861
51023
|
/**
|
|
50862
51024
|
* No-op list - returns empty array.
|
|
50863
51025
|
* @returns Empty array
|
|
50864
51026
|
*/
|
|
50865
|
-
async listMemory() {
|
|
51027
|
+
async listMemory(_when) {
|
|
50866
51028
|
return [];
|
|
50867
51029
|
}
|
|
50868
51030
|
/**
|
|
50869
51031
|
* No-op remove.
|
|
50870
51032
|
* @returns Promise that resolves immediately
|
|
50871
51033
|
*/
|
|
50872
|
-
async removeMemory() {
|
|
51034
|
+
async removeMemory(_memoryId, _when) {
|
|
50873
51035
|
}
|
|
50874
51036
|
/** No-op. */
|
|
50875
51037
|
dispose() {
|
|
@@ -50913,6 +51075,7 @@ class MemoryBacktestAdapter {
|
|
|
50913
51075
|
* @param dto.signalId - Signal identifier
|
|
50914
51076
|
* @param dto.bucketName - Bucket name
|
|
50915
51077
|
* @param dto.description - BM25 index string; defaults to JSON.stringify(value)
|
|
51078
|
+
* @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
|
|
50916
51079
|
*/
|
|
50917
51080
|
this.writeMemory = async (dto) => {
|
|
50918
51081
|
backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_WRITE, {
|
|
@@ -50924,13 +51087,14 @@ class MemoryBacktestAdapter {
|
|
|
50924
51087
|
const isInitial = !this.getInstance.has(key);
|
|
50925
51088
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
50926
51089
|
await instance.waitForInit(isInitial);
|
|
50927
|
-
return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
|
|
51090
|
+
return await instance.writeMemory(dto.memoryId, dto.value, dto.description, dto.when);
|
|
50928
51091
|
};
|
|
50929
51092
|
/**
|
|
50930
51093
|
* Search memory using BM25 full-text scoring.
|
|
50931
51094
|
* @param dto.query - Search query string
|
|
50932
51095
|
* @param dto.signalId - Signal identifier
|
|
50933
51096
|
* @param dto.bucketName - Bucket name
|
|
51097
|
+
* @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
|
|
50934
51098
|
* @returns Matching entries sorted by relevance score
|
|
50935
51099
|
*/
|
|
50936
51100
|
this.searchMemory = async (dto) => {
|
|
@@ -50943,12 +51107,13 @@ class MemoryBacktestAdapter {
|
|
|
50943
51107
|
const isInitial = !this.getInstance.has(key);
|
|
50944
51108
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
50945
51109
|
await instance.waitForInit(isInitial);
|
|
50946
|
-
return await instance.searchMemory(dto.query, dto.settings);
|
|
51110
|
+
return await instance.searchMemory(dto.query, dto.when, dto.settings);
|
|
50947
51111
|
};
|
|
50948
51112
|
/**
|
|
50949
51113
|
* List all entries in memory.
|
|
50950
51114
|
* @param dto.signalId - Signal identifier
|
|
50951
51115
|
* @param dto.bucketName - Bucket name
|
|
51116
|
+
* @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
|
|
50952
51117
|
* @returns Array of all stored entries
|
|
50953
51118
|
*/
|
|
50954
51119
|
this.listMemory = async (dto) => {
|
|
@@ -50960,13 +51125,14 @@ class MemoryBacktestAdapter {
|
|
|
50960
51125
|
const isInitial = !this.getInstance.has(key);
|
|
50961
51126
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
50962
51127
|
await instance.waitForInit(isInitial);
|
|
50963
|
-
return await instance.listMemory();
|
|
51128
|
+
return await instance.listMemory(dto.when);
|
|
50964
51129
|
};
|
|
50965
51130
|
/**
|
|
50966
51131
|
* Remove an entry from memory.
|
|
50967
51132
|
* @param dto.memoryId - Unique entry identifier
|
|
50968
51133
|
* @param dto.signalId - Signal identifier
|
|
50969
51134
|
* @param dto.bucketName - Bucket name
|
|
51135
|
+
* @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
|
|
50970
51136
|
*/
|
|
50971
51137
|
this.removeMemory = async (dto) => {
|
|
50972
51138
|
backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_REMOVE, {
|
|
@@ -50978,13 +51144,14 @@ class MemoryBacktestAdapter {
|
|
|
50978
51144
|
const isInitial = !this.getInstance.has(key);
|
|
50979
51145
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
50980
51146
|
await instance.waitForInit(isInitial);
|
|
50981
|
-
return await instance.removeMemory(dto.memoryId);
|
|
51147
|
+
return await instance.removeMemory(dto.memoryId, dto.when);
|
|
50982
51148
|
};
|
|
50983
51149
|
/**
|
|
50984
51150
|
* Read a single entry from memory.
|
|
50985
51151
|
* @param dto.memoryId - Unique entry identifier
|
|
50986
51152
|
* @param dto.signalId - Signal identifier
|
|
50987
51153
|
* @param dto.bucketName - Bucket name
|
|
51154
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50988
51155
|
* @returns Entry value
|
|
50989
51156
|
* @throws Error if entry not found
|
|
50990
51157
|
*/
|
|
@@ -50998,7 +51165,7 @@ class MemoryBacktestAdapter {
|
|
|
50998
51165
|
const isInitial = !this.getInstance.has(key);
|
|
50999
51166
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51000
51167
|
await instance.waitForInit(isInitial);
|
|
51001
|
-
return await instance.readMemory(dto.memoryId);
|
|
51168
|
+
return await instance.readMemory(dto.memoryId, dto.when);
|
|
51002
51169
|
};
|
|
51003
51170
|
/**
|
|
51004
51171
|
* Switches to in-memory BM25 adapter (default).
|
|
@@ -51080,6 +51247,7 @@ class MemoryLiveAdapter {
|
|
|
51080
51247
|
* @param dto.signalId - Signal identifier
|
|
51081
51248
|
* @param dto.bucketName - Bucket name
|
|
51082
51249
|
* @param dto.description - BM25 index string; defaults to JSON.stringify(value)
|
|
51250
|
+
* @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
|
|
51083
51251
|
*/
|
|
51084
51252
|
this.writeMemory = async (dto) => {
|
|
51085
51253
|
backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_WRITE, {
|
|
@@ -51091,13 +51259,14 @@ class MemoryLiveAdapter {
|
|
|
51091
51259
|
const isInitial = !this.getInstance.has(key);
|
|
51092
51260
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51093
51261
|
await instance.waitForInit(isInitial);
|
|
51094
|
-
return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
|
|
51262
|
+
return await instance.writeMemory(dto.memoryId, dto.value, dto.description, dto.when);
|
|
51095
51263
|
};
|
|
51096
51264
|
/**
|
|
51097
51265
|
* Search memory using BM25 full-text scoring.
|
|
51098
51266
|
* @param dto.query - Search query string
|
|
51099
51267
|
* @param dto.signalId - Signal identifier
|
|
51100
51268
|
* @param dto.bucketName - Bucket name
|
|
51269
|
+
* @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
|
|
51101
51270
|
* @returns Matching entries sorted by relevance score
|
|
51102
51271
|
*/
|
|
51103
51272
|
this.searchMemory = async (dto) => {
|
|
@@ -51110,12 +51279,13 @@ class MemoryLiveAdapter {
|
|
|
51110
51279
|
const isInitial = !this.getInstance.has(key);
|
|
51111
51280
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51112
51281
|
await instance.waitForInit(isInitial);
|
|
51113
|
-
return await instance.searchMemory(dto.query, dto.settings);
|
|
51282
|
+
return await instance.searchMemory(dto.query, dto.when, dto.settings);
|
|
51114
51283
|
};
|
|
51115
51284
|
/**
|
|
51116
51285
|
* List all entries in memory.
|
|
51117
51286
|
* @param dto.signalId - Signal identifier
|
|
51118
51287
|
* @param dto.bucketName - Bucket name
|
|
51288
|
+
* @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
|
|
51119
51289
|
* @returns Array of all stored entries
|
|
51120
51290
|
*/
|
|
51121
51291
|
this.listMemory = async (dto) => {
|
|
@@ -51127,13 +51297,14 @@ class MemoryLiveAdapter {
|
|
|
51127
51297
|
const isInitial = !this.getInstance.has(key);
|
|
51128
51298
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51129
51299
|
await instance.waitForInit(isInitial);
|
|
51130
|
-
return await instance.listMemory();
|
|
51300
|
+
return await instance.listMemory(dto.when);
|
|
51131
51301
|
};
|
|
51132
51302
|
/**
|
|
51133
51303
|
* Remove an entry from memory.
|
|
51134
51304
|
* @param dto.memoryId - Unique entry identifier
|
|
51135
51305
|
* @param dto.signalId - Signal identifier
|
|
51136
51306
|
* @param dto.bucketName - Bucket name
|
|
51307
|
+
* @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
|
|
51137
51308
|
*/
|
|
51138
51309
|
this.removeMemory = async (dto) => {
|
|
51139
51310
|
backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_REMOVE, {
|
|
@@ -51145,13 +51316,14 @@ class MemoryLiveAdapter {
|
|
|
51145
51316
|
const isInitial = !this.getInstance.has(key);
|
|
51146
51317
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51147
51318
|
await instance.waitForInit(isInitial);
|
|
51148
|
-
return await instance.removeMemory(dto.memoryId);
|
|
51319
|
+
return await instance.removeMemory(dto.memoryId, dto.when);
|
|
51149
51320
|
};
|
|
51150
51321
|
/**
|
|
51151
51322
|
* Read a single entry from memory.
|
|
51152
51323
|
* @param dto.memoryId - Unique entry identifier
|
|
51153
51324
|
* @param dto.signalId - Signal identifier
|
|
51154
51325
|
* @param dto.bucketName - Bucket name
|
|
51326
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
51155
51327
|
* @returns Entry value
|
|
51156
51328
|
* @throws Error if entry not found
|
|
51157
51329
|
*/
|
|
@@ -51165,7 +51337,7 @@ class MemoryLiveAdapter {
|
|
|
51165
51337
|
const isInitial = !this.getInstance.has(key);
|
|
51166
51338
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51167
51339
|
await instance.waitForInit(isInitial);
|
|
51168
|
-
return await instance.readMemory(dto.memoryId);
|
|
51340
|
+
return await instance.readMemory(dto.memoryId, dto.when);
|
|
51169
51341
|
};
|
|
51170
51342
|
/**
|
|
51171
51343
|
* Switches to in-memory BM25 adapter.
|
|
@@ -51264,6 +51436,7 @@ class MemoryAdapter {
|
|
|
51264
51436
|
* @param dto.bucketName - Bucket name
|
|
51265
51437
|
* @param dto.description - BM25 index string; defaults to JSON.stringify(value)
|
|
51266
51438
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
51439
|
+
* @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
|
|
51267
51440
|
*/
|
|
51268
51441
|
this.writeMemory = async (dto) => {
|
|
51269
51442
|
if (!this.enable.hasValue()) {
|
|
@@ -51287,6 +51460,7 @@ class MemoryAdapter {
|
|
|
51287
51460
|
* @param dto.signalId - Signal identifier
|
|
51288
51461
|
* @param dto.bucketName - Bucket name
|
|
51289
51462
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
51463
|
+
* @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
|
|
51290
51464
|
* @returns Matching entries sorted by relevance score
|
|
51291
51465
|
*/
|
|
51292
51466
|
this.searchMemory = async (dto) => {
|
|
@@ -51310,6 +51484,7 @@ class MemoryAdapter {
|
|
|
51310
51484
|
* @param dto.signalId - Signal identifier
|
|
51311
51485
|
* @param dto.bucketName - Bucket name
|
|
51312
51486
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
51487
|
+
* @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
|
|
51313
51488
|
* @returns Array of all stored entries
|
|
51314
51489
|
*/
|
|
51315
51490
|
this.listMemory = async (dto) => {
|
|
@@ -51333,6 +51508,7 @@ class MemoryAdapter {
|
|
|
51333
51508
|
* @param dto.signalId - Signal identifier
|
|
51334
51509
|
* @param dto.bucketName - Bucket name
|
|
51335
51510
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
51511
|
+
* @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
|
|
51336
51512
|
*/
|
|
51337
51513
|
this.removeMemory = async (dto) => {
|
|
51338
51514
|
if (!this.enable.hasValue()) {
|
|
@@ -51356,6 +51532,7 @@ class MemoryAdapter {
|
|
|
51356
51532
|
* @param dto.signalId - Signal identifier
|
|
51357
51533
|
* @param dto.bucketName - Bucket name
|
|
51358
51534
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
51535
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
51359
51536
|
* @returns Entry value
|
|
51360
51537
|
* @throws Error if entry not found
|
|
51361
51538
|
*/
|
|
@@ -51428,7 +51605,7 @@ async function writeMemory(dto) {
|
|
|
51428
51605
|
if (!MethodContextService.hasContext()) {
|
|
51429
51606
|
throw new Error("writeMemory requires a method context");
|
|
51430
51607
|
}
|
|
51431
|
-
const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
|
|
51608
|
+
const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
|
|
51432
51609
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
51433
51610
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
51434
51611
|
let signal;
|
|
@@ -51440,6 +51617,7 @@ async function writeMemory(dto) {
|
|
|
51440
51617
|
bucketName,
|
|
51441
51618
|
description,
|
|
51442
51619
|
backtest: isBacktest,
|
|
51620
|
+
when,
|
|
51443
51621
|
});
|
|
51444
51622
|
return;
|
|
51445
51623
|
}
|
|
@@ -51451,6 +51629,7 @@ async function writeMemory(dto) {
|
|
|
51451
51629
|
bucketName,
|
|
51452
51630
|
description,
|
|
51453
51631
|
backtest: isBacktest,
|
|
51632
|
+
when,
|
|
51454
51633
|
});
|
|
51455
51634
|
return;
|
|
51456
51635
|
}
|
|
@@ -51486,7 +51665,7 @@ async function readMemory(dto) {
|
|
|
51486
51665
|
if (!MethodContextService.hasContext()) {
|
|
51487
51666
|
throw new Error("readMemory requires a method context");
|
|
51488
51667
|
}
|
|
51489
|
-
const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
|
|
51668
|
+
const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
|
|
51490
51669
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
51491
51670
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
51492
51671
|
let signal;
|
|
@@ -51496,6 +51675,7 @@ async function readMemory(dto) {
|
|
|
51496
51675
|
signalId: signal.id,
|
|
51497
51676
|
bucketName,
|
|
51498
51677
|
backtest: isBacktest,
|
|
51678
|
+
when,
|
|
51499
51679
|
});
|
|
51500
51680
|
}
|
|
51501
51681
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -51504,6 +51684,7 @@ async function readMemory(dto) {
|
|
|
51504
51684
|
signalId: signal.id,
|
|
51505
51685
|
bucketName,
|
|
51506
51686
|
backtest: isBacktest,
|
|
51687
|
+
when,
|
|
51507
51688
|
});
|
|
51508
51689
|
}
|
|
51509
51690
|
throw new Error(`readMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
|
|
@@ -51538,7 +51719,7 @@ async function searchMemory(dto) {
|
|
|
51538
51719
|
if (!MethodContextService.hasContext()) {
|
|
51539
51720
|
throw new Error("searchMemory requires a method context");
|
|
51540
51721
|
}
|
|
51541
|
-
const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
|
|
51722
|
+
const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
|
|
51542
51723
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
51543
51724
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
51544
51725
|
let signal;
|
|
@@ -51548,6 +51729,7 @@ async function searchMemory(dto) {
|
|
|
51548
51729
|
signalId: signal.id,
|
|
51549
51730
|
bucketName,
|
|
51550
51731
|
backtest: isBacktest,
|
|
51732
|
+
when,
|
|
51551
51733
|
});
|
|
51552
51734
|
}
|
|
51553
51735
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -51556,6 +51738,7 @@ async function searchMemory(dto) {
|
|
|
51556
51738
|
signalId: signal.id,
|
|
51557
51739
|
bucketName,
|
|
51558
51740
|
backtest: isBacktest,
|
|
51741
|
+
when,
|
|
51559
51742
|
});
|
|
51560
51743
|
}
|
|
51561
51744
|
throw new Error(`searchMemory requires a pending or scheduled signal for symbol=${symbol} query=${query}`);
|
|
@@ -51588,7 +51771,7 @@ async function listMemory(dto) {
|
|
|
51588
51771
|
if (!MethodContextService.hasContext()) {
|
|
51589
51772
|
throw new Error("listMemory requires a method context");
|
|
51590
51773
|
}
|
|
51591
|
-
const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
|
|
51774
|
+
const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
|
|
51592
51775
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
51593
51776
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
51594
51777
|
let signal;
|
|
@@ -51597,6 +51780,7 @@ async function listMemory(dto) {
|
|
|
51597
51780
|
signalId: signal.id,
|
|
51598
51781
|
bucketName,
|
|
51599
51782
|
backtest: isBacktest,
|
|
51783
|
+
when,
|
|
51600
51784
|
});
|
|
51601
51785
|
}
|
|
51602
51786
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -51604,6 +51788,7 @@ async function listMemory(dto) {
|
|
|
51604
51788
|
signalId: signal.id,
|
|
51605
51789
|
bucketName,
|
|
51606
51790
|
backtest: isBacktest,
|
|
51791
|
+
when,
|
|
51607
51792
|
});
|
|
51608
51793
|
}
|
|
51609
51794
|
throw new Error(`listMemory requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
|
|
@@ -51638,7 +51823,7 @@ async function removeMemory(dto) {
|
|
|
51638
51823
|
if (!MethodContextService.hasContext()) {
|
|
51639
51824
|
throw new Error("removeMemory requires a method context");
|
|
51640
51825
|
}
|
|
51641
|
-
const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
|
|
51826
|
+
const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
|
|
51642
51827
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
51643
51828
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
51644
51829
|
let signal;
|
|
@@ -51648,6 +51833,7 @@ async function removeMemory(dto) {
|
|
|
51648
51833
|
signalId: signal.id,
|
|
51649
51834
|
bucketName,
|
|
51650
51835
|
backtest: isBacktest,
|
|
51836
|
+
when,
|
|
51651
51837
|
});
|
|
51652
51838
|
return;
|
|
51653
51839
|
}
|
|
@@ -51657,6 +51843,7 @@ async function removeMemory(dto) {
|
|
|
51657
51843
|
signalId: signal.id,
|
|
51658
51844
|
bucketName,
|
|
51659
51845
|
backtest: isBacktest,
|
|
51846
|
+
when,
|
|
51660
51847
|
});
|
|
51661
51848
|
return;
|
|
51662
51849
|
}
|
|
@@ -51922,6 +52109,9 @@ class DumpMemoryInstance {
|
|
|
51922
52109
|
value: { messages },
|
|
51923
52110
|
description,
|
|
51924
52111
|
backtest: this.backtest,
|
|
52112
|
+
// when=0: dumps are UI artifacts (agent transcripts, markdown reports);
|
|
52113
|
+
// they must stay visible regardless of the reader's logical time.
|
|
52114
|
+
when: new Date(0)
|
|
51925
52115
|
});
|
|
51926
52116
|
}
|
|
51927
52117
|
/**
|
|
@@ -51944,6 +52134,7 @@ class DumpMemoryInstance {
|
|
|
51944
52134
|
value: record,
|
|
51945
52135
|
description,
|
|
51946
52136
|
backtest: this.backtest,
|
|
52137
|
+
when: new Date(0)
|
|
51947
52138
|
});
|
|
51948
52139
|
}
|
|
51949
52140
|
/**
|
|
@@ -51967,6 +52158,7 @@ class DumpMemoryInstance {
|
|
|
51967
52158
|
value: { rows },
|
|
51968
52159
|
description,
|
|
51969
52160
|
backtest: this.backtest,
|
|
52161
|
+
when: new Date(0)
|
|
51970
52162
|
});
|
|
51971
52163
|
}
|
|
51972
52164
|
/**
|
|
@@ -51989,6 +52181,7 @@ class DumpMemoryInstance {
|
|
|
51989
52181
|
value: { content },
|
|
51990
52182
|
description,
|
|
51991
52183
|
backtest: this.backtest,
|
|
52184
|
+
when: new Date(0)
|
|
51992
52185
|
});
|
|
51993
52186
|
}
|
|
51994
52187
|
/**
|
|
@@ -52011,6 +52204,7 @@ class DumpMemoryInstance {
|
|
|
52011
52204
|
value: { content },
|
|
52012
52205
|
description,
|
|
52013
52206
|
backtest: this.backtest,
|
|
52207
|
+
when: new Date(0)
|
|
52014
52208
|
});
|
|
52015
52209
|
}
|
|
52016
52210
|
/**
|
|
@@ -52034,6 +52228,7 @@ class DumpMemoryInstance {
|
|
|
52034
52228
|
value: json,
|
|
52035
52229
|
description,
|
|
52036
52230
|
backtest: this.backtest,
|
|
52231
|
+
when: new Date(0)
|
|
52037
52232
|
});
|
|
52038
52233
|
}
|
|
52039
52234
|
/** Releases resources held by this instance. */
|
|
@@ -60995,8 +61190,8 @@ const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
|
|
|
60995
61190
|
const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
|
|
60996
61191
|
const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
|
|
60997
61192
|
const CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "CacheFileInstance.hasValue";
|
|
60998
|
-
const MS_PER_MINUTE$
|
|
60999
|
-
const INTERVAL_MINUTES$
|
|
61193
|
+
const MS_PER_MINUTE$2 = 60000;
|
|
61194
|
+
const INTERVAL_MINUTES$2 = {
|
|
61000
61195
|
"1m": 1,
|
|
61001
61196
|
"3m": 3,
|
|
61002
61197
|
"5m": 5,
|
|
@@ -61030,11 +61225,11 @@ const INTERVAL_MINUTES$1 = {
|
|
|
61030
61225
|
* ```
|
|
61031
61226
|
*/
|
|
61032
61227
|
const align$1 = (timestamp, interval) => {
|
|
61033
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
61228
|
+
const intervalMinutes = INTERVAL_MINUTES$2[interval];
|
|
61034
61229
|
if (!intervalMinutes) {
|
|
61035
61230
|
throw new Error(`align: unknown interval=${interval}`);
|
|
61036
61231
|
}
|
|
61037
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
61232
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
|
|
61038
61233
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
61039
61234
|
};
|
|
61040
61235
|
/**
|
|
@@ -61123,7 +61318,7 @@ class CacheFnInstance {
|
|
|
61123
61318
|
*/
|
|
61124
61319
|
this.run = (...args) => {
|
|
61125
61320
|
backtest.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
|
|
61126
|
-
const step = INTERVAL_MINUTES$
|
|
61321
|
+
const step = INTERVAL_MINUTES$2[this.interval];
|
|
61127
61322
|
{
|
|
61128
61323
|
if (!MethodContextService.hasContext()) {
|
|
61129
61324
|
throw new Error("CacheFnInstance run requires method context");
|
|
@@ -61318,7 +61513,7 @@ class CacheFileInstance {
|
|
|
61318
61513
|
*/
|
|
61319
61514
|
this.run = async (...args) => {
|
|
61320
61515
|
backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
61321
|
-
const step = INTERVAL_MINUTES$
|
|
61516
|
+
const step = INTERVAL_MINUTES$2[this.interval];
|
|
61322
61517
|
{
|
|
61323
61518
|
if (!MethodContextService.hasContext()) {
|
|
61324
61519
|
throw new Error("CacheFileInstance run requires method context");
|
|
@@ -61340,7 +61535,7 @@ class CacheFileInstance {
|
|
|
61340
61535
|
return cached.data;
|
|
61341
61536
|
}
|
|
61342
61537
|
const result = await this.fn.call(null, ...args);
|
|
61343
|
-
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
61538
|
+
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey, when);
|
|
61344
61539
|
return result;
|
|
61345
61540
|
};
|
|
61346
61541
|
/**
|
|
@@ -61629,8 +61824,8 @@ const INTERVAL_METHOD_NAME_FILE_HAS_VALUE = "IntervalUtils.file.hasValue";
|
|
|
61629
61824
|
const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
|
|
61630
61825
|
const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
|
|
61631
61826
|
const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
|
|
61632
|
-
const MS_PER_MINUTE = 60000;
|
|
61633
|
-
const INTERVAL_MINUTES = {
|
|
61827
|
+
const MS_PER_MINUTE$1 = 60000;
|
|
61828
|
+
const INTERVAL_MINUTES$1 = {
|
|
61634
61829
|
"1m": 1,
|
|
61635
61830
|
"3m": 3,
|
|
61636
61831
|
"5m": 5,
|
|
@@ -61660,11 +61855,11 @@ const INTERVAL_MINUTES = {
|
|
|
61660
61855
|
* ```
|
|
61661
61856
|
*/
|
|
61662
61857
|
const align = (timestamp, interval) => {
|
|
61663
|
-
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
61858
|
+
const intervalMinutes = INTERVAL_MINUTES$1[interval];
|
|
61664
61859
|
if (!intervalMinutes) {
|
|
61665
61860
|
throw new Error(`align: unknown interval=${interval}`);
|
|
61666
61861
|
}
|
|
61667
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
61862
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
|
|
61668
61863
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
61669
61864
|
};
|
|
61670
61865
|
/**
|
|
@@ -61739,7 +61934,7 @@ class IntervalFnInstance {
|
|
|
61739
61934
|
*/
|
|
61740
61935
|
this.run = (...args) => {
|
|
61741
61936
|
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
|
|
61742
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
61937
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
61743
61938
|
{
|
|
61744
61939
|
if (!MethodContextService.hasContext()) {
|
|
61745
61940
|
throw new Error("IntervalFnInstance run requires method context");
|
|
@@ -61906,7 +62101,7 @@ class IntervalFileInstance {
|
|
|
61906
62101
|
*/
|
|
61907
62102
|
this.run = async (...args) => {
|
|
61908
62103
|
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
61909
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
62104
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
61910
62105
|
{
|
|
61911
62106
|
if (!MethodContextService.hasContext()) {
|
|
61912
62107
|
throw new Error("IntervalFileInstance run requires method context");
|
|
@@ -61929,7 +62124,7 @@ class IntervalFileInstance {
|
|
|
61929
62124
|
}
|
|
61930
62125
|
const result = await this.fn.call(null, ...args);
|
|
61931
62126
|
if (result !== null) {
|
|
61932
|
-
await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
62127
|
+
await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, when, removed: false }, bucket, entityKey, when);
|
|
61933
62128
|
}
|
|
61934
62129
|
return result;
|
|
61935
62130
|
};
|
|
@@ -63053,6 +63248,31 @@ class ActionBase {
|
|
|
63053
63248
|
// @ts-ignore
|
|
63054
63249
|
ActionBase = functoolsKit.makeExtendable(ActionBase);
|
|
63055
63250
|
|
|
63251
|
+
const MS_PER_MINUTE = 60000;
|
|
63252
|
+
const INTERVAL_MINUTES = {
|
|
63253
|
+
"1m": 1,
|
|
63254
|
+
"3m": 3,
|
|
63255
|
+
"5m": 5,
|
|
63256
|
+
"15m": 15,
|
|
63257
|
+
"30m": 30,
|
|
63258
|
+
"1h": 60,
|
|
63259
|
+
"2h": 120,
|
|
63260
|
+
"4h": 240,
|
|
63261
|
+
"6h": 360,
|
|
63262
|
+
"8h": 480,
|
|
63263
|
+
"1d": 1440,
|
|
63264
|
+
};
|
|
63265
|
+
/**
|
|
63266
|
+
* Returns the step in milliseconds for a given candle interval.
|
|
63267
|
+
* For example, for "15m" interval, it returns 900000 (15 * 60 * 1000).
|
|
63268
|
+
*
|
|
63269
|
+
* @param interval - Candle interval (e.g., "1m", "15m", "1h")
|
|
63270
|
+
* @returns Step in milliseconds corresponding to the interval
|
|
63271
|
+
*/
|
|
63272
|
+
const intervalStepMs = (interval) => {
|
|
63273
|
+
return INTERVAL_MINUTES[interval] * MS_PER_MINUTE;
|
|
63274
|
+
};
|
|
63275
|
+
|
|
63056
63276
|
/**
|
|
63057
63277
|
* Rounds a price to the appropriate precision based on the tick size.
|
|
63058
63278
|
*
|
|
@@ -63511,6 +63731,7 @@ exports.getWalkerSchema = getWalkerSchema;
|
|
|
63511
63731
|
exports.hasNoPendingSignal = hasNoPendingSignal;
|
|
63512
63732
|
exports.hasNoScheduledSignal = hasNoScheduledSignal;
|
|
63513
63733
|
exports.hasTradeContext = hasTradeContext;
|
|
63734
|
+
exports.intervalStepMs = intervalStepMs;
|
|
63514
63735
|
exports.investedCostToPercent = investedCostToPercent;
|
|
63515
63736
|
exports.lib = backtest;
|
|
63516
63737
|
exports.listExchangeSchema = listExchangeSchema;
|