backtest-kit 9.0.0 → 9.0.2
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 +455 -230
- package/build/index.mjs +455 -231
- package/package.json +1 -1
- package/types.d.ts +172 -65
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`.
|
|
@@ -3581,7 +3581,7 @@ class PersistMemoryInstance {
|
|
|
3581
3581
|
* @param memoryId - Memory entry identifier
|
|
3582
3582
|
* @returns Promise that resolves when write is complete
|
|
3583
3583
|
*/
|
|
3584
|
-
async writeMemoryData(data, memoryId) {
|
|
3584
|
+
async writeMemoryData(data, memoryId, _when) {
|
|
3585
3585
|
await this._storage.writeValue(memoryId, data);
|
|
3586
3586
|
}
|
|
3587
3587
|
/**
|
|
@@ -3646,7 +3646,7 @@ class PersistMemoryDummyInstance {
|
|
|
3646
3646
|
* No-op write (discards entry).
|
|
3647
3647
|
* @returns Promise that resolves immediately
|
|
3648
3648
|
*/
|
|
3649
|
-
async writeMemoryData(_data, _memoryId) { }
|
|
3649
|
+
async writeMemoryData(_data, _memoryId, _when) { }
|
|
3650
3650
|
/**
|
|
3651
3651
|
* No-op remove.
|
|
3652
3652
|
* @returns Promise that resolves immediately
|
|
@@ -3739,19 +3739,20 @@ class PersistMemoryUtils {
|
|
|
3739
3739
|
* Writes a memory entry for the given context.
|
|
3740
3740
|
* Lazily initializes the instance on first access.
|
|
3741
3741
|
*
|
|
3742
|
-
* @param data - Entry data to persist
|
|
3742
|
+
* @param data - Entry data to persist (already carries `data.when`)
|
|
3743
3743
|
* @param signalId - Signal identifier
|
|
3744
3744
|
* @param bucketName - Bucket name
|
|
3745
3745
|
* @param memoryId - Memory entry identifier
|
|
3746
|
+
* @param when - Logical timestamp this entry belongs to (duplicates `data.when` for API consistency)
|
|
3746
3747
|
* @returns Promise that resolves when write is complete
|
|
3747
3748
|
*/
|
|
3748
|
-
this.writeMemoryData = async (data, signalId, bucketName, memoryId) => {
|
|
3749
|
+
this.writeMemoryData = async (data, signalId, bucketName, memoryId, when) => {
|
|
3749
3750
|
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName, memoryId });
|
|
3750
3751
|
const key = `${signalId}:${bucketName}`;
|
|
3751
3752
|
const isInitial = !this.getMemoryStorage.has(key);
|
|
3752
3753
|
const instance = this.getMemoryStorage(signalId, bucketName);
|
|
3753
3754
|
await instance.waitForInit(isInitial);
|
|
3754
|
-
return instance.writeMemoryData(data, memoryId);
|
|
3755
|
+
return instance.writeMemoryData(data, memoryId, when);
|
|
3755
3756
|
};
|
|
3756
3757
|
/**
|
|
3757
3758
|
* Soft-deletes a memory entry for the given context.
|
|
@@ -3915,7 +3916,7 @@ class PersistRecentInstance {
|
|
|
3915
3916
|
* @param signalRow - Recent signal data to persist
|
|
3916
3917
|
* @returns Promise that resolves when write is complete
|
|
3917
3918
|
*/
|
|
3918
|
-
async writeRecentData(signalRow) {
|
|
3919
|
+
async writeRecentData(signalRow, _when) {
|
|
3919
3920
|
await this._storage.writeValue(this.symbol, signalRow);
|
|
3920
3921
|
}
|
|
3921
3922
|
}
|
|
@@ -3943,7 +3944,7 @@ class PersistRecentDummyInstance {
|
|
|
3943
3944
|
* No-op write (discards recent signal).
|
|
3944
3945
|
* @returns Promise that resolves immediately
|
|
3945
3946
|
*/
|
|
3946
|
-
async writeRecentData(_signalRow) { }
|
|
3947
|
+
async writeRecentData(_signalRow, _when) { }
|
|
3947
3948
|
}
|
|
3948
3949
|
/**
|
|
3949
3950
|
* Utility class for managing recent signal persistence.
|
|
@@ -3990,21 +3991,22 @@ class PersistRecentUtils {
|
|
|
3990
3991
|
* Writes the latest recent signal for the given context.
|
|
3991
3992
|
* Lazily initializes the instance on first access.
|
|
3992
3993
|
*
|
|
3993
|
-
* @param signalRow - Recent signal data to persist
|
|
3994
|
+
* @param signalRow - Recent signal data to persist (already carries `signalRow.timestamp`)
|
|
3994
3995
|
* @param symbol - Trading pair symbol
|
|
3995
3996
|
* @param strategyName - Strategy identifier
|
|
3996
3997
|
* @param exchangeName - Exchange identifier
|
|
3997
3998
|
* @param frameName - Frame identifier (may be empty)
|
|
3998
3999
|
* @param backtest - True for backtest mode, false for live mode
|
|
4000
|
+
* @param when - Logical timestamp this signal belongs to (duplicates `signalRow.timestamp` for API consistency)
|
|
3999
4001
|
* @returns Promise that resolves when write is complete
|
|
4000
4002
|
*/
|
|
4001
|
-
this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
4003
|
+
this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest, when) => {
|
|
4002
4004
|
LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
|
|
4003
4005
|
const key = this.createKey(symbol, strategyName, exchangeName, frameName, backtest);
|
|
4004
4006
|
const isInitial = !this.getStorage.has(key);
|
|
4005
4007
|
const instance = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
4006
4008
|
await instance.waitForInit(isInitial);
|
|
4007
|
-
return instance.writeRecentData(signalRow);
|
|
4009
|
+
return instance.writeRecentData(signalRow, when);
|
|
4008
4010
|
};
|
|
4009
4011
|
}
|
|
4010
4012
|
/**
|
|
@@ -4118,7 +4120,7 @@ class PersistStateInstance {
|
|
|
4118
4120
|
* @param data - State data to persist
|
|
4119
4121
|
* @returns Promise that resolves when write is complete
|
|
4120
4122
|
*/
|
|
4121
|
-
async writeStateData(data) {
|
|
4123
|
+
async writeStateData(data, _when) {
|
|
4122
4124
|
await this._storage.writeValue(this.bucketName, data);
|
|
4123
4125
|
}
|
|
4124
4126
|
/**
|
|
@@ -4151,7 +4153,7 @@ class PersistStateDummyInstance {
|
|
|
4151
4153
|
* No-op write (discards state).
|
|
4152
4154
|
* @returns Promise that resolves immediately
|
|
4153
4155
|
*/
|
|
4154
|
-
async writeStateData(_data) { }
|
|
4156
|
+
async writeStateData(_data, _when) { }
|
|
4155
4157
|
/**
|
|
4156
4158
|
* No-op dispose.
|
|
4157
4159
|
*/
|
|
@@ -4216,18 +4218,19 @@ class PersistStateUtils {
|
|
|
4216
4218
|
* Writes state for the given context.
|
|
4217
4219
|
* Lazily initializes the instance on first access.
|
|
4218
4220
|
*
|
|
4219
|
-
* @param data - State data to persist
|
|
4221
|
+
* @param data - State data to persist (already carries `data.when`)
|
|
4220
4222
|
* @param signalId - Signal identifier
|
|
4221
4223
|
* @param bucketName - Bucket name
|
|
4224
|
+
* @param when - Logical timestamp this value belongs to (duplicates `data.when` for API consistency)
|
|
4222
4225
|
* @returns Promise that resolves when write is complete
|
|
4223
4226
|
*/
|
|
4224
|
-
this.writeStateData = async (data, signalId, bucketName) => {
|
|
4227
|
+
this.writeStateData = async (data, signalId, bucketName, when) => {
|
|
4225
4228
|
LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName });
|
|
4226
4229
|
const key = `${signalId}:${bucketName}`;
|
|
4227
4230
|
const isInitial = !this.getStateStorage.has(key);
|
|
4228
4231
|
const instance = this.getStateStorage(signalId, bucketName);
|
|
4229
4232
|
await instance.waitForInit(isInitial);
|
|
4230
|
-
return instance.writeStateData(data);
|
|
4233
|
+
return instance.writeStateData(data, when);
|
|
4231
4234
|
};
|
|
4232
4235
|
/**
|
|
4233
4236
|
* Switches to PersistStateDummyInstance (all operations are no-ops).
|
|
@@ -4337,7 +4340,7 @@ class PersistSessionInstance {
|
|
|
4337
4340
|
* @param data - Session data to persist
|
|
4338
4341
|
* @returns Promise that resolves when write is complete
|
|
4339
4342
|
*/
|
|
4340
|
-
async writeSessionData(data) {
|
|
4343
|
+
async writeSessionData(data, _when) {
|
|
4341
4344
|
await this._storage.writeValue(this.frameName, data);
|
|
4342
4345
|
}
|
|
4343
4346
|
/**
|
|
@@ -4370,7 +4373,7 @@ class PersistSessionDummyInstance {
|
|
|
4370
4373
|
* No-op write (discards session data).
|
|
4371
4374
|
* @returns Promise that resolves immediately
|
|
4372
4375
|
*/
|
|
4373
|
-
async writeSessionData(_data) { }
|
|
4376
|
+
async writeSessionData(_data, _when) { }
|
|
4374
4377
|
/**
|
|
4375
4378
|
* No-op dispose.
|
|
4376
4379
|
*/
|
|
@@ -4438,19 +4441,20 @@ class PersistSessionUtils {
|
|
|
4438
4441
|
* Writes session data for the given context.
|
|
4439
4442
|
* Lazily initializes the instance on first access.
|
|
4440
4443
|
*
|
|
4441
|
-
* @param data - Session data to persist
|
|
4444
|
+
* @param data - Session data to persist (already carries `data.when`)
|
|
4442
4445
|
* @param strategyName - Strategy identifier
|
|
4443
4446
|
* @param exchangeName - Exchange identifier
|
|
4444
4447
|
* @param frameName - Frame identifier
|
|
4448
|
+
* @param when - Logical timestamp this value belongs to (duplicates `data.when` for API consistency)
|
|
4445
4449
|
* @returns Promise that resolves when write is complete
|
|
4446
4450
|
*/
|
|
4447
|
-
this.writeSessionData = async (data, strategyName, exchangeName, frameName) => {
|
|
4451
|
+
this.writeSessionData = async (data, strategyName, exchangeName, frameName, when) => {
|
|
4448
4452
|
LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_WRITE_DATA, { strategyName, exchangeName, frameName });
|
|
4449
4453
|
const key = `${strategyName}:${exchangeName}:${frameName}`;
|
|
4450
4454
|
const isInitial = !this.getSessionStorage.has(key);
|
|
4451
4455
|
const instance = this.getSessionStorage(strategyName, exchangeName, frameName);
|
|
4452
4456
|
await instance.waitForInit(isInitial);
|
|
4453
|
-
return instance.writeSessionData(data);
|
|
4457
|
+
return instance.writeSessionData(data, when);
|
|
4454
4458
|
};
|
|
4455
4459
|
/**
|
|
4456
4460
|
* Switches to PersistSessionDummyInstance (all operations are no-ops).
|
|
@@ -4629,8 +4633,8 @@ class CandleUtils {
|
|
|
4629
4633
|
}
|
|
4630
4634
|
const Candle = new CandleUtils();
|
|
4631
4635
|
|
|
4632
|
-
const MS_PER_MINUTE$
|
|
4633
|
-
const INTERVAL_MINUTES$
|
|
4636
|
+
const MS_PER_MINUTE$7 = 60000;
|
|
4637
|
+
const INTERVAL_MINUTES$9 = {
|
|
4634
4638
|
"1m": 1,
|
|
4635
4639
|
"3m": 3,
|
|
4636
4640
|
"5m": 5,
|
|
@@ -4661,7 +4665,7 @@ const INTERVAL_MINUTES$8 = {
|
|
|
4661
4665
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
4662
4666
|
*/
|
|
4663
4667
|
const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
|
|
4664
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
4668
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$7;
|
|
4665
4669
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
4666
4670
|
};
|
|
4667
4671
|
/**
|
|
@@ -4815,9 +4819,9 @@ const WRITE_CANDLES_CACHE_FN$1 = functoolsKit.trycatch(functoolsKit.queued(async
|
|
|
4815
4819
|
* @returns Promise resolving to array of candle data
|
|
4816
4820
|
*/
|
|
4817
4821
|
const GET_CANDLES_FN$1 = async (dto, since, self) => {
|
|
4818
|
-
const step = INTERVAL_MINUTES$
|
|
4822
|
+
const step = INTERVAL_MINUTES$9[dto.interval];
|
|
4819
4823
|
const sinceTimestamp = since.getTime();
|
|
4820
|
-
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$
|
|
4824
|
+
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$7;
|
|
4821
4825
|
await Candle.acquireLock(`ClientExchange GET_CANDLES_FN symbol=${dto.symbol} interval=${dto.interval} limit=${dto.limit}`);
|
|
4822
4826
|
try {
|
|
4823
4827
|
// Try to read from cache first
|
|
@@ -4931,11 +4935,11 @@ class ClientExchange {
|
|
|
4931
4935
|
interval,
|
|
4932
4936
|
limit,
|
|
4933
4937
|
});
|
|
4934
|
-
const step = INTERVAL_MINUTES$
|
|
4938
|
+
const step = INTERVAL_MINUTES$9[interval];
|
|
4935
4939
|
if (!step) {
|
|
4936
4940
|
throw new Error(`ClientExchange unknown interval=${interval}`);
|
|
4937
4941
|
}
|
|
4938
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
4942
|
+
const stepMs = step * MS_PER_MINUTE$7;
|
|
4939
4943
|
// Align when down to interval boundary
|
|
4940
4944
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
4941
4945
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
@@ -5012,11 +5016,11 @@ class ClientExchange {
|
|
|
5012
5016
|
if (!this.params.execution.context.backtest) {
|
|
5013
5017
|
throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
|
|
5014
5018
|
}
|
|
5015
|
-
const step = INTERVAL_MINUTES$
|
|
5019
|
+
const step = INTERVAL_MINUTES$9[interval];
|
|
5016
5020
|
if (!step) {
|
|
5017
5021
|
throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
|
|
5018
5022
|
}
|
|
5019
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
5023
|
+
const stepMs = step * MS_PER_MINUTE$7;
|
|
5020
5024
|
const now = Date.now();
|
|
5021
5025
|
// Align when down to interval boundary
|
|
5022
5026
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
@@ -5201,11 +5205,11 @@ class ClientExchange {
|
|
|
5201
5205
|
sDate,
|
|
5202
5206
|
eDate,
|
|
5203
5207
|
});
|
|
5204
|
-
const step = INTERVAL_MINUTES$
|
|
5208
|
+
const step = INTERVAL_MINUTES$9[interval];
|
|
5205
5209
|
if (!step) {
|
|
5206
5210
|
throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
|
|
5207
5211
|
}
|
|
5208
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
5212
|
+
const stepMs = step * MS_PER_MINUTE$7;
|
|
5209
5213
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
5210
5214
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
5211
5215
|
let sinceTimestamp;
|
|
@@ -5334,7 +5338,7 @@ class ClientExchange {
|
|
|
5334
5338
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
5335
5339
|
const to = new Date(alignedTo);
|
|
5336
5340
|
const from = new Date(alignedTo -
|
|
5337
|
-
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
5341
|
+
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$7);
|
|
5338
5342
|
return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
|
|
5339
5343
|
}
|
|
5340
5344
|
/**
|
|
@@ -5363,7 +5367,7 @@ class ClientExchange {
|
|
|
5363
5367
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
5364
5368
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
5365
5369
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, 1);
|
|
5366
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
5370
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$7 - MS_PER_MINUTE$7;
|
|
5367
5371
|
// No limit: fetch a single window and return as-is
|
|
5368
5372
|
if (limit === undefined) {
|
|
5369
5373
|
const to = new Date(alignedTo);
|
|
@@ -6317,7 +6321,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
|
|
|
6317
6321
|
}
|
|
6318
6322
|
};
|
|
6319
6323
|
|
|
6320
|
-
const INTERVAL_MINUTES$
|
|
6324
|
+
const INTERVAL_MINUTES$8 = {
|
|
6321
6325
|
"1m": 1,
|
|
6322
6326
|
"3m": 3,
|
|
6323
6327
|
"5m": 5,
|
|
@@ -6738,7 +6742,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
6738
6742
|
}
|
|
6739
6743
|
const currentTime = self.params.execution.context.when.getTime();
|
|
6740
6744
|
{
|
|
6741
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
6745
|
+
const intervalMinutes = INTERVAL_MINUTES$8[self.params.interval];
|
|
6742
6746
|
const intervalMs = intervalMinutes * 60 * 1000;
|
|
6743
6747
|
const alignedTime = Math.floor(currentTime / intervalMs) * intervalMs;
|
|
6744
6748
|
// Проверяем что наступил новый интервал (по aligned timestamp)
|
|
@@ -13733,7 +13737,7 @@ class StrategyConnectionService {
|
|
|
13733
13737
|
* Maps FrameInterval to minutes for timestamp calculation.
|
|
13734
13738
|
* Used to generate timeframe arrays with proper spacing.
|
|
13735
13739
|
*/
|
|
13736
|
-
const INTERVAL_MINUTES$
|
|
13740
|
+
const INTERVAL_MINUTES$7 = {
|
|
13737
13741
|
"1m": 1,
|
|
13738
13742
|
"3m": 3,
|
|
13739
13743
|
"5m": 5,
|
|
@@ -13787,7 +13791,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
13787
13791
|
symbol,
|
|
13788
13792
|
});
|
|
13789
13793
|
const { interval, startDate, endDate } = self.params;
|
|
13790
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
13794
|
+
const intervalMinutes = INTERVAL_MINUTES$7[interval];
|
|
13791
13795
|
if (!intervalMinutes) {
|
|
13792
13796
|
throw new Error(`ClientFrame unknown interval: ${interval}`);
|
|
13793
13797
|
}
|
|
@@ -14152,8 +14156,8 @@ const get = (object, path) => {
|
|
|
14152
14156
|
return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
|
|
14153
14157
|
};
|
|
14154
14158
|
|
|
14155
|
-
const MS_PER_MINUTE$
|
|
14156
|
-
const INTERVAL_MINUTES$
|
|
14159
|
+
const MS_PER_MINUTE$6 = 60000;
|
|
14160
|
+
const INTERVAL_MINUTES$6 = {
|
|
14157
14161
|
"1m": 1,
|
|
14158
14162
|
"3m": 3,
|
|
14159
14163
|
"5m": 5,
|
|
@@ -14184,11 +14188,11 @@ const INTERVAL_MINUTES$5 = {
|
|
|
14184
14188
|
* @returns New Date aligned down to interval boundary
|
|
14185
14189
|
*/
|
|
14186
14190
|
const alignToInterval = (date, interval) => {
|
|
14187
|
-
const minutes = INTERVAL_MINUTES$
|
|
14191
|
+
const minutes = INTERVAL_MINUTES$6[interval];
|
|
14188
14192
|
if (minutes === undefined) {
|
|
14189
14193
|
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
14190
14194
|
}
|
|
14191
|
-
const intervalMs = minutes * MS_PER_MINUTE$
|
|
14195
|
+
const intervalMs = minutes * MS_PER_MINUTE$6;
|
|
14192
14196
|
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
14193
14197
|
};
|
|
14194
14198
|
|
|
@@ -19670,8 +19674,8 @@ class BacktestLogicPrivateService {
|
|
|
19670
19674
|
}
|
|
19671
19675
|
|
|
19672
19676
|
const EMITTER_CHECK_INTERVAL = 5000;
|
|
19673
|
-
const MS_PER_MINUTE$
|
|
19674
|
-
const INTERVAL_MINUTES$
|
|
19677
|
+
const MS_PER_MINUTE$5 = 60000;
|
|
19678
|
+
const INTERVAL_MINUTES$5 = {
|
|
19675
19679
|
"1m": 1,
|
|
19676
19680
|
"3m": 3,
|
|
19677
19681
|
"5m": 5,
|
|
@@ -19686,7 +19690,7 @@ const INTERVAL_MINUTES$4 = {
|
|
|
19686
19690
|
};
|
|
19687
19691
|
const createEmitter = functoolsKit.memoize(([interval]) => `${interval}`, (interval) => {
|
|
19688
19692
|
const tickSubject = new functoolsKit.Subject();
|
|
19689
|
-
const intervalMs = INTERVAL_MINUTES$
|
|
19693
|
+
const intervalMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$5;
|
|
19690
19694
|
{
|
|
19691
19695
|
let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
19692
19696
|
functoolsKit.Source.fromInterval(EMITTER_CHECK_INTERVAL)
|
|
@@ -35192,7 +35196,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
|
35192
35196
|
const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
|
|
35193
35197
|
const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
|
|
35194
35198
|
const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
|
|
35195
|
-
const MS_PER_MINUTE$
|
|
35199
|
+
const MS_PER_MINUTE$4 = 60000;
|
|
35196
35200
|
/**
|
|
35197
35201
|
* Gets current timestamp from execution context if available.
|
|
35198
35202
|
* Returns current Date() if no execution context exists (non-trading GUI).
|
|
@@ -35259,7 +35263,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
|
|
|
35259
35263
|
const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
|
|
35260
35264
|
throw new Error(`getAggregatedTrades is not implemented for this exchange`);
|
|
35261
35265
|
};
|
|
35262
|
-
const INTERVAL_MINUTES$
|
|
35266
|
+
const INTERVAL_MINUTES$4 = {
|
|
35263
35267
|
"1m": 1,
|
|
35264
35268
|
"3m": 3,
|
|
35265
35269
|
"5m": 5,
|
|
@@ -35290,7 +35294,7 @@ const INTERVAL_MINUTES$3 = {
|
|
|
35290
35294
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
35291
35295
|
*/
|
|
35292
35296
|
const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
|
|
35293
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
35297
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$4;
|
|
35294
35298
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
35295
35299
|
};
|
|
35296
35300
|
/**
|
|
@@ -35430,11 +35434,11 @@ class ExchangeInstance {
|
|
|
35430
35434
|
limit,
|
|
35431
35435
|
});
|
|
35432
35436
|
const getCandles = this._methods.getCandles;
|
|
35433
|
-
const step = INTERVAL_MINUTES$
|
|
35437
|
+
const step = INTERVAL_MINUTES$4[interval];
|
|
35434
35438
|
if (!step) {
|
|
35435
35439
|
throw new Error(`ExchangeInstance unknown interval=${interval}`);
|
|
35436
35440
|
}
|
|
35437
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
35441
|
+
const stepMs = step * MS_PER_MINUTE$4;
|
|
35438
35442
|
// Align when down to interval boundary
|
|
35439
35443
|
const when = await GET_TIMESTAMP_FN();
|
|
35440
35444
|
const whenTimestamp = when.getTime();
|
|
@@ -35648,7 +35652,7 @@ class ExchangeInstance {
|
|
|
35648
35652
|
const when = await GET_TIMESTAMP_FN();
|
|
35649
35653
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
35650
35654
|
const to = new Date(alignedTo);
|
|
35651
|
-
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
35655
|
+
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$4);
|
|
35652
35656
|
const isBacktest = await GET_BACKTEST_FN();
|
|
35653
35657
|
return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
|
|
35654
35658
|
};
|
|
@@ -35680,7 +35684,7 @@ class ExchangeInstance {
|
|
|
35680
35684
|
const when = await GET_TIMESTAMP_FN();
|
|
35681
35685
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
35682
35686
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
|
|
35683
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
35687
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$4 - MS_PER_MINUTE$4;
|
|
35684
35688
|
const isBacktest = await GET_BACKTEST_FN();
|
|
35685
35689
|
// No limit: fetch a single window and return as-is
|
|
35686
35690
|
if (limit === undefined) {
|
|
@@ -35743,11 +35747,11 @@ class ExchangeInstance {
|
|
|
35743
35747
|
sDate,
|
|
35744
35748
|
eDate,
|
|
35745
35749
|
});
|
|
35746
|
-
const step = INTERVAL_MINUTES$
|
|
35750
|
+
const step = INTERVAL_MINUTES$4[interval];
|
|
35747
35751
|
if (!step) {
|
|
35748
35752
|
throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
|
|
35749
35753
|
}
|
|
35750
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
35754
|
+
const stepMs = step * MS_PER_MINUTE$4;
|
|
35751
35755
|
const when = await GET_TIMESTAMP_FN();
|
|
35752
35756
|
const nowTimestamp = when.getTime();
|
|
35753
35757
|
const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
|
|
@@ -36059,8 +36063,8 @@ const Exchange = new ExchangeUtils();
|
|
|
36059
36063
|
|
|
36060
36064
|
const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
|
|
36061
36065
|
const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
|
|
36062
|
-
const MS_PER_MINUTE$
|
|
36063
|
-
const INTERVAL_MINUTES$
|
|
36066
|
+
const MS_PER_MINUTE$3 = 60000;
|
|
36067
|
+
const INTERVAL_MINUTES$3 = {
|
|
36064
36068
|
"1m": 1,
|
|
36065
36069
|
"3m": 3,
|
|
36066
36070
|
"5m": 5,
|
|
@@ -36074,7 +36078,7 @@ const INTERVAL_MINUTES$2 = {
|
|
|
36074
36078
|
"1d": 1440,
|
|
36075
36079
|
};
|
|
36076
36080
|
const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
|
|
36077
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
36081
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
|
|
36078
36082
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
36079
36083
|
};
|
|
36080
36084
|
const BAR_LENGTH = 30;
|
|
@@ -36099,11 +36103,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
|
|
|
36099
36103
|
async function checkCandles(params) {
|
|
36100
36104
|
const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
|
|
36101
36105
|
backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
|
|
36102
|
-
const step = INTERVAL_MINUTES$
|
|
36106
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
36103
36107
|
if (!step) {
|
|
36104
36108
|
throw new Error(`checkCandles: unsupported interval=${interval}`);
|
|
36105
36109
|
}
|
|
36106
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
36110
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
36107
36111
|
const dir = path.join(baseDir, exchangeName, symbol, interval);
|
|
36108
36112
|
const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
36109
36113
|
const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -36173,11 +36177,11 @@ async function warmCandles(params) {
|
|
|
36173
36177
|
from,
|
|
36174
36178
|
to,
|
|
36175
36179
|
});
|
|
36176
|
-
const step = INTERVAL_MINUTES$
|
|
36180
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
36177
36181
|
if (!step) {
|
|
36178
36182
|
throw new Error(`warmCandles: unsupported interval=${interval}`);
|
|
36179
36183
|
}
|
|
36180
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
36184
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
36181
36185
|
const instance = new ExchangeInstance(exchangeName);
|
|
36182
36186
|
const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
36183
36187
|
const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -48520,18 +48524,21 @@ class RecentPersistBacktestUtils {
|
|
|
48520
48524
|
backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
48521
48525
|
signalId: event.data.id,
|
|
48522
48526
|
});
|
|
48523
|
-
await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
|
|
48527
|
+
await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest, new Date(event.data.timestamp));
|
|
48524
48528
|
};
|
|
48525
48529
|
/**
|
|
48526
48530
|
* Retrieves the latest persisted signal for the given context.
|
|
48531
|
+
* Returns null if the stored signal's `timestamp` is greater than the requested `when`
|
|
48532
|
+
* (look-ahead bias protection).
|
|
48527
48533
|
* @param symbol - Trading pair symbol
|
|
48528
48534
|
* @param strategyName - Strategy identifier
|
|
48529
48535
|
* @param exchangeName - Exchange identifier
|
|
48530
48536
|
* @param frameName - Frame identifier
|
|
48531
48537
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48532
|
-
* @
|
|
48538
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48539
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48533
48540
|
*/
|
|
48534
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48541
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48535
48542
|
backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
48536
48543
|
symbol,
|
|
48537
48544
|
strategyName,
|
|
@@ -48539,20 +48546,26 @@ class RecentPersistBacktestUtils {
|
|
|
48539
48546
|
frameName,
|
|
48540
48547
|
backtest: backtest$1,
|
|
48541
48548
|
});
|
|
48542
|
-
|
|
48549
|
+
const signal = await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48550
|
+
if (!signal || signal.timestamp > when.getTime()) {
|
|
48551
|
+
return null;
|
|
48552
|
+
}
|
|
48553
|
+
return signal;
|
|
48543
48554
|
};
|
|
48544
48555
|
/**
|
|
48545
48556
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48546
|
-
*
|
|
48557
|
+
* `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
|
|
48558
|
+
* the requested one is treated as not yet visible.
|
|
48559
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48547
48560
|
* @param symbol - Trading pair symbol
|
|
48548
48561
|
* @param strategyName - Strategy identifier
|
|
48549
48562
|
* @param exchangeName - Exchange identifier
|
|
48550
48563
|
* @param frameName - Frame identifier
|
|
48551
48564
|
* @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
|
|
48565
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48553
48566
|
*/
|
|
48554
48567
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
48555
|
-
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
48568
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
|
|
48556
48569
|
if (!signal) {
|
|
48557
48570
|
return null;
|
|
48558
48571
|
}
|
|
@@ -48588,30 +48601,39 @@ class RecentMemoryBacktestUtils {
|
|
|
48588
48601
|
};
|
|
48589
48602
|
/**
|
|
48590
48603
|
* Retrieves the latest in-memory signal for the given context.
|
|
48604
|
+
* Returns null if the stored signal's `timestamp` is greater than the requested `when`
|
|
48605
|
+
* (look-ahead bias protection).
|
|
48591
48606
|
* @param symbol - Trading pair symbol
|
|
48592
48607
|
* @param strategyName - Strategy identifier
|
|
48593
48608
|
* @param exchangeName - Exchange identifier
|
|
48594
48609
|
* @param frameName - Frame identifier
|
|
48595
48610
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48596
|
-
* @
|
|
48611
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48612
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48597
48613
|
*/
|
|
48598
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48614
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48599
48615
|
const key = CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48600
48616
|
backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
|
|
48601
|
-
|
|
48617
|
+
const signal = this._signals.get(key) ?? null;
|
|
48618
|
+
if (!signal || signal.timestamp > when.getTime()) {
|
|
48619
|
+
return null;
|
|
48620
|
+
}
|
|
48621
|
+
return signal;
|
|
48602
48622
|
};
|
|
48603
48623
|
/**
|
|
48604
48624
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48605
|
-
*
|
|
48625
|
+
* `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
|
|
48626
|
+
* the requested one is treated as not yet visible.
|
|
48627
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48606
48628
|
* @param symbol - Trading pair symbol
|
|
48607
48629
|
* @param strategyName - Strategy identifier
|
|
48608
48630
|
* @param exchangeName - Exchange identifier
|
|
48609
48631
|
* @param frameName - Frame identifier
|
|
48610
48632
|
* @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
|
|
48633
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48612
48634
|
*/
|
|
48613
48635
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
48614
|
-
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
48636
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
|
|
48615
48637
|
if (!signal) {
|
|
48616
48638
|
return null;
|
|
48617
48639
|
}
|
|
@@ -48639,18 +48661,21 @@ class RecentPersistLiveUtils {
|
|
|
48639
48661
|
backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
48640
48662
|
signalId: event.data.id,
|
|
48641
48663
|
});
|
|
48642
|
-
await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
|
|
48664
|
+
await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest, new Date(event.data.timestamp));
|
|
48643
48665
|
};
|
|
48644
48666
|
/**
|
|
48645
48667
|
* Retrieves the latest persisted signal for the given context.
|
|
48668
|
+
* Returns null if the stored signal's `timestamp` is greater than the requested `when`
|
|
48669
|
+
* (look-ahead bias protection).
|
|
48646
48670
|
* @param symbol - Trading pair symbol
|
|
48647
48671
|
* @param strategyName - Strategy identifier
|
|
48648
48672
|
* @param exchangeName - Exchange identifier
|
|
48649
48673
|
* @param frameName - Frame identifier
|
|
48650
48674
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48651
|
-
* @
|
|
48675
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48676
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48652
48677
|
*/
|
|
48653
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48678
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48654
48679
|
backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
48655
48680
|
symbol,
|
|
48656
48681
|
strategyName,
|
|
@@ -48658,20 +48683,26 @@ class RecentPersistLiveUtils {
|
|
|
48658
48683
|
frameName,
|
|
48659
48684
|
backtest: backtest$1,
|
|
48660
48685
|
});
|
|
48661
|
-
|
|
48686
|
+
const signal = await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48687
|
+
if (!signal || signal.timestamp > when.getTime()) {
|
|
48688
|
+
return null;
|
|
48689
|
+
}
|
|
48690
|
+
return signal;
|
|
48662
48691
|
};
|
|
48663
48692
|
/**
|
|
48664
48693
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48665
|
-
*
|
|
48694
|
+
* `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
|
|
48695
|
+
* the requested one is treated as not yet visible.
|
|
48696
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48666
48697
|
* @param symbol - Trading pair symbol
|
|
48667
48698
|
* @param strategyName - Strategy identifier
|
|
48668
48699
|
* @param exchangeName - Exchange identifier
|
|
48669
48700
|
* @param frameName - Frame identifier
|
|
48670
48701
|
* @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
|
|
48702
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48672
48703
|
*/
|
|
48673
48704
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
48674
|
-
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
48705
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
|
|
48675
48706
|
if (!signal) {
|
|
48676
48707
|
return null;
|
|
48677
48708
|
}
|
|
@@ -48707,30 +48738,39 @@ class RecentMemoryLiveUtils {
|
|
|
48707
48738
|
};
|
|
48708
48739
|
/**
|
|
48709
48740
|
* Retrieves the latest in-memory signal for the given context.
|
|
48741
|
+
* Returns null if the stored signal's `timestamp` is greater than the requested `when`
|
|
48742
|
+
* (look-ahead bias protection).
|
|
48710
48743
|
* @param symbol - Trading pair symbol
|
|
48711
48744
|
* @param strategyName - Strategy identifier
|
|
48712
48745
|
* @param exchangeName - Exchange identifier
|
|
48713
48746
|
* @param frameName - Frame identifier
|
|
48714
48747
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48715
|
-
* @
|
|
48748
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48749
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48716
48750
|
*/
|
|
48717
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48751
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48718
48752
|
const key = CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48719
48753
|
backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
|
|
48720
|
-
|
|
48754
|
+
const signal = this._signals.get(key) ?? null;
|
|
48755
|
+
if (!signal || signal.timestamp > when.getTime()) {
|
|
48756
|
+
return null;
|
|
48757
|
+
}
|
|
48758
|
+
return signal;
|
|
48721
48759
|
};
|
|
48722
48760
|
/**
|
|
48723
48761
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48724
|
-
*
|
|
48762
|
+
* `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
|
|
48763
|
+
* the requested one is treated as not yet visible.
|
|
48764
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48725
48765
|
* @param symbol - Trading pair symbol
|
|
48726
48766
|
* @param strategyName - Strategy identifier
|
|
48727
48767
|
* @param exchangeName - Exchange identifier
|
|
48728
48768
|
* @param frameName - Frame identifier
|
|
48729
48769
|
* @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
|
|
48770
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48731
48771
|
*/
|
|
48732
48772
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
48733
|
-
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
48773
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
|
|
48734
48774
|
if (!signal) {
|
|
48735
48775
|
return null;
|
|
48736
48776
|
}
|
|
@@ -48770,9 +48810,10 @@ class RecentBacktestAdapter {
|
|
|
48770
48810
|
* @param exchangeName - Exchange identifier
|
|
48771
48811
|
* @param frameName - Frame identifier
|
|
48772
48812
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48773
|
-
* @
|
|
48813
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48814
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48774
48815
|
*/
|
|
48775
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48816
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48776
48817
|
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
48777
48818
|
symbol,
|
|
48778
48819
|
strategyName,
|
|
@@ -48780,18 +48821,20 @@ class RecentBacktestAdapter {
|
|
|
48780
48821
|
frameName,
|
|
48781
48822
|
backtest: backtest$1,
|
|
48782
48823
|
});
|
|
48783
|
-
return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48824
|
+
return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, when);
|
|
48784
48825
|
};
|
|
48785
48826
|
/**
|
|
48786
48827
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48787
|
-
* Proxies call to the underlying storage adapter.
|
|
48788
|
-
*
|
|
48828
|
+
* Proxies call to the underlying storage adapter. `timestamp` doubles as the
|
|
48829
|
+
* look-ahead cutoff — a signal whose `timestamp` exceeds the requested one is
|
|
48830
|
+
* treated as not yet visible.
|
|
48831
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48789
48832
|
* @param symbol - Trading pair symbol
|
|
48790
48833
|
* @param strategyName - Strategy identifier
|
|
48791
48834
|
* @param exchangeName - Exchange identifier
|
|
48792
48835
|
* @param frameName - Frame identifier
|
|
48793
48836
|
* @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
|
|
48837
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48795
48838
|
*/
|
|
48796
48839
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48797
48840
|
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
@@ -48802,7 +48845,7 @@ class RecentBacktestAdapter {
|
|
|
48802
48845
|
backtest: backtest$1,
|
|
48803
48846
|
timestamp,
|
|
48804
48847
|
});
|
|
48805
|
-
const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48848
|
+
const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, new Date(timestamp));
|
|
48806
48849
|
if (!signal) {
|
|
48807
48850
|
return null;
|
|
48808
48851
|
}
|
|
@@ -48874,9 +48917,10 @@ class RecentLiveAdapter {
|
|
|
48874
48917
|
* @param exchangeName - Exchange identifier
|
|
48875
48918
|
* @param frameName - Frame identifier
|
|
48876
48919
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
48877
|
-
* @
|
|
48920
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
48921
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
48878
48922
|
*/
|
|
48879
|
-
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48923
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
|
|
48880
48924
|
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
48881
48925
|
symbol,
|
|
48882
48926
|
strategyName,
|
|
@@ -48884,18 +48928,20 @@ class RecentLiveAdapter {
|
|
|
48884
48928
|
frameName,
|
|
48885
48929
|
backtest: backtest$1,
|
|
48886
48930
|
});
|
|
48887
|
-
return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48931
|
+
return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, when);
|
|
48888
48932
|
};
|
|
48889
48933
|
/**
|
|
48890
48934
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
48891
|
-
* Proxies call to the underlying storage adapter.
|
|
48892
|
-
*
|
|
48935
|
+
* Proxies call to the underlying storage adapter. `timestamp` doubles as the
|
|
48936
|
+
* look-ahead cutoff — a signal whose `timestamp` exceeds the requested one is
|
|
48937
|
+
* treated as not yet visible.
|
|
48938
|
+
* @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
|
|
48893
48939
|
* @param symbol - Trading pair symbol
|
|
48894
48940
|
* @param strategyName - Strategy identifier
|
|
48895
48941
|
* @param exchangeName - Exchange identifier
|
|
48896
48942
|
* @param frameName - Frame identifier
|
|
48897
48943
|
* @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
|
|
48944
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
48899
48945
|
*/
|
|
48900
48946
|
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
48901
48947
|
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
@@ -48906,7 +48952,7 @@ class RecentLiveAdapter {
|
|
|
48906
48952
|
backtest: backtest$1,
|
|
48907
48953
|
timestamp,
|
|
48908
48954
|
});
|
|
48909
|
-
const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
48955
|
+
const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, new Date(timestamp));
|
|
48910
48956
|
if (!signal) {
|
|
48911
48957
|
return null;
|
|
48912
48958
|
}
|
|
@@ -48996,14 +49042,16 @@ class RecentAdapter {
|
|
|
48996
49042
|
/**
|
|
48997
49043
|
* Retrieves the latest active signal for the given symbol and context.
|
|
48998
49044
|
* Searches backtest storage first, then live storage.
|
|
49045
|
+
* Returns null if the stored signal's `timestamp` is greater than the requested `when`
|
|
49046
|
+
* (look-ahead bias protection).
|
|
48999
49047
|
*
|
|
49000
49048
|
* @param symbol - Trading pair symbol
|
|
49001
49049
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
49002
|
-
* @param
|
|
49003
|
-
* @returns The latest signal or null if not found
|
|
49050
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
49051
|
+
* @returns The latest signal or null if not found / shadowed by look-ahead
|
|
49004
49052
|
* @throws Error if RecentAdapter is not enabled
|
|
49005
49053
|
*/
|
|
49006
|
-
this.getLatestSignal = async (symbol, context) => {
|
|
49054
|
+
this.getLatestSignal = async (symbol, context, when) => {
|
|
49007
49055
|
backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
49008
49056
|
symbol,
|
|
49009
49057
|
context,
|
|
@@ -49012,10 +49060,10 @@ class RecentAdapter {
|
|
|
49012
49060
|
throw new Error("RecentAdapter is not enabled. Call enable() first.");
|
|
49013
49061
|
}
|
|
49014
49062
|
let result = null;
|
|
49015
|
-
if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
|
|
49063
|
+
if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true, when)) {
|
|
49016
49064
|
return result;
|
|
49017
49065
|
}
|
|
49018
|
-
if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
|
|
49066
|
+
if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false, when)) {
|
|
49019
49067
|
return result;
|
|
49020
49068
|
}
|
|
49021
49069
|
return null;
|
|
@@ -49023,13 +49071,16 @@ class RecentAdapter {
|
|
|
49023
49071
|
/**
|
|
49024
49072
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
49025
49073
|
* Searches backtest storage first, then live storage.
|
|
49026
|
-
*
|
|
49074
|
+
* `when` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
|
|
49075
|
+
* `when.getTime()` is treated as not yet visible — and as the "now" against
|
|
49076
|
+
* which elapsed minutes are computed.
|
|
49027
49077
|
* @param symbol - Trading pair symbol
|
|
49028
49078
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
49029
|
-
* @
|
|
49079
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead cutoff + "now")
|
|
49080
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
|
|
49030
49081
|
* @throws Error if RecentAdapter is not enabled
|
|
49031
49082
|
*/
|
|
49032
|
-
this.getMinutesSinceLatestSignalCreated = async (symbol, context) => {
|
|
49083
|
+
this.getMinutesSinceLatestSignalCreated = async (symbol, context, when) => {
|
|
49033
49084
|
backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL, {
|
|
49034
49085
|
symbol,
|
|
49035
49086
|
context,
|
|
@@ -49038,13 +49089,11 @@ class RecentAdapter {
|
|
|
49038
49089
|
throw new Error("RecentAdapter is not enabled. Call enable() first.");
|
|
49039
49090
|
}
|
|
49040
49091
|
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));
|
|
49092
|
+
if (signal = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true, when)) {
|
|
49093
|
+
return Math.floor((when.getTime() - signal.timestamp) / (1000 * 60));
|
|
49044
49094
|
}
|
|
49045
|
-
if (signal = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
|
|
49046
|
-
|
|
49047
|
-
return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
|
|
49095
|
+
if (signal = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false, when)) {
|
|
49096
|
+
return Math.floor((when.getTime() - signal.timestamp) / (1000 * 60));
|
|
49048
49097
|
}
|
|
49049
49098
|
return null;
|
|
49050
49099
|
};
|
|
@@ -49109,41 +49158,54 @@ class StateLocalInstance {
|
|
|
49109
49158
|
this.initialValue = initialValue;
|
|
49110
49159
|
this.signalId = signalId;
|
|
49111
49160
|
this.bucketName = bucketName;
|
|
49161
|
+
this._when = 0;
|
|
49112
49162
|
/**
|
|
49113
49163
|
* Initializes _value from initialValue - local state needs no async setup.
|
|
49114
49164
|
* @returns Promise that resolves immediately
|
|
49115
49165
|
*/
|
|
49116
49166
|
this.waitForInit = functoolsKit.singleshot(async (_initial) => {
|
|
49117
49167
|
this._value = this.initialValue;
|
|
49168
|
+
this._when = 0;
|
|
49118
49169
|
});
|
|
49119
49170
|
/**
|
|
49120
49171
|
* Update the in-memory state value.
|
|
49172
|
+
* Records `when` so future reads with a smaller `when` see `initialValue`.
|
|
49173
|
+
* The dispatch updater receives the look-ahead-guarded current value.
|
|
49121
49174
|
* @param dispatch - New value or updater function receiving current value
|
|
49175
|
+
* @param when - Logical timestamp this value belongs to
|
|
49122
49176
|
* @returns Updated state value
|
|
49123
49177
|
*/
|
|
49124
|
-
this.setState = functoolsKit.queued(async (dispatch) => {
|
|
49178
|
+
this.setState = functoolsKit.queued(async (dispatch, when) => {
|
|
49125
49179
|
backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_SET, {
|
|
49126
49180
|
signalId: this.signalId,
|
|
49127
49181
|
bucketName: this.bucketName,
|
|
49128
49182
|
});
|
|
49129
49183
|
if (typeof dispatch === "function") {
|
|
49130
|
-
this.
|
|
49184
|
+
const prev = this._when > when.getTime() ? this.initialValue : this._value;
|
|
49185
|
+
this._value = await dispatch(prev);
|
|
49131
49186
|
}
|
|
49132
49187
|
else {
|
|
49133
49188
|
this._value = dispatch;
|
|
49134
49189
|
}
|
|
49190
|
+
this._when = when.getTime();
|
|
49135
49191
|
return this._value;
|
|
49136
49192
|
});
|
|
49137
49193
|
}
|
|
49138
49194
|
/**
|
|
49139
49195
|
* Read the current in-memory state value.
|
|
49196
|
+
* Returns `initialValue` when the stored `when` is greater than the requested `when`
|
|
49197
|
+
* (look-ahead bias protection).
|
|
49198
|
+
* @param when - Logical timestamp at which the read is happening
|
|
49140
49199
|
* @returns Current state value
|
|
49141
49200
|
*/
|
|
49142
|
-
async getState() {
|
|
49201
|
+
async getState(when) {
|
|
49143
49202
|
backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_GET, {
|
|
49144
49203
|
signalId: this.signalId,
|
|
49145
49204
|
bucketName: this.bucketName,
|
|
49146
49205
|
});
|
|
49206
|
+
if (this._when > when.getTime()) {
|
|
49207
|
+
return this.initialValue;
|
|
49208
|
+
}
|
|
49147
49209
|
return this._value;
|
|
49148
49210
|
}
|
|
49149
49211
|
/** Releases resources held by this instance. */
|
|
@@ -49177,14 +49239,14 @@ class StateDummyInstance {
|
|
|
49177
49239
|
* No-op read - always returns initialValue.
|
|
49178
49240
|
* @returns initialValue
|
|
49179
49241
|
*/
|
|
49180
|
-
async getState() {
|
|
49242
|
+
async getState(_when) {
|
|
49181
49243
|
return this.initialValue;
|
|
49182
49244
|
}
|
|
49183
49245
|
/**
|
|
49184
49246
|
* No-op write - discards the value and returns initialValue.
|
|
49185
49247
|
* @returns initialValue
|
|
49186
49248
|
*/
|
|
49187
|
-
async setState(_dispatch) {
|
|
49249
|
+
async setState(_dispatch, _when) {
|
|
49188
49250
|
return this.initialValue;
|
|
49189
49251
|
}
|
|
49190
49252
|
/** No-op. */
|
|
@@ -49210,6 +49272,7 @@ class StatePersistInstance {
|
|
|
49210
49272
|
this.initialValue = initialValue;
|
|
49211
49273
|
this.signalId = signalId;
|
|
49212
49274
|
this.bucketName = bucketName;
|
|
49275
|
+
this._when = 0;
|
|
49213
49276
|
/**
|
|
49214
49277
|
* Initialize persistence storage and restore state from disk.
|
|
49215
49278
|
* @param initial - Whether this is the first initialization
|
|
@@ -49224,40 +49287,54 @@ class StatePersistInstance {
|
|
|
49224
49287
|
const data = await PersistStateAdapter.readStateData(this.signalId, this.bucketName);
|
|
49225
49288
|
if (data) {
|
|
49226
49289
|
this._value = data.data;
|
|
49290
|
+
this._when = data.when;
|
|
49227
49291
|
return;
|
|
49228
49292
|
}
|
|
49229
49293
|
this._value = this.initialValue;
|
|
49294
|
+
this._when = 0;
|
|
49230
49295
|
});
|
|
49231
49296
|
/**
|
|
49232
49297
|
* Update state and persist to disk atomically.
|
|
49298
|
+
* A write with a smaller `when` overwrites an existing record — that lets a
|
|
49299
|
+
* restarted backtest reset live-written state without breaking live.
|
|
49300
|
+
* The dispatch updater receives the look-ahead-guarded current value.
|
|
49233
49301
|
* @param dispatch - New value or updater function receiving current value
|
|
49302
|
+
* @param when - Logical timestamp this value belongs to
|
|
49234
49303
|
* @returns Updated state value
|
|
49235
49304
|
*/
|
|
49236
|
-
this.setState = functoolsKit.queued(async (dispatch) => {
|
|
49305
|
+
this.setState = functoolsKit.queued(async (dispatch, when) => {
|
|
49237
49306
|
backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_SET, {
|
|
49238
49307
|
signalId: this.signalId,
|
|
49239
49308
|
bucketName: this.bucketName,
|
|
49240
49309
|
});
|
|
49241
49310
|
if (typeof dispatch === "function") {
|
|
49242
|
-
this.
|
|
49311
|
+
const prev = this._when > when.getTime() ? this.initialValue : this._value;
|
|
49312
|
+
this._value = await dispatch(prev);
|
|
49243
49313
|
}
|
|
49244
49314
|
else {
|
|
49245
49315
|
this._value = dispatch;
|
|
49246
49316
|
}
|
|
49317
|
+
this._when = when.getTime();
|
|
49247
49318
|
const id = CREATE_KEY_FN$6(this.signalId, this.bucketName);
|
|
49248
|
-
await PersistStateAdapter.writeStateData({ id, data: this._value }, this.signalId, this.bucketName);
|
|
49319
|
+
await PersistStateAdapter.writeStateData({ id, data: this._value, when: this._when }, this.signalId, this.bucketName, when);
|
|
49249
49320
|
return this._value;
|
|
49250
49321
|
});
|
|
49251
49322
|
}
|
|
49252
49323
|
/**
|
|
49253
49324
|
* Read the current persisted state value.
|
|
49325
|
+
* Returns `initialValue` when the stored `when` is greater than the requested `when`
|
|
49326
|
+
* (look-ahead bias protection).
|
|
49327
|
+
* @param when - Logical timestamp at which the read is happening
|
|
49254
49328
|
* @returns Current state value
|
|
49255
49329
|
*/
|
|
49256
|
-
async getState() {
|
|
49330
|
+
async getState(when) {
|
|
49257
49331
|
backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_GET, {
|
|
49258
49332
|
signalId: this.signalId,
|
|
49259
49333
|
bucketName: this.bucketName,
|
|
49260
49334
|
});
|
|
49335
|
+
if (this._when > when.getTime()) {
|
|
49336
|
+
return this.initialValue;
|
|
49337
|
+
}
|
|
49261
49338
|
return this._value;
|
|
49262
49339
|
}
|
|
49263
49340
|
/** Releases resources held by this instance. */
|
|
@@ -49310,6 +49387,7 @@ class StateBacktestAdapter {
|
|
|
49310
49387
|
* @param dto.signalId - Signal identifier
|
|
49311
49388
|
* @param dto.bucketName - Bucket name
|
|
49312
49389
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49390
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
49313
49391
|
* @returns Current state value
|
|
49314
49392
|
*/
|
|
49315
49393
|
this.getState = async (dto) => {
|
|
@@ -49321,7 +49399,7 @@ class StateBacktestAdapter {
|
|
|
49321
49399
|
const isInitial = !this.getInstance.has(key);
|
|
49322
49400
|
const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
|
|
49323
49401
|
await instance.waitForInit(isInitial);
|
|
49324
|
-
return await instance.getState();
|
|
49402
|
+
return await instance.getState(dto.when);
|
|
49325
49403
|
};
|
|
49326
49404
|
/**
|
|
49327
49405
|
* Update the state value for a signal.
|
|
@@ -49329,6 +49407,7 @@ class StateBacktestAdapter {
|
|
|
49329
49407
|
* @param dto.signalId - Signal identifier
|
|
49330
49408
|
* @param dto.bucketName - Bucket name
|
|
49331
49409
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49410
|
+
* @param dto.when - Logical timestamp this value belongs to
|
|
49332
49411
|
* @returns Updated state value
|
|
49333
49412
|
*/
|
|
49334
49413
|
this.setState = async (dispatch, dto) => {
|
|
@@ -49340,7 +49419,7 @@ class StateBacktestAdapter {
|
|
|
49340
49419
|
const isInitial = !this.getInstance.has(key);
|
|
49341
49420
|
const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
|
|
49342
49421
|
await instance.waitForInit(isInitial);
|
|
49343
|
-
return await instance.setState(dispatch);
|
|
49422
|
+
return await instance.setState(dispatch, dto.when);
|
|
49344
49423
|
};
|
|
49345
49424
|
/**
|
|
49346
49425
|
* Switches to in-memory adapter (default).
|
|
@@ -49425,6 +49504,7 @@ class StateLiveAdapter {
|
|
|
49425
49504
|
* @param dto.signalId - Signal identifier
|
|
49426
49505
|
* @param dto.bucketName - Bucket name
|
|
49427
49506
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49507
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
49428
49508
|
* @returns Current state value
|
|
49429
49509
|
*/
|
|
49430
49510
|
this.getState = async (dto) => {
|
|
@@ -49436,7 +49516,7 @@ class StateLiveAdapter {
|
|
|
49436
49516
|
const isInitial = !this.getInstance.has(key);
|
|
49437
49517
|
const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
|
|
49438
49518
|
await instance.waitForInit(isInitial);
|
|
49439
|
-
return await instance.getState();
|
|
49519
|
+
return await instance.getState(dto.when);
|
|
49440
49520
|
};
|
|
49441
49521
|
/**
|
|
49442
49522
|
* Update the state value for a signal.
|
|
@@ -49444,6 +49524,7 @@ class StateLiveAdapter {
|
|
|
49444
49524
|
* @param dto.signalId - Signal identifier
|
|
49445
49525
|
* @param dto.bucketName - Bucket name
|
|
49446
49526
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49527
|
+
* @param dto.when - Logical timestamp this value belongs to
|
|
49447
49528
|
* @returns Updated state value
|
|
49448
49529
|
*/
|
|
49449
49530
|
this.setState = async (dispatch, dto) => {
|
|
@@ -49455,7 +49536,7 @@ class StateLiveAdapter {
|
|
|
49455
49536
|
const isInitial = !this.getInstance.has(key);
|
|
49456
49537
|
const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
|
|
49457
49538
|
await instance.waitForInit(isInitial);
|
|
49458
|
-
return await instance.setState(dispatch);
|
|
49539
|
+
return await instance.setState(dispatch, dto.when);
|
|
49459
49540
|
};
|
|
49460
49541
|
/**
|
|
49461
49542
|
* Switches to in-memory adapter.
|
|
@@ -49552,6 +49633,7 @@ class StateAdapter {
|
|
|
49552
49633
|
* @param dto.bucketName - Bucket name
|
|
49553
49634
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49554
49635
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
49636
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
49555
49637
|
* @returns Current state value
|
|
49556
49638
|
* @throws Error if adapter is not enabled
|
|
49557
49639
|
*/
|
|
@@ -49577,6 +49659,7 @@ class StateAdapter {
|
|
|
49577
49659
|
* @param dto.bucketName - Bucket name
|
|
49578
49660
|
* @param dto.initialValue - Default value when no persisted state exists
|
|
49579
49661
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
49662
|
+
* @param dto.when - Logical timestamp this value belongs to
|
|
49580
49663
|
* @returns Updated state value
|
|
49581
49664
|
* @throws Error if adapter is not enabled
|
|
49582
49665
|
*/
|
|
@@ -49650,8 +49733,9 @@ async function getLatestSignal(symbol) {
|
|
|
49650
49733
|
if (!MethodContextService.hasContext()) {
|
|
49651
49734
|
throw new Error("getLatestSignal requires a method context");
|
|
49652
49735
|
}
|
|
49736
|
+
const { when } = backtest.executionContextService.context;
|
|
49653
49737
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
49654
|
-
return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
|
|
49738
|
+
return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName }, when);
|
|
49655
49739
|
}
|
|
49656
49740
|
/**
|
|
49657
49741
|
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
@@ -49686,8 +49770,9 @@ async function getMinutesSinceLatestSignalCreated(symbol) {
|
|
|
49686
49770
|
if (!MethodContextService.hasContext()) {
|
|
49687
49771
|
throw new Error("getMinutesSinceLatestSignalCreated requires a method context");
|
|
49688
49772
|
}
|
|
49773
|
+
const { when } = backtest.executionContextService.context;
|
|
49689
49774
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
49690
|
-
return await Recent.getMinutesSinceLatestSignalCreated(symbol, { exchangeName, frameName, strategyName });
|
|
49775
|
+
return await Recent.getMinutesSinceLatestSignalCreated(symbol, { exchangeName, frameName, strategyName }, when);
|
|
49691
49776
|
}
|
|
49692
49777
|
/**
|
|
49693
49778
|
* Reads the state value scoped to the current active signal.
|
|
@@ -49732,7 +49817,7 @@ async function getSignalState(symbol, dto) {
|
|
|
49732
49817
|
if (!MethodContextService.hasContext()) {
|
|
49733
49818
|
throw new Error("getSignalState requires a method context");
|
|
49734
49819
|
}
|
|
49735
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
49820
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
49736
49821
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
49737
49822
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
49738
49823
|
let signal;
|
|
@@ -49742,6 +49827,7 @@ async function getSignalState(symbol, dto) {
|
|
|
49742
49827
|
bucketName,
|
|
49743
49828
|
initialValue,
|
|
49744
49829
|
backtest: isBacktest,
|
|
49830
|
+
when,
|
|
49745
49831
|
});
|
|
49746
49832
|
}
|
|
49747
49833
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -49750,6 +49836,7 @@ async function getSignalState(symbol, dto) {
|
|
|
49750
49836
|
bucketName,
|
|
49751
49837
|
initialValue,
|
|
49752
49838
|
backtest: isBacktest,
|
|
49839
|
+
when,
|
|
49753
49840
|
});
|
|
49754
49841
|
}
|
|
49755
49842
|
throw new Error(`getSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
|
|
@@ -49801,7 +49888,7 @@ async function setSignalState(symbol, dispatch, dto) {
|
|
|
49801
49888
|
if (!MethodContextService.hasContext()) {
|
|
49802
49889
|
throw new Error("setSignalState requires a method context");
|
|
49803
49890
|
}
|
|
49804
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
49891
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
49805
49892
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
49806
49893
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
49807
49894
|
let signal;
|
|
@@ -49811,6 +49898,7 @@ async function setSignalState(symbol, dispatch, dto) {
|
|
|
49811
49898
|
bucketName,
|
|
49812
49899
|
initialValue,
|
|
49813
49900
|
backtest: isBacktest,
|
|
49901
|
+
when,
|
|
49814
49902
|
});
|
|
49815
49903
|
}
|
|
49816
49904
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -49819,6 +49907,7 @@ async function setSignalState(symbol, dispatch, dto) {
|
|
|
49819
49907
|
bucketName,
|
|
49820
49908
|
initialValue,
|
|
49821
49909
|
backtest: isBacktest,
|
|
49910
|
+
when,
|
|
49822
49911
|
});
|
|
49823
49912
|
}
|
|
49824
49913
|
throw new Error(`setSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
|
|
@@ -49872,36 +49961,47 @@ class SessionLocalInstance {
|
|
|
49872
49961
|
this.frameName = frameName;
|
|
49873
49962
|
this.backtest = backtest$1;
|
|
49874
49963
|
this._data = null;
|
|
49964
|
+
this._when = 0;
|
|
49875
49965
|
/**
|
|
49876
49966
|
* Initializes _data to null — local session needs no async setup.
|
|
49877
49967
|
* @returns Promise that resolves immediately
|
|
49878
49968
|
*/
|
|
49879
49969
|
this.waitForInit = functoolsKit.singleshot(async (_initial) => {
|
|
49880
49970
|
this._data = null;
|
|
49971
|
+
this._when = 0;
|
|
49881
49972
|
});
|
|
49882
49973
|
/**
|
|
49883
49974
|
* Read the current in-memory session value.
|
|
49884
|
-
*
|
|
49975
|
+
* Returns null if the stored `when` is greater than the requested `when`
|
|
49976
|
+
* (look-ahead bias protection).
|
|
49977
|
+
* @param when - Logical timestamp at which the read is happening
|
|
49978
|
+
* @returns Current session value, or null
|
|
49885
49979
|
*/
|
|
49886
|
-
this.getData = async () => {
|
|
49980
|
+
this.getData = async (when) => {
|
|
49887
49981
|
backtest.loggerService.debug(SESSION_LOCAL_INSTANCE_METHOD_NAME_GET, {
|
|
49888
49982
|
strategyName: this.strategyName,
|
|
49889
49983
|
exchangeName: this.exchangeName,
|
|
49890
49984
|
frameName: this.frameName,
|
|
49891
49985
|
});
|
|
49986
|
+
if (this._when > when.getTime()) {
|
|
49987
|
+
return null;
|
|
49988
|
+
}
|
|
49892
49989
|
return this._data;
|
|
49893
49990
|
};
|
|
49894
49991
|
/**
|
|
49895
49992
|
* Update the in-memory session value.
|
|
49993
|
+
* Records `when` so future reads with a smaller `when` see no value.
|
|
49896
49994
|
* @param value - New value or null to clear
|
|
49995
|
+
* @param when - Logical timestamp this value belongs to
|
|
49897
49996
|
*/
|
|
49898
|
-
this.setData = async (value) => {
|
|
49997
|
+
this.setData = async (value, when) => {
|
|
49899
49998
|
backtest.loggerService.debug(SESSION_LOCAL_INSTANCE_METHOD_NAME_SET, {
|
|
49900
49999
|
strategyName: this.strategyName,
|
|
49901
50000
|
exchangeName: this.exchangeName,
|
|
49902
50001
|
frameName: this.frameName,
|
|
49903
50002
|
});
|
|
49904
50003
|
this._data = value;
|
|
50004
|
+
this._when = when.getTime();
|
|
49905
50005
|
};
|
|
49906
50006
|
}
|
|
49907
50007
|
/** Releases resources held by this instance. */
|
|
@@ -49937,13 +50037,13 @@ class SessionDummyInstance {
|
|
|
49937
50037
|
* No-op read — always returns null.
|
|
49938
50038
|
* @returns null
|
|
49939
50039
|
*/
|
|
49940
|
-
this.getData = async () => {
|
|
50040
|
+
this.getData = async (_when) => {
|
|
49941
50041
|
return null;
|
|
49942
50042
|
};
|
|
49943
50043
|
/**
|
|
49944
50044
|
* No-op write — discards the value.
|
|
49945
50045
|
*/
|
|
49946
|
-
this.setData = async (_value) => {
|
|
50046
|
+
this.setData = async (_value, _when) => {
|
|
49947
50047
|
};
|
|
49948
50048
|
}
|
|
49949
50049
|
/** No-op. */
|
|
@@ -49969,6 +50069,7 @@ class SessionPersistInstance {
|
|
|
49969
50069
|
this.frameName = frameName;
|
|
49970
50070
|
this.backtest = backtest$1;
|
|
49971
50071
|
this._data = null;
|
|
50072
|
+
this._when = 0;
|
|
49972
50073
|
/**
|
|
49973
50074
|
* Initialize persistence storage and restore session from disk.
|
|
49974
50075
|
* @param initial - Whether this is the first initialization
|
|
@@ -49984,35 +50085,47 @@ class SessionPersistInstance {
|
|
|
49984
50085
|
const data = await PersistSessionAdapter.readSessionData(this.strategyName, this.exchangeName, this.frameName);
|
|
49985
50086
|
if (data) {
|
|
49986
50087
|
this._data = data.data;
|
|
50088
|
+
this._when = data.when;
|
|
49987
50089
|
return;
|
|
49988
50090
|
}
|
|
49989
50091
|
this._data = null;
|
|
50092
|
+
this._when = 0;
|
|
49990
50093
|
});
|
|
49991
50094
|
/**
|
|
49992
50095
|
* Read the current persisted session value.
|
|
49993
|
-
*
|
|
50096
|
+
* Returns null if the stored `when` is greater than the requested `when`
|
|
50097
|
+
* (look-ahead bias protection).
|
|
50098
|
+
* @param when - Logical timestamp at which the read is happening
|
|
50099
|
+
* @returns Current session value, or null
|
|
49994
50100
|
*/
|
|
49995
|
-
this.getData = async () => {
|
|
50101
|
+
this.getData = async (when) => {
|
|
49996
50102
|
backtest.loggerService.debug(SESSION_PERSIST_INSTANCE_METHOD_NAME_GET, {
|
|
49997
50103
|
strategyName: this.strategyName,
|
|
49998
50104
|
exchangeName: this.exchangeName,
|
|
49999
50105
|
frameName: this.frameName,
|
|
50000
50106
|
});
|
|
50107
|
+
if (this._when > when.getTime()) {
|
|
50108
|
+
return null;
|
|
50109
|
+
}
|
|
50001
50110
|
return this._data;
|
|
50002
50111
|
};
|
|
50003
50112
|
/**
|
|
50004
50113
|
* Update session value and persist to disk atomically.
|
|
50114
|
+
* A write with a smaller `when` overwrites an existing record — that lets
|
|
50115
|
+
* a restarted backtest reset live-written state without breaking live.
|
|
50005
50116
|
* @param value - New value or null to clear
|
|
50117
|
+
* @param when - Logical timestamp this value belongs to
|
|
50006
50118
|
*/
|
|
50007
|
-
this.setData = async (value) => {
|
|
50119
|
+
this.setData = async (value, when) => {
|
|
50008
50120
|
backtest.loggerService.debug(SESSION_PERSIST_INSTANCE_METHOD_NAME_SET, {
|
|
50009
50121
|
strategyName: this.strategyName,
|
|
50010
50122
|
exchangeName: this.exchangeName,
|
|
50011
50123
|
frameName: this.frameName,
|
|
50012
50124
|
});
|
|
50013
50125
|
this._data = value;
|
|
50126
|
+
this._when = when.getTime();
|
|
50014
50127
|
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);
|
|
50128
|
+
await PersistSessionAdapter.writeSessionData({ id, data: value, when: this._when }, this.strategyName, this.exchangeName, this.frameName, when);
|
|
50016
50129
|
};
|
|
50017
50130
|
}
|
|
50018
50131
|
/** Releases resources held by this instance. */
|
|
@@ -50045,9 +50158,10 @@ class SessionBacktestAdapter {
|
|
|
50045
50158
|
* @param context.strategyName - Strategy identifier
|
|
50046
50159
|
* @param context.exchangeName - Exchange identifier
|
|
50047
50160
|
* @param context.frameName - Frame identifier
|
|
50048
|
-
* @
|
|
50161
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50162
|
+
* @returns Current session value, or null if not set / look-ahead
|
|
50049
50163
|
*/
|
|
50050
|
-
this.getData = async (symbol, context) => {
|
|
50164
|
+
this.getData = async (symbol, context, when) => {
|
|
50051
50165
|
backtest.loggerService.debug(SESSION_BACKTEST_ADAPTER_METHOD_NAME_GET, {
|
|
50052
50166
|
strategyName: context.strategyName,
|
|
50053
50167
|
exchangeName: context.exchangeName,
|
|
@@ -50057,7 +50171,7 @@ class SessionBacktestAdapter {
|
|
|
50057
50171
|
const isInitial = !this.getInstance.has(key);
|
|
50058
50172
|
const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, true);
|
|
50059
50173
|
await instance.waitForInit(isInitial);
|
|
50060
|
-
return await instance.getData();
|
|
50174
|
+
return await instance.getData(when);
|
|
50061
50175
|
};
|
|
50062
50176
|
/**
|
|
50063
50177
|
* Update the session value for a backtest run.
|
|
@@ -50066,8 +50180,9 @@ class SessionBacktestAdapter {
|
|
|
50066
50180
|
* @param context.strategyName - Strategy identifier
|
|
50067
50181
|
* @param context.exchangeName - Exchange identifier
|
|
50068
50182
|
* @param context.frameName - Frame identifier
|
|
50183
|
+
* @param when - Logical timestamp this value belongs to
|
|
50069
50184
|
*/
|
|
50070
|
-
this.setData = async (symbol, value, context) => {
|
|
50185
|
+
this.setData = async (symbol, value, context, when) => {
|
|
50071
50186
|
backtest.loggerService.debug(SESSION_BACKTEST_ADAPTER_METHOD_NAME_SET, {
|
|
50072
50187
|
strategyName: context.strategyName,
|
|
50073
50188
|
exchangeName: context.exchangeName,
|
|
@@ -50077,7 +50192,7 @@ class SessionBacktestAdapter {
|
|
|
50077
50192
|
const isInitial = !this.getInstance.has(key);
|
|
50078
50193
|
const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, true);
|
|
50079
50194
|
await instance.waitForInit(isInitial);
|
|
50080
|
-
return await instance.setData(value);
|
|
50195
|
+
return await instance.setData(value, when);
|
|
50081
50196
|
};
|
|
50082
50197
|
/**
|
|
50083
50198
|
* Switches to in-memory adapter (default).
|
|
@@ -50141,9 +50256,10 @@ class SessionLiveAdapter {
|
|
|
50141
50256
|
* @param context.strategyName - Strategy identifier
|
|
50142
50257
|
* @param context.exchangeName - Exchange identifier
|
|
50143
50258
|
* @param context.frameName - Frame identifier
|
|
50144
|
-
* @
|
|
50259
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50260
|
+
* @returns Current session value, or null if not set / look-ahead
|
|
50145
50261
|
*/
|
|
50146
|
-
this.getData = async (symbol, context) => {
|
|
50262
|
+
this.getData = async (symbol, context, when) => {
|
|
50147
50263
|
backtest.loggerService.debug(SESSION_LIVE_ADAPTER_METHOD_NAME_GET, {
|
|
50148
50264
|
strategyName: context.strategyName,
|
|
50149
50265
|
exchangeName: context.exchangeName,
|
|
@@ -50153,7 +50269,7 @@ class SessionLiveAdapter {
|
|
|
50153
50269
|
const isInitial = !this.getInstance.has(key);
|
|
50154
50270
|
const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, false);
|
|
50155
50271
|
await instance.waitForInit(isInitial);
|
|
50156
|
-
return await instance.getData();
|
|
50272
|
+
return await instance.getData(when);
|
|
50157
50273
|
};
|
|
50158
50274
|
/**
|
|
50159
50275
|
* Update the session value for a live run.
|
|
@@ -50162,8 +50278,9 @@ class SessionLiveAdapter {
|
|
|
50162
50278
|
* @param context.strategyName - Strategy identifier
|
|
50163
50279
|
* @param context.exchangeName - Exchange identifier
|
|
50164
50280
|
* @param context.frameName - Frame identifier
|
|
50281
|
+
* @param when - Logical timestamp this value belongs to
|
|
50165
50282
|
*/
|
|
50166
|
-
this.setData = async (symbol, value, context) => {
|
|
50283
|
+
this.setData = async (symbol, value, context, when) => {
|
|
50167
50284
|
backtest.loggerService.debug(SESSION_LIVE_ADAPTER_METHOD_NAME_SET, {
|
|
50168
50285
|
strategyName: context.strategyName,
|
|
50169
50286
|
exchangeName: context.exchangeName,
|
|
@@ -50173,7 +50290,7 @@ class SessionLiveAdapter {
|
|
|
50173
50290
|
const isInitial = !this.getInstance.has(key);
|
|
50174
50291
|
const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, false);
|
|
50175
50292
|
await instance.waitForInit(isInitial);
|
|
50176
|
-
return await instance.setData(value);
|
|
50293
|
+
return await instance.setData(value, when);
|
|
50177
50294
|
};
|
|
50178
50295
|
/**
|
|
50179
50296
|
* Switches to in-memory adapter.
|
|
@@ -50233,9 +50350,10 @@ class SessionAdapter {
|
|
|
50233
50350
|
* @param context.exchangeName - Exchange identifier
|
|
50234
50351
|
* @param context.frameName - Frame identifier
|
|
50235
50352
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
50236
|
-
* @
|
|
50353
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50354
|
+
* @returns Current session value, or null if not set / look-ahead
|
|
50237
50355
|
*/
|
|
50238
|
-
this.getData = async (symbol, context, backtest$1) => {
|
|
50356
|
+
this.getData = async (symbol, context, backtest$1, when) => {
|
|
50239
50357
|
backtest.loggerService.debug(SESSION_ADAPTER_METHOD_NAME_GET, {
|
|
50240
50358
|
strategyName: context.strategyName,
|
|
50241
50359
|
exchangeName: context.exchangeName,
|
|
@@ -50243,9 +50361,9 @@ class SessionAdapter {
|
|
|
50243
50361
|
backtest: backtest$1,
|
|
50244
50362
|
});
|
|
50245
50363
|
if (backtest$1) {
|
|
50246
|
-
return await SessionBacktest.getData(symbol, context);
|
|
50364
|
+
return await SessionBacktest.getData(symbol, context, when);
|
|
50247
50365
|
}
|
|
50248
|
-
return await SessionLive.getData(symbol, context);
|
|
50366
|
+
return await SessionLive.getData(symbol, context, when);
|
|
50249
50367
|
};
|
|
50250
50368
|
/**
|
|
50251
50369
|
* Update the session value for a signal.
|
|
@@ -50256,8 +50374,9 @@ class SessionAdapter {
|
|
|
50256
50374
|
* @param context.exchangeName - Exchange identifier
|
|
50257
50375
|
* @param context.frameName - Frame identifier
|
|
50258
50376
|
* @param backtest - Flag indicating if the context is backtest or live
|
|
50377
|
+
* @param when - Logical timestamp this value belongs to
|
|
50259
50378
|
*/
|
|
50260
|
-
this.setData = async (symbol, value, context, backtest$1) => {
|
|
50379
|
+
this.setData = async (symbol, value, context, backtest$1, when) => {
|
|
50261
50380
|
backtest.loggerService.debug(SESSION_ADAPTER_METHOD_NAME_SET, {
|
|
50262
50381
|
strategyName: context.strategyName,
|
|
50263
50382
|
exchangeName: context.exchangeName,
|
|
@@ -50265,9 +50384,9 @@ class SessionAdapter {
|
|
|
50265
50384
|
backtest: backtest$1,
|
|
50266
50385
|
});
|
|
50267
50386
|
if (backtest$1) {
|
|
50268
|
-
return await SessionBacktest.setData(symbol, value, context);
|
|
50387
|
+
return await SessionBacktest.setData(symbol, value, context, when);
|
|
50269
50388
|
}
|
|
50270
|
-
return await SessionLive.setData(symbol, value, context);
|
|
50389
|
+
return await SessionLive.setData(symbol, value, context, when);
|
|
50271
50390
|
};
|
|
50272
50391
|
}
|
|
50273
50392
|
}
|
|
@@ -50319,9 +50438,9 @@ async function getSessionData(symbol) {
|
|
|
50319
50438
|
if (!MethodContextService.hasContext()) {
|
|
50320
50439
|
throw new Error("getSession requires a method context");
|
|
50321
50440
|
}
|
|
50322
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
50441
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
50323
50442
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
50324
|
-
return await Session.getData(symbol, { exchangeName, frameName, strategyName }, isBacktest);
|
|
50443
|
+
return await Session.getData(symbol, { exchangeName, frameName, strategyName }, isBacktest, when);
|
|
50325
50444
|
}
|
|
50326
50445
|
/**
|
|
50327
50446
|
* Writes a session value scoped to the current (symbol, strategy, exchange, frame) context.
|
|
@@ -50353,9 +50472,9 @@ async function setSessionData(symbol, value) {
|
|
|
50353
50472
|
if (!MethodContextService.hasContext()) {
|
|
50354
50473
|
throw new Error("setSession requires a method context");
|
|
50355
50474
|
}
|
|
50356
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
50475
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
50357
50476
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
50358
|
-
await Session.setData(symbol, value, { exchangeName, frameName, strategyName }, isBacktest);
|
|
50477
|
+
await Session.setData(symbol, value, { exchangeName, frameName, strategyName }, isBacktest, when);
|
|
50359
50478
|
}
|
|
50360
50479
|
|
|
50361
50480
|
const CREATE_SIGNAL_STATE_METHOD_NAME = "state.createSignalState";
|
|
@@ -50366,7 +50485,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
|
|
|
50366
50485
|
if (!MethodContextService.hasContext()) {
|
|
50367
50486
|
throw new Error("createSignalState requires a method context");
|
|
50368
50487
|
}
|
|
50369
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
50488
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
50370
50489
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
50371
50490
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
50372
50491
|
let signal;
|
|
@@ -50376,6 +50495,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
|
|
|
50376
50495
|
bucketName: params.bucketName,
|
|
50377
50496
|
initialValue: params.initialValue,
|
|
50378
50497
|
signalId: signal.id,
|
|
50498
|
+
when,
|
|
50379
50499
|
});
|
|
50380
50500
|
}
|
|
50381
50501
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -50384,6 +50504,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
|
|
|
50384
50504
|
bucketName: params.bucketName,
|
|
50385
50505
|
initialValue: params.initialValue,
|
|
50386
50506
|
signalId: signal.id,
|
|
50507
|
+
when,
|
|
50387
50508
|
});
|
|
50388
50509
|
}
|
|
50389
50510
|
throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
|
|
@@ -50395,7 +50516,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
|
|
|
50395
50516
|
if (!MethodContextService.hasContext()) {
|
|
50396
50517
|
throw new Error("createSignalState requires a method context");
|
|
50397
50518
|
}
|
|
50398
|
-
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
50519
|
+
const { backtest: isBacktest, when } = backtest.executionContextService.context;
|
|
50399
50520
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
50400
50521
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
50401
50522
|
let signal;
|
|
@@ -50405,6 +50526,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
|
|
|
50405
50526
|
bucketName: params.bucketName,
|
|
50406
50527
|
initialValue: params.initialValue,
|
|
50407
50528
|
signalId: signal.id,
|
|
50529
|
+
when,
|
|
50408
50530
|
});
|
|
50409
50531
|
}
|
|
50410
50532
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -50413,6 +50535,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
|
|
|
50413
50535
|
bucketName: params.bucketName,
|
|
50414
50536
|
initialValue: params.initialValue,
|
|
50415
50537
|
signalId: signal.id,
|
|
50538
|
+
when,
|
|
50416
50539
|
});
|
|
50417
50540
|
}
|
|
50418
50541
|
throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
|
|
@@ -50495,7 +50618,7 @@ const createSearchIndex = () => {
|
|
|
50495
50618
|
df.set(term, count);
|
|
50496
50619
|
});
|
|
50497
50620
|
};
|
|
50498
|
-
const upsert = ({ id, content, index = JSON.stringify(content), priority = Date.now(), }) => {
|
|
50621
|
+
const upsert = ({ id, content, when, index = JSON.stringify(content), priority = Date.now(), }) => {
|
|
50499
50622
|
const existing = docs.get(id);
|
|
50500
50623
|
{
|
|
50501
50624
|
existing && subtractDf(existing.tf);
|
|
@@ -50504,12 +50627,21 @@ const createSearchIndex = () => {
|
|
|
50504
50627
|
const tf = new Map();
|
|
50505
50628
|
for (const t of tokens)
|
|
50506
50629
|
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
50507
|
-
docs.set(id, { tf, len: tokens.length, content, priority });
|
|
50630
|
+
docs.set(id, { tf, len: tokens.length, content, priority, when });
|
|
50508
50631
|
{
|
|
50509
50632
|
addDf(tf);
|
|
50510
50633
|
}
|
|
50511
50634
|
};
|
|
50512
|
-
|
|
50635
|
+
/**
|
|
50636
|
+
* Read a document by id. Returns undefined when the document was written
|
|
50637
|
+
* with a `when` greater than the requested `when` (look-ahead guard).
|
|
50638
|
+
*/
|
|
50639
|
+
const read = (id, when) => {
|
|
50640
|
+
const doc = docs.get(id);
|
|
50641
|
+
if (!doc || doc.when > when)
|
|
50642
|
+
return undefined;
|
|
50643
|
+
return doc.content;
|
|
50644
|
+
};
|
|
50513
50645
|
const remove = (id) => {
|
|
50514
50646
|
{
|
|
50515
50647
|
const existing = docs.get(id);
|
|
@@ -50517,16 +50649,30 @@ const createSearchIndex = () => {
|
|
|
50517
50649
|
}
|
|
50518
50650
|
docs.delete(id);
|
|
50519
50651
|
};
|
|
50520
|
-
|
|
50652
|
+
/**
|
|
50653
|
+
* List documents whose `when` is less than or equal to the requested `when`
|
|
50654
|
+
* (look-ahead guard), sorted by priority.
|
|
50655
|
+
*/
|
|
50656
|
+
const list = (when) => Array.from(docs.entries())
|
|
50657
|
+
.filter(([, doc]) => doc.when <= when)
|
|
50521
50658
|
.sort(([, a], [, b]) => a.priority - b.priority)
|
|
50522
50659
|
.map(([id, { content }]) => ({ id, content }));
|
|
50523
|
-
|
|
50660
|
+
/**
|
|
50661
|
+
* BM25 search over documents whose `when` is less than or equal to the
|
|
50662
|
+
* requested `when` (look-ahead guard).
|
|
50663
|
+
*
|
|
50664
|
+
* Document frequency (df) is computed across the whole index — the time-cut
|
|
50665
|
+
* is applied only to the candidate set, so scores stay comparable across
|
|
50666
|
+
* different `when` values.
|
|
50667
|
+
*/
|
|
50668
|
+
const search = (query, when, settings = DEFAULT_SETTINGS) => {
|
|
50524
50669
|
const terms = tokenize(query);
|
|
50525
50670
|
if (!terms.length || !docs.size)
|
|
50526
50671
|
return [];
|
|
50527
50672
|
const N = docs.size;
|
|
50528
50673
|
const avgLen = [...docs.values()].reduce((s, d) => s + d.len, 0) / N;
|
|
50529
50674
|
return [...docs.entries()]
|
|
50675
|
+
.filter(([, doc]) => doc.when <= when)
|
|
50530
50676
|
.map(([id, doc]) => {
|
|
50531
50677
|
let score = 0;
|
|
50532
50678
|
for (const term of terms) {
|
|
@@ -50623,8 +50769,9 @@ class MemoryLocalInstance {
|
|
|
50623
50769
|
* @param memoryId - Unique entry identifier
|
|
50624
50770
|
* @param value - Value to store and index
|
|
50625
50771
|
* @param description - BM25 index string
|
|
50772
|
+
* @param when - Logical timestamp this entry belongs to (look-ahead guard)
|
|
50626
50773
|
*/
|
|
50627
|
-
async writeMemory(memoryId, value, description) {
|
|
50774
|
+
async writeMemory(memoryId, value, description, when) {
|
|
50628
50775
|
backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_WRITE, {
|
|
50629
50776
|
signalId: this.signalId,
|
|
50630
50777
|
bucketName: this.bucketName,
|
|
@@ -50635,21 +50782,24 @@ class MemoryLocalInstance {
|
|
|
50635
50782
|
content: value,
|
|
50636
50783
|
index: description,
|
|
50637
50784
|
priority: Date.now(),
|
|
50785
|
+
when: when.getTime(),
|
|
50638
50786
|
});
|
|
50639
50787
|
}
|
|
50640
50788
|
/**
|
|
50641
50789
|
* Read a single entry from the in-memory index.
|
|
50790
|
+
* Behaves as not-found if the stored `when` is greater than the requested `when`.
|
|
50642
50791
|
* @param memoryId - Unique entry identifier
|
|
50792
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50643
50793
|
* @returns Parsed entry value
|
|
50644
|
-
* @throws Error if entry not found
|
|
50794
|
+
* @throws Error if entry not found (or shadowed by look-ahead)
|
|
50645
50795
|
*/
|
|
50646
|
-
async readMemory(memoryId) {
|
|
50796
|
+
async readMemory(memoryId, when) {
|
|
50647
50797
|
backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_READ, {
|
|
50648
50798
|
signalId: this.signalId,
|
|
50649
50799
|
bucketName: this.bucketName,
|
|
50650
50800
|
memoryId,
|
|
50651
50801
|
});
|
|
50652
|
-
const value = this._index.read(memoryId);
|
|
50802
|
+
const value = this._index.read(memoryId, when.getTime());
|
|
50653
50803
|
if (!value) {
|
|
50654
50804
|
throw new Error(`MemoryLocalInstance value not found memoryId=${memoryId}`);
|
|
50655
50805
|
}
|
|
@@ -50657,33 +50807,38 @@ class MemoryLocalInstance {
|
|
|
50657
50807
|
}
|
|
50658
50808
|
/**
|
|
50659
50809
|
* Search entries using BM25 full-text scoring.
|
|
50810
|
+
* Filters out entries whose `when` is greater than the requested `when`.
|
|
50660
50811
|
* @param query - Search query string
|
|
50812
|
+
* @param when - Logical timestamp at which the search is happening (look-ahead guard)
|
|
50661
50813
|
* @returns Matching entries sorted by relevance score
|
|
50662
50814
|
*/
|
|
50663
|
-
async searchMemory(query, settings) {
|
|
50815
|
+
async searchMemory(query, when, settings) {
|
|
50664
50816
|
backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_SEARCH, {
|
|
50665
50817
|
signalId: this.signalId,
|
|
50666
50818
|
bucketName: this.bucketName,
|
|
50667
50819
|
query,
|
|
50668
50820
|
});
|
|
50669
|
-
return this._index.search(query, settings).map(SEARCH_MEMORY_FN);
|
|
50821
|
+
return this._index.search(query, when.getTime(), settings).map(SEARCH_MEMORY_FN);
|
|
50670
50822
|
}
|
|
50671
50823
|
/**
|
|
50672
50824
|
* List all entries stored in the in-memory index.
|
|
50825
|
+
* Filters out entries whose `when` is greater than the requested `when`.
|
|
50826
|
+
* @param when - Logical timestamp at which the list is happening (look-ahead guard)
|
|
50673
50827
|
* @returns Array of all stored entries
|
|
50674
50828
|
*/
|
|
50675
|
-
async listMemory() {
|
|
50829
|
+
async listMemory(when) {
|
|
50676
50830
|
backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_LIST, {
|
|
50677
50831
|
signalId: this.signalId,
|
|
50678
50832
|
bucketName: this.bucketName,
|
|
50679
50833
|
});
|
|
50680
|
-
return this._index.list().map(LIST_MEMORY_FN);
|
|
50834
|
+
return this._index.list(when.getTime()).map(LIST_MEMORY_FN);
|
|
50681
50835
|
}
|
|
50682
50836
|
/**
|
|
50683
50837
|
* Remove an entry from the in-memory index.
|
|
50684
50838
|
* @param memoryId - Unique entry identifier
|
|
50839
|
+
* @param when - Logical timestamp (kept for API consistency; removal is by UUID)
|
|
50685
50840
|
*/
|
|
50686
|
-
async removeMemory(memoryId) {
|
|
50841
|
+
async removeMemory(memoryId, _when) {
|
|
50687
50842
|
backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_REMOVE, {
|
|
50688
50843
|
signalId: this.signalId,
|
|
50689
50844
|
bucketName: this.bucketName,
|
|
@@ -50729,12 +50884,13 @@ class MemoryPersistInstance {
|
|
|
50729
50884
|
initial,
|
|
50730
50885
|
});
|
|
50731
50886
|
await PersistMemoryAdapter.waitForInit(this.signalId, this.bucketName, initial);
|
|
50732
|
-
for await (const { memoryId, data: { data, index, priority } } of PersistMemoryAdapter.listMemoryData(this.signalId, this.bucketName)) {
|
|
50887
|
+
for await (const { memoryId, data: { data, index, priority, when } } of PersistMemoryAdapter.listMemoryData(this.signalId, this.bucketName)) {
|
|
50733
50888
|
this._index.upsert({
|
|
50734
50889
|
id: memoryId,
|
|
50735
50890
|
content: data,
|
|
50736
50891
|
index,
|
|
50737
50892
|
priority,
|
|
50893
|
+
when,
|
|
50738
50894
|
});
|
|
50739
50895
|
}
|
|
50740
50896
|
}
|
|
@@ -50743,69 +50899,79 @@ class MemoryPersistInstance {
|
|
|
50743
50899
|
* @param memoryId - Unique entry identifier
|
|
50744
50900
|
* @param value - Value to persist and index
|
|
50745
50901
|
* @param index - BM25 index string; defaults to JSON.stringify(value)
|
|
50902
|
+
* @param when - Logical timestamp this entry belongs to (look-ahead guard)
|
|
50746
50903
|
*/
|
|
50747
|
-
async writeMemory(memoryId, value, index
|
|
50904
|
+
async writeMemory(memoryId, value, index, when) {
|
|
50748
50905
|
backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_WRITE, {
|
|
50749
50906
|
signalId: this.signalId,
|
|
50750
50907
|
bucketName: this.bucketName,
|
|
50751
50908
|
memoryId,
|
|
50752
50909
|
});
|
|
50753
50910
|
const priority = Date.now();
|
|
50754
|
-
|
|
50911
|
+
const whenMs = when.getTime();
|
|
50912
|
+
await PersistMemoryAdapter.writeMemoryData({ data: value, priority, removed: false, index, when: whenMs }, this.signalId, this.bucketName, memoryId, when);
|
|
50755
50913
|
this._index.upsert({
|
|
50756
50914
|
id: memoryId,
|
|
50757
50915
|
content: value,
|
|
50758
50916
|
index,
|
|
50759
50917
|
priority,
|
|
50918
|
+
when: whenMs,
|
|
50760
50919
|
});
|
|
50761
50920
|
}
|
|
50762
50921
|
/**
|
|
50763
50922
|
* Read a single entry from disk.
|
|
50923
|
+
* Behaves as not-found if the stored `when` is greater than the requested `when`.
|
|
50764
50924
|
* @param memoryId - Unique entry identifier
|
|
50925
|
+
* @param when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50765
50926
|
* @returns Entry value
|
|
50766
|
-
* @throws Error if entry not found
|
|
50927
|
+
* @throws Error if entry not found (or shadowed by look-ahead)
|
|
50767
50928
|
*/
|
|
50768
|
-
async readMemory(memoryId) {
|
|
50929
|
+
async readMemory(memoryId, when) {
|
|
50769
50930
|
backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_READ, {
|
|
50770
50931
|
signalId: this.signalId,
|
|
50771
50932
|
bucketName: this.bucketName,
|
|
50772
50933
|
memoryId,
|
|
50773
50934
|
});
|
|
50774
50935
|
const data = await PersistMemoryAdapter.readMemoryData(this.signalId, this.bucketName, memoryId);
|
|
50775
|
-
if (!data) {
|
|
50936
|
+
if (!data || data.when > when.getTime()) {
|
|
50776
50937
|
throw new Error(`MemoryPersistInstance value not found memoryId=${memoryId}`);
|
|
50777
50938
|
}
|
|
50778
50939
|
return data.data;
|
|
50779
50940
|
}
|
|
50780
50941
|
/**
|
|
50781
50942
|
* Search entries using BM25 index rebuilt from disk on init.
|
|
50943
|
+
* Filters out entries whose `when` is greater than the requested `when`.
|
|
50782
50944
|
* @param query - Search query string
|
|
50945
|
+
* @param when - Logical timestamp at which the search is happening (look-ahead guard)
|
|
50783
50946
|
* @returns Matching entries sorted by relevance score
|
|
50784
50947
|
*/
|
|
50785
|
-
async searchMemory(query, settings) {
|
|
50948
|
+
async searchMemory(query, when, settings) {
|
|
50786
50949
|
backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_SEARCH, {
|
|
50787
50950
|
signalId: this.signalId,
|
|
50788
50951
|
bucketName: this.bucketName,
|
|
50789
50952
|
query,
|
|
50790
50953
|
});
|
|
50791
|
-
return this._index.search(query, settings).map(SEARCH_MEMORY_FN);
|
|
50954
|
+
return this._index.search(query, when.getTime(), settings).map(SEARCH_MEMORY_FN);
|
|
50792
50955
|
}
|
|
50793
50956
|
/**
|
|
50794
50957
|
* List all entries from the in-memory index (populated from disk on init).
|
|
50958
|
+
* Filters out entries whose `when` is greater than the requested `when`.
|
|
50959
|
+
* @param when - Logical timestamp at which the list is happening (look-ahead guard)
|
|
50795
50960
|
* @returns Array of all stored entries
|
|
50796
50961
|
*/
|
|
50797
|
-
async listMemory() {
|
|
50962
|
+
async listMemory(when) {
|
|
50798
50963
|
backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_LIST, {
|
|
50799
50964
|
signalId: this.signalId,
|
|
50800
50965
|
bucketName: this.bucketName,
|
|
50801
50966
|
});
|
|
50802
|
-
return this._index.list().map(LIST_MEMORY_FN);
|
|
50967
|
+
return this._index.list(when.getTime()).map(LIST_MEMORY_FN);
|
|
50803
50968
|
}
|
|
50804
50969
|
/**
|
|
50805
50970
|
* Remove an entry from disk and from the BM25 index.
|
|
50806
50971
|
* @param memoryId - Unique entry identifier
|
|
50972
|
+
* @param when - Logical timestamp (kept for API consistency; removal is by UUID)
|
|
50807
50973
|
*/
|
|
50808
|
-
async removeMemory(memoryId) {
|
|
50974
|
+
async removeMemory(memoryId, _when) {
|
|
50809
50975
|
backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_REMOVE, {
|
|
50810
50976
|
signalId: this.signalId,
|
|
50811
50977
|
bucketName: this.bucketName,
|
|
@@ -50842,34 +51008,34 @@ class MemoryDummyInstance {
|
|
|
50842
51008
|
* No-op write - discards the value.
|
|
50843
51009
|
* @returns Promise that resolves immediately
|
|
50844
51010
|
*/
|
|
50845
|
-
async writeMemory() {
|
|
51011
|
+
async writeMemory(_memoryId, _value, _description, _when) {
|
|
50846
51012
|
}
|
|
50847
51013
|
/**
|
|
50848
51014
|
* No-op read - always throws.
|
|
50849
51015
|
* @throws Error always
|
|
50850
51016
|
*/
|
|
50851
|
-
async readMemory(_memoryId) {
|
|
51017
|
+
async readMemory(_memoryId, _when) {
|
|
50852
51018
|
throw new Error("MemoryDummyInstance: readMemory not supported");
|
|
50853
51019
|
}
|
|
50854
51020
|
/**
|
|
50855
51021
|
* No-op search - returns empty array.
|
|
50856
51022
|
* @returns Empty array
|
|
50857
51023
|
*/
|
|
50858
|
-
async searchMemory() {
|
|
51024
|
+
async searchMemory(_query, _when, _settings) {
|
|
50859
51025
|
return [];
|
|
50860
51026
|
}
|
|
50861
51027
|
/**
|
|
50862
51028
|
* No-op list - returns empty array.
|
|
50863
51029
|
* @returns Empty array
|
|
50864
51030
|
*/
|
|
50865
|
-
async listMemory() {
|
|
51031
|
+
async listMemory(_when) {
|
|
50866
51032
|
return [];
|
|
50867
51033
|
}
|
|
50868
51034
|
/**
|
|
50869
51035
|
* No-op remove.
|
|
50870
51036
|
* @returns Promise that resolves immediately
|
|
50871
51037
|
*/
|
|
50872
|
-
async removeMemory() {
|
|
51038
|
+
async removeMemory(_memoryId, _when) {
|
|
50873
51039
|
}
|
|
50874
51040
|
/** No-op. */
|
|
50875
51041
|
dispose() {
|
|
@@ -50913,6 +51079,7 @@ class MemoryBacktestAdapter {
|
|
|
50913
51079
|
* @param dto.signalId - Signal identifier
|
|
50914
51080
|
* @param dto.bucketName - Bucket name
|
|
50915
51081
|
* @param dto.description - BM25 index string; defaults to JSON.stringify(value)
|
|
51082
|
+
* @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
|
|
50916
51083
|
*/
|
|
50917
51084
|
this.writeMemory = async (dto) => {
|
|
50918
51085
|
backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_WRITE, {
|
|
@@ -50924,13 +51091,14 @@ class MemoryBacktestAdapter {
|
|
|
50924
51091
|
const isInitial = !this.getInstance.has(key);
|
|
50925
51092
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
50926
51093
|
await instance.waitForInit(isInitial);
|
|
50927
|
-
return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
|
|
51094
|
+
return await instance.writeMemory(dto.memoryId, dto.value, dto.description, dto.when);
|
|
50928
51095
|
};
|
|
50929
51096
|
/**
|
|
50930
51097
|
* Search memory using BM25 full-text scoring.
|
|
50931
51098
|
* @param dto.query - Search query string
|
|
50932
51099
|
* @param dto.signalId - Signal identifier
|
|
50933
51100
|
* @param dto.bucketName - Bucket name
|
|
51101
|
+
* @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
|
|
50934
51102
|
* @returns Matching entries sorted by relevance score
|
|
50935
51103
|
*/
|
|
50936
51104
|
this.searchMemory = async (dto) => {
|
|
@@ -50943,12 +51111,13 @@ class MemoryBacktestAdapter {
|
|
|
50943
51111
|
const isInitial = !this.getInstance.has(key);
|
|
50944
51112
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
50945
51113
|
await instance.waitForInit(isInitial);
|
|
50946
|
-
return await instance.searchMemory(dto.query, dto.settings);
|
|
51114
|
+
return await instance.searchMemory(dto.query, dto.when, dto.settings);
|
|
50947
51115
|
};
|
|
50948
51116
|
/**
|
|
50949
51117
|
* List all entries in memory.
|
|
50950
51118
|
* @param dto.signalId - Signal identifier
|
|
50951
51119
|
* @param dto.bucketName - Bucket name
|
|
51120
|
+
* @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
|
|
50952
51121
|
* @returns Array of all stored entries
|
|
50953
51122
|
*/
|
|
50954
51123
|
this.listMemory = async (dto) => {
|
|
@@ -50960,13 +51129,14 @@ class MemoryBacktestAdapter {
|
|
|
50960
51129
|
const isInitial = !this.getInstance.has(key);
|
|
50961
51130
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
50962
51131
|
await instance.waitForInit(isInitial);
|
|
50963
|
-
return await instance.listMemory();
|
|
51132
|
+
return await instance.listMemory(dto.when);
|
|
50964
51133
|
};
|
|
50965
51134
|
/**
|
|
50966
51135
|
* Remove an entry from memory.
|
|
50967
51136
|
* @param dto.memoryId - Unique entry identifier
|
|
50968
51137
|
* @param dto.signalId - Signal identifier
|
|
50969
51138
|
* @param dto.bucketName - Bucket name
|
|
51139
|
+
* @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
|
|
50970
51140
|
*/
|
|
50971
51141
|
this.removeMemory = async (dto) => {
|
|
50972
51142
|
backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_REMOVE, {
|
|
@@ -50978,13 +51148,14 @@ class MemoryBacktestAdapter {
|
|
|
50978
51148
|
const isInitial = !this.getInstance.has(key);
|
|
50979
51149
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
50980
51150
|
await instance.waitForInit(isInitial);
|
|
50981
|
-
return await instance.removeMemory(dto.memoryId);
|
|
51151
|
+
return await instance.removeMemory(dto.memoryId, dto.when);
|
|
50982
51152
|
};
|
|
50983
51153
|
/**
|
|
50984
51154
|
* Read a single entry from memory.
|
|
50985
51155
|
* @param dto.memoryId - Unique entry identifier
|
|
50986
51156
|
* @param dto.signalId - Signal identifier
|
|
50987
51157
|
* @param dto.bucketName - Bucket name
|
|
51158
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
50988
51159
|
* @returns Entry value
|
|
50989
51160
|
* @throws Error if entry not found
|
|
50990
51161
|
*/
|
|
@@ -50998,7 +51169,7 @@ class MemoryBacktestAdapter {
|
|
|
50998
51169
|
const isInitial = !this.getInstance.has(key);
|
|
50999
51170
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51000
51171
|
await instance.waitForInit(isInitial);
|
|
51001
|
-
return await instance.readMemory(dto.memoryId);
|
|
51172
|
+
return await instance.readMemory(dto.memoryId, dto.when);
|
|
51002
51173
|
};
|
|
51003
51174
|
/**
|
|
51004
51175
|
* Switches to in-memory BM25 adapter (default).
|
|
@@ -51080,6 +51251,7 @@ class MemoryLiveAdapter {
|
|
|
51080
51251
|
* @param dto.signalId - Signal identifier
|
|
51081
51252
|
* @param dto.bucketName - Bucket name
|
|
51082
51253
|
* @param dto.description - BM25 index string; defaults to JSON.stringify(value)
|
|
51254
|
+
* @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
|
|
51083
51255
|
*/
|
|
51084
51256
|
this.writeMemory = async (dto) => {
|
|
51085
51257
|
backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_WRITE, {
|
|
@@ -51091,13 +51263,14 @@ class MemoryLiveAdapter {
|
|
|
51091
51263
|
const isInitial = !this.getInstance.has(key);
|
|
51092
51264
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51093
51265
|
await instance.waitForInit(isInitial);
|
|
51094
|
-
return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
|
|
51266
|
+
return await instance.writeMemory(dto.memoryId, dto.value, dto.description, dto.when);
|
|
51095
51267
|
};
|
|
51096
51268
|
/**
|
|
51097
51269
|
* Search memory using BM25 full-text scoring.
|
|
51098
51270
|
* @param dto.query - Search query string
|
|
51099
51271
|
* @param dto.signalId - Signal identifier
|
|
51100
51272
|
* @param dto.bucketName - Bucket name
|
|
51273
|
+
* @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
|
|
51101
51274
|
* @returns Matching entries sorted by relevance score
|
|
51102
51275
|
*/
|
|
51103
51276
|
this.searchMemory = async (dto) => {
|
|
@@ -51110,12 +51283,13 @@ class MemoryLiveAdapter {
|
|
|
51110
51283
|
const isInitial = !this.getInstance.has(key);
|
|
51111
51284
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51112
51285
|
await instance.waitForInit(isInitial);
|
|
51113
|
-
return await instance.searchMemory(dto.query, dto.settings);
|
|
51286
|
+
return await instance.searchMemory(dto.query, dto.when, dto.settings);
|
|
51114
51287
|
};
|
|
51115
51288
|
/**
|
|
51116
51289
|
* List all entries in memory.
|
|
51117
51290
|
* @param dto.signalId - Signal identifier
|
|
51118
51291
|
* @param dto.bucketName - Bucket name
|
|
51292
|
+
* @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
|
|
51119
51293
|
* @returns Array of all stored entries
|
|
51120
51294
|
*/
|
|
51121
51295
|
this.listMemory = async (dto) => {
|
|
@@ -51127,13 +51301,14 @@ class MemoryLiveAdapter {
|
|
|
51127
51301
|
const isInitial = !this.getInstance.has(key);
|
|
51128
51302
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51129
51303
|
await instance.waitForInit(isInitial);
|
|
51130
|
-
return await instance.listMemory();
|
|
51304
|
+
return await instance.listMemory(dto.when);
|
|
51131
51305
|
};
|
|
51132
51306
|
/**
|
|
51133
51307
|
* Remove an entry from memory.
|
|
51134
51308
|
* @param dto.memoryId - Unique entry identifier
|
|
51135
51309
|
* @param dto.signalId - Signal identifier
|
|
51136
51310
|
* @param dto.bucketName - Bucket name
|
|
51311
|
+
* @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
|
|
51137
51312
|
*/
|
|
51138
51313
|
this.removeMemory = async (dto) => {
|
|
51139
51314
|
backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_REMOVE, {
|
|
@@ -51145,13 +51320,14 @@ class MemoryLiveAdapter {
|
|
|
51145
51320
|
const isInitial = !this.getInstance.has(key);
|
|
51146
51321
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51147
51322
|
await instance.waitForInit(isInitial);
|
|
51148
|
-
return await instance.removeMemory(dto.memoryId);
|
|
51323
|
+
return await instance.removeMemory(dto.memoryId, dto.when);
|
|
51149
51324
|
};
|
|
51150
51325
|
/**
|
|
51151
51326
|
* Read a single entry from memory.
|
|
51152
51327
|
* @param dto.memoryId - Unique entry identifier
|
|
51153
51328
|
* @param dto.signalId - Signal identifier
|
|
51154
51329
|
* @param dto.bucketName - Bucket name
|
|
51330
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
51155
51331
|
* @returns Entry value
|
|
51156
51332
|
* @throws Error if entry not found
|
|
51157
51333
|
*/
|
|
@@ -51165,7 +51341,7 @@ class MemoryLiveAdapter {
|
|
|
51165
51341
|
const isInitial = !this.getInstance.has(key);
|
|
51166
51342
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
51167
51343
|
await instance.waitForInit(isInitial);
|
|
51168
|
-
return await instance.readMemory(dto.memoryId);
|
|
51344
|
+
return await instance.readMemory(dto.memoryId, dto.when);
|
|
51169
51345
|
};
|
|
51170
51346
|
/**
|
|
51171
51347
|
* Switches to in-memory BM25 adapter.
|
|
@@ -51264,6 +51440,7 @@ class MemoryAdapter {
|
|
|
51264
51440
|
* @param dto.bucketName - Bucket name
|
|
51265
51441
|
* @param dto.description - BM25 index string; defaults to JSON.stringify(value)
|
|
51266
51442
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
51443
|
+
* @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
|
|
51267
51444
|
*/
|
|
51268
51445
|
this.writeMemory = async (dto) => {
|
|
51269
51446
|
if (!this.enable.hasValue()) {
|
|
@@ -51287,6 +51464,7 @@ class MemoryAdapter {
|
|
|
51287
51464
|
* @param dto.signalId - Signal identifier
|
|
51288
51465
|
* @param dto.bucketName - Bucket name
|
|
51289
51466
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
51467
|
+
* @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
|
|
51290
51468
|
* @returns Matching entries sorted by relevance score
|
|
51291
51469
|
*/
|
|
51292
51470
|
this.searchMemory = async (dto) => {
|
|
@@ -51310,6 +51488,7 @@ class MemoryAdapter {
|
|
|
51310
51488
|
* @param dto.signalId - Signal identifier
|
|
51311
51489
|
* @param dto.bucketName - Bucket name
|
|
51312
51490
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
51491
|
+
* @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
|
|
51313
51492
|
* @returns Array of all stored entries
|
|
51314
51493
|
*/
|
|
51315
51494
|
this.listMemory = async (dto) => {
|
|
@@ -51333,6 +51512,7 @@ class MemoryAdapter {
|
|
|
51333
51512
|
* @param dto.signalId - Signal identifier
|
|
51334
51513
|
* @param dto.bucketName - Bucket name
|
|
51335
51514
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
51515
|
+
* @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
|
|
51336
51516
|
*/
|
|
51337
51517
|
this.removeMemory = async (dto) => {
|
|
51338
51518
|
if (!this.enable.hasValue()) {
|
|
@@ -51356,6 +51536,7 @@ class MemoryAdapter {
|
|
|
51356
51536
|
* @param dto.signalId - Signal identifier
|
|
51357
51537
|
* @param dto.bucketName - Bucket name
|
|
51358
51538
|
* @param dto.backtest - Flag indicating if the context is backtest or live
|
|
51539
|
+
* @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
|
|
51359
51540
|
* @returns Entry value
|
|
51360
51541
|
* @throws Error if entry not found
|
|
51361
51542
|
*/
|
|
@@ -51428,7 +51609,7 @@ async function writeMemory(dto) {
|
|
|
51428
51609
|
if (!MethodContextService.hasContext()) {
|
|
51429
51610
|
throw new Error("writeMemory requires a method context");
|
|
51430
51611
|
}
|
|
51431
|
-
const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
|
|
51612
|
+
const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
|
|
51432
51613
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
51433
51614
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
51434
51615
|
let signal;
|
|
@@ -51440,6 +51621,7 @@ async function writeMemory(dto) {
|
|
|
51440
51621
|
bucketName,
|
|
51441
51622
|
description,
|
|
51442
51623
|
backtest: isBacktest,
|
|
51624
|
+
when,
|
|
51443
51625
|
});
|
|
51444
51626
|
return;
|
|
51445
51627
|
}
|
|
@@ -51451,6 +51633,7 @@ async function writeMemory(dto) {
|
|
|
51451
51633
|
bucketName,
|
|
51452
51634
|
description,
|
|
51453
51635
|
backtest: isBacktest,
|
|
51636
|
+
when,
|
|
51454
51637
|
});
|
|
51455
51638
|
return;
|
|
51456
51639
|
}
|
|
@@ -51486,7 +51669,7 @@ async function readMemory(dto) {
|
|
|
51486
51669
|
if (!MethodContextService.hasContext()) {
|
|
51487
51670
|
throw new Error("readMemory requires a method context");
|
|
51488
51671
|
}
|
|
51489
|
-
const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
|
|
51672
|
+
const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
|
|
51490
51673
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
51491
51674
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
51492
51675
|
let signal;
|
|
@@ -51496,6 +51679,7 @@ async function readMemory(dto) {
|
|
|
51496
51679
|
signalId: signal.id,
|
|
51497
51680
|
bucketName,
|
|
51498
51681
|
backtest: isBacktest,
|
|
51682
|
+
when,
|
|
51499
51683
|
});
|
|
51500
51684
|
}
|
|
51501
51685
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -51504,6 +51688,7 @@ async function readMemory(dto) {
|
|
|
51504
51688
|
signalId: signal.id,
|
|
51505
51689
|
bucketName,
|
|
51506
51690
|
backtest: isBacktest,
|
|
51691
|
+
when,
|
|
51507
51692
|
});
|
|
51508
51693
|
}
|
|
51509
51694
|
throw new Error(`readMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
|
|
@@ -51538,7 +51723,7 @@ async function searchMemory(dto) {
|
|
|
51538
51723
|
if (!MethodContextService.hasContext()) {
|
|
51539
51724
|
throw new Error("searchMemory requires a method context");
|
|
51540
51725
|
}
|
|
51541
|
-
const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
|
|
51726
|
+
const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
|
|
51542
51727
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
51543
51728
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
51544
51729
|
let signal;
|
|
@@ -51548,6 +51733,7 @@ async function searchMemory(dto) {
|
|
|
51548
51733
|
signalId: signal.id,
|
|
51549
51734
|
bucketName,
|
|
51550
51735
|
backtest: isBacktest,
|
|
51736
|
+
when,
|
|
51551
51737
|
});
|
|
51552
51738
|
}
|
|
51553
51739
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -51556,6 +51742,7 @@ async function searchMemory(dto) {
|
|
|
51556
51742
|
signalId: signal.id,
|
|
51557
51743
|
bucketName,
|
|
51558
51744
|
backtest: isBacktest,
|
|
51745
|
+
when,
|
|
51559
51746
|
});
|
|
51560
51747
|
}
|
|
51561
51748
|
throw new Error(`searchMemory requires a pending or scheduled signal for symbol=${symbol} query=${query}`);
|
|
@@ -51588,7 +51775,7 @@ async function listMemory(dto) {
|
|
|
51588
51775
|
if (!MethodContextService.hasContext()) {
|
|
51589
51776
|
throw new Error("listMemory requires a method context");
|
|
51590
51777
|
}
|
|
51591
|
-
const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
|
|
51778
|
+
const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
|
|
51592
51779
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
51593
51780
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
51594
51781
|
let signal;
|
|
@@ -51597,6 +51784,7 @@ async function listMemory(dto) {
|
|
|
51597
51784
|
signalId: signal.id,
|
|
51598
51785
|
bucketName,
|
|
51599
51786
|
backtest: isBacktest,
|
|
51787
|
+
when,
|
|
51600
51788
|
});
|
|
51601
51789
|
}
|
|
51602
51790
|
if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
|
|
@@ -51604,6 +51792,7 @@ async function listMemory(dto) {
|
|
|
51604
51792
|
signalId: signal.id,
|
|
51605
51793
|
bucketName,
|
|
51606
51794
|
backtest: isBacktest,
|
|
51795
|
+
when,
|
|
51607
51796
|
});
|
|
51608
51797
|
}
|
|
51609
51798
|
throw new Error(`listMemory requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
|
|
@@ -51638,7 +51827,7 @@ async function removeMemory(dto) {
|
|
|
51638
51827
|
if (!MethodContextService.hasContext()) {
|
|
51639
51828
|
throw new Error("removeMemory requires a method context");
|
|
51640
51829
|
}
|
|
51641
|
-
const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
|
|
51830
|
+
const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
|
|
51642
51831
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
51643
51832
|
const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
51644
51833
|
let signal;
|
|
@@ -51648,6 +51837,7 @@ async function removeMemory(dto) {
|
|
|
51648
51837
|
signalId: signal.id,
|
|
51649
51838
|
bucketName,
|
|
51650
51839
|
backtest: isBacktest,
|
|
51840
|
+
when,
|
|
51651
51841
|
});
|
|
51652
51842
|
return;
|
|
51653
51843
|
}
|
|
@@ -51657,6 +51847,7 @@ async function removeMemory(dto) {
|
|
|
51657
51847
|
signalId: signal.id,
|
|
51658
51848
|
bucketName,
|
|
51659
51849
|
backtest: isBacktest,
|
|
51850
|
+
when,
|
|
51660
51851
|
});
|
|
51661
51852
|
return;
|
|
51662
51853
|
}
|
|
@@ -51922,6 +52113,9 @@ class DumpMemoryInstance {
|
|
|
51922
52113
|
value: { messages },
|
|
51923
52114
|
description,
|
|
51924
52115
|
backtest: this.backtest,
|
|
52116
|
+
// when=0: dumps are UI artifacts (agent transcripts, markdown reports);
|
|
52117
|
+
// they must stay visible regardless of the reader's logical time.
|
|
52118
|
+
when: new Date(0)
|
|
51925
52119
|
});
|
|
51926
52120
|
}
|
|
51927
52121
|
/**
|
|
@@ -51944,6 +52138,7 @@ class DumpMemoryInstance {
|
|
|
51944
52138
|
value: record,
|
|
51945
52139
|
description,
|
|
51946
52140
|
backtest: this.backtest,
|
|
52141
|
+
when: new Date(0)
|
|
51947
52142
|
});
|
|
51948
52143
|
}
|
|
51949
52144
|
/**
|
|
@@ -51967,6 +52162,7 @@ class DumpMemoryInstance {
|
|
|
51967
52162
|
value: { rows },
|
|
51968
52163
|
description,
|
|
51969
52164
|
backtest: this.backtest,
|
|
52165
|
+
when: new Date(0)
|
|
51970
52166
|
});
|
|
51971
52167
|
}
|
|
51972
52168
|
/**
|
|
@@ -51989,6 +52185,7 @@ class DumpMemoryInstance {
|
|
|
51989
52185
|
value: { content },
|
|
51990
52186
|
description,
|
|
51991
52187
|
backtest: this.backtest,
|
|
52188
|
+
when: new Date(0)
|
|
51992
52189
|
});
|
|
51993
52190
|
}
|
|
51994
52191
|
/**
|
|
@@ -52011,6 +52208,7 @@ class DumpMemoryInstance {
|
|
|
52011
52208
|
value: { content },
|
|
52012
52209
|
description,
|
|
52013
52210
|
backtest: this.backtest,
|
|
52211
|
+
when: new Date(0)
|
|
52014
52212
|
});
|
|
52015
52213
|
}
|
|
52016
52214
|
/**
|
|
@@ -52034,6 +52232,7 @@ class DumpMemoryInstance {
|
|
|
52034
52232
|
value: json,
|
|
52035
52233
|
description,
|
|
52036
52234
|
backtest: this.backtest,
|
|
52235
|
+
when: new Date(0)
|
|
52037
52236
|
});
|
|
52038
52237
|
}
|
|
52039
52238
|
/** Releases resources held by this instance. */
|
|
@@ -60995,8 +61194,8 @@ const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
|
|
|
60995
61194
|
const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
|
|
60996
61195
|
const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
|
|
60997
61196
|
const CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "CacheFileInstance.hasValue";
|
|
60998
|
-
const MS_PER_MINUTE$
|
|
60999
|
-
const INTERVAL_MINUTES$
|
|
61197
|
+
const MS_PER_MINUTE$2 = 60000;
|
|
61198
|
+
const INTERVAL_MINUTES$2 = {
|
|
61000
61199
|
"1m": 1,
|
|
61001
61200
|
"3m": 3,
|
|
61002
61201
|
"5m": 5,
|
|
@@ -61030,11 +61229,11 @@ const INTERVAL_MINUTES$1 = {
|
|
|
61030
61229
|
* ```
|
|
61031
61230
|
*/
|
|
61032
61231
|
const align$1 = (timestamp, interval) => {
|
|
61033
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
61232
|
+
const intervalMinutes = INTERVAL_MINUTES$2[interval];
|
|
61034
61233
|
if (!intervalMinutes) {
|
|
61035
61234
|
throw new Error(`align: unknown interval=${interval}`);
|
|
61036
61235
|
}
|
|
61037
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
61236
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
|
|
61038
61237
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
61039
61238
|
};
|
|
61040
61239
|
/**
|
|
@@ -61123,7 +61322,7 @@ class CacheFnInstance {
|
|
|
61123
61322
|
*/
|
|
61124
61323
|
this.run = (...args) => {
|
|
61125
61324
|
backtest.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
|
|
61126
|
-
const step = INTERVAL_MINUTES$
|
|
61325
|
+
const step = INTERVAL_MINUTES$2[this.interval];
|
|
61127
61326
|
{
|
|
61128
61327
|
if (!MethodContextService.hasContext()) {
|
|
61129
61328
|
throw new Error("CacheFnInstance run requires method context");
|
|
@@ -61318,7 +61517,7 @@ class CacheFileInstance {
|
|
|
61318
61517
|
*/
|
|
61319
61518
|
this.run = async (...args) => {
|
|
61320
61519
|
backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
61321
|
-
const step = INTERVAL_MINUTES$
|
|
61520
|
+
const step = INTERVAL_MINUTES$2[this.interval];
|
|
61322
61521
|
{
|
|
61323
61522
|
if (!MethodContextService.hasContext()) {
|
|
61324
61523
|
throw new Error("CacheFileInstance run requires method context");
|
|
@@ -61340,7 +61539,7 @@ class CacheFileInstance {
|
|
|
61340
61539
|
return cached.data;
|
|
61341
61540
|
}
|
|
61342
61541
|
const result = await this.fn.call(null, ...args);
|
|
61343
|
-
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
61542
|
+
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey, when);
|
|
61344
61543
|
return result;
|
|
61345
61544
|
};
|
|
61346
61545
|
/**
|
|
@@ -61629,8 +61828,8 @@ const INTERVAL_METHOD_NAME_FILE_HAS_VALUE = "IntervalUtils.file.hasValue";
|
|
|
61629
61828
|
const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
|
|
61630
61829
|
const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
|
|
61631
61830
|
const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
|
|
61632
|
-
const MS_PER_MINUTE = 60000;
|
|
61633
|
-
const INTERVAL_MINUTES = {
|
|
61831
|
+
const MS_PER_MINUTE$1 = 60000;
|
|
61832
|
+
const INTERVAL_MINUTES$1 = {
|
|
61634
61833
|
"1m": 1,
|
|
61635
61834
|
"3m": 3,
|
|
61636
61835
|
"5m": 5,
|
|
@@ -61660,11 +61859,11 @@ const INTERVAL_MINUTES = {
|
|
|
61660
61859
|
* ```
|
|
61661
61860
|
*/
|
|
61662
61861
|
const align = (timestamp, interval) => {
|
|
61663
|
-
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
61862
|
+
const intervalMinutes = INTERVAL_MINUTES$1[interval];
|
|
61664
61863
|
if (!intervalMinutes) {
|
|
61665
61864
|
throw new Error(`align: unknown interval=${interval}`);
|
|
61666
61865
|
}
|
|
61667
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
61866
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
|
|
61668
61867
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
61669
61868
|
};
|
|
61670
61869
|
/**
|
|
@@ -61739,7 +61938,7 @@ class IntervalFnInstance {
|
|
|
61739
61938
|
*/
|
|
61740
61939
|
this.run = (...args) => {
|
|
61741
61940
|
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
|
|
61742
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
61941
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
61743
61942
|
{
|
|
61744
61943
|
if (!MethodContextService.hasContext()) {
|
|
61745
61944
|
throw new Error("IntervalFnInstance run requires method context");
|
|
@@ -61906,7 +62105,7 @@ class IntervalFileInstance {
|
|
|
61906
62105
|
*/
|
|
61907
62106
|
this.run = async (...args) => {
|
|
61908
62107
|
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
61909
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
62108
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
61910
62109
|
{
|
|
61911
62110
|
if (!MethodContextService.hasContext()) {
|
|
61912
62111
|
throw new Error("IntervalFileInstance run requires method context");
|
|
@@ -61929,7 +62128,7 @@ class IntervalFileInstance {
|
|
|
61929
62128
|
}
|
|
61930
62129
|
const result = await this.fn.call(null, ...args);
|
|
61931
62130
|
if (result !== null) {
|
|
61932
|
-
await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
62131
|
+
await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, when, removed: false }, bucket, entityKey, when);
|
|
61933
62132
|
}
|
|
61934
62133
|
return result;
|
|
61935
62134
|
};
|
|
@@ -63053,6 +63252,31 @@ class ActionBase {
|
|
|
63053
63252
|
// @ts-ignore
|
|
63054
63253
|
ActionBase = functoolsKit.makeExtendable(ActionBase);
|
|
63055
63254
|
|
|
63255
|
+
const MS_PER_MINUTE = 60000;
|
|
63256
|
+
const INTERVAL_MINUTES = {
|
|
63257
|
+
"1m": 1,
|
|
63258
|
+
"3m": 3,
|
|
63259
|
+
"5m": 5,
|
|
63260
|
+
"15m": 15,
|
|
63261
|
+
"30m": 30,
|
|
63262
|
+
"1h": 60,
|
|
63263
|
+
"2h": 120,
|
|
63264
|
+
"4h": 240,
|
|
63265
|
+
"6h": 360,
|
|
63266
|
+
"8h": 480,
|
|
63267
|
+
"1d": 1440,
|
|
63268
|
+
};
|
|
63269
|
+
/**
|
|
63270
|
+
* Returns the step in milliseconds for a given candle interval.
|
|
63271
|
+
* For example, for "15m" interval, it returns 900000 (15 * 60 * 1000).
|
|
63272
|
+
*
|
|
63273
|
+
* @param interval - Candle interval (e.g., "1m", "15m", "1h")
|
|
63274
|
+
* @returns Step in milliseconds corresponding to the interval
|
|
63275
|
+
*/
|
|
63276
|
+
const intervalStepMs = (interval) => {
|
|
63277
|
+
return INTERVAL_MINUTES[interval] * MS_PER_MINUTE;
|
|
63278
|
+
};
|
|
63279
|
+
|
|
63056
63280
|
/**
|
|
63057
63281
|
* Rounds a price to the appropriate precision based on the tick size.
|
|
63058
63282
|
*
|
|
@@ -63511,6 +63735,7 @@ exports.getWalkerSchema = getWalkerSchema;
|
|
|
63511
63735
|
exports.hasNoPendingSignal = hasNoPendingSignal;
|
|
63512
63736
|
exports.hasNoScheduledSignal = hasNoScheduledSignal;
|
|
63513
63737
|
exports.hasTradeContext = hasTradeContext;
|
|
63738
|
+
exports.intervalStepMs = intervalStepMs;
|
|
63514
63739
|
exports.investedCostToPercent = investedCostToPercent;
|
|
63515
63740
|
exports.lib = backtest;
|
|
63516
63741
|
exports.listExchangeSchema = listExchangeSchema;
|