backtest-kit 6.8.1 → 6.9.0
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 +854 -188
- package/build/index.mjs +852 -189
- package/package.json +1 -1
- package/types.d.ts +288 -9
package/build/index.mjs
CHANGED
|
@@ -888,7 +888,7 @@ const LOGGER_SERVICE$7 = new LoggerService();
|
|
|
888
888
|
/** Symbol key for the singleshot waitForInit function on PersistBase instances. */
|
|
889
889
|
const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
|
|
890
890
|
// Calculate step in milliseconds for candle close time validation
|
|
891
|
-
const INTERVAL_MINUTES$
|
|
891
|
+
const INTERVAL_MINUTES$9 = {
|
|
892
892
|
"1m": 1,
|
|
893
893
|
"3m": 3,
|
|
894
894
|
"5m": 5,
|
|
@@ -902,7 +902,7 @@ const INTERVAL_MINUTES$8 = {
|
|
|
902
902
|
"1d": 1440,
|
|
903
903
|
"1w": 10080,
|
|
904
904
|
};
|
|
905
|
-
const MS_PER_MINUTE$
|
|
905
|
+
const MS_PER_MINUTE$7 = 60000;
|
|
906
906
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
|
|
907
907
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
|
|
908
908
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistSignalUtils.writeSignalData";
|
|
@@ -961,8 +961,18 @@ const PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA = "PersistMeasureUtils.readMea
|
|
|
961
961
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA = "PersistMeasureUtils.writeMeasureData";
|
|
962
962
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON = "PersistMeasureUtils.useJson";
|
|
963
963
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY = "PersistMeasureUtils.useDummy";
|
|
964
|
+
const PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA = "PersistMeasureUtils.removeMeasureData";
|
|
965
|
+
const PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA = "PersistMeasureUtils.listMeasureData";
|
|
964
966
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_CLEAR = "PersistMeasureUtils.clear";
|
|
965
967
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER = "PersistMeasureUtils.usePersistMeasureAdapter";
|
|
968
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA = "PersistIntervalUtils.readIntervalData";
|
|
969
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistIntervalUtils.writeIntervalData";
|
|
970
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON = "PersistIntervalUtils.useJson";
|
|
971
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY = "PersistIntervalUtils.useDummy";
|
|
972
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA = "PersistIntervalUtils.removeIntervalData";
|
|
973
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA = "PersistIntervalUtils.listIntervalData";
|
|
974
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR = "PersistIntervalUtils.clear";
|
|
975
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER = "PersistIntervalUtils.usePersistIntervalAdapter";
|
|
966
976
|
const PERSIST_CANDLE_UTILS_METHOD_NAME_CLEAR = "PersistCandleUtils.clear";
|
|
967
977
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_USE_PERSIST_MEMORY_ADAPTER = "PersistMemoryUtils.usePersistMemoryAdapter";
|
|
968
978
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA = "PersistMemoryUtils.readMemoryData";
|
|
@@ -1875,7 +1885,7 @@ class PersistCandleUtils {
|
|
|
1875
1885
|
const isInitial = !this.getCandlesStorage.has(key);
|
|
1876
1886
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1877
1887
|
await stateStorage.waitForInit(isInitial);
|
|
1878
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1888
|
+
const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
|
|
1879
1889
|
// Calculate expected timestamps and fetch each candle directly
|
|
1880
1890
|
const cachedCandles = [];
|
|
1881
1891
|
for (let i = 0; i < limit; i++) {
|
|
@@ -1931,7 +1941,7 @@ class PersistCandleUtils {
|
|
|
1931
1941
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1932
1942
|
await stateStorage.waitForInit(isInitial);
|
|
1933
1943
|
// Calculate step in milliseconds to determine candle close time
|
|
1934
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1944
|
+
const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
|
|
1935
1945
|
const now = Date.now();
|
|
1936
1946
|
// Write each candle as a separate file, skipping incomplete candles
|
|
1937
1947
|
for (const candle of candles) {
|
|
@@ -2357,7 +2367,8 @@ class PersistMeasureUtils {
|
|
|
2357
2367
|
const stateStorage = this.getMeasureStorage(bucket);
|
|
2358
2368
|
await stateStorage.waitForInit(isInitial);
|
|
2359
2369
|
if (await stateStorage.hasValue(key)) {
|
|
2360
|
-
|
|
2370
|
+
const data = await stateStorage.readValue(key);
|
|
2371
|
+
return data.removed ? null : data;
|
|
2361
2372
|
}
|
|
2362
2373
|
return null;
|
|
2363
2374
|
};
|
|
@@ -2379,6 +2390,27 @@ class PersistMeasureUtils {
|
|
|
2379
2390
|
await stateStorage.waitForInit(isInitial);
|
|
2380
2391
|
await stateStorage.writeValue(key, data);
|
|
2381
2392
|
};
|
|
2393
|
+
/**
|
|
2394
|
+
* Marks a cached entry as removed (soft delete — file is kept on disk).
|
|
2395
|
+
* After this call `readMeasureData` for the same key returns `null`.
|
|
2396
|
+
*
|
|
2397
|
+
* @param bucket - Storage bucket
|
|
2398
|
+
* @param key - Dynamic cache key within the bucket
|
|
2399
|
+
* @returns Promise that resolves when removal is complete
|
|
2400
|
+
*/
|
|
2401
|
+
this.removeMeasureData = async (bucket, key) => {
|
|
2402
|
+
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, {
|
|
2403
|
+
bucket,
|
|
2404
|
+
key,
|
|
2405
|
+
});
|
|
2406
|
+
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2407
|
+
const stateStorage = this.getMeasureStorage(bucket);
|
|
2408
|
+
await stateStorage.waitForInit(isInitial);
|
|
2409
|
+
const data = await stateStorage.readValue(key);
|
|
2410
|
+
if (data) {
|
|
2411
|
+
await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
2412
|
+
}
|
|
2413
|
+
};
|
|
2382
2414
|
}
|
|
2383
2415
|
/**
|
|
2384
2416
|
* Registers a custom persistence adapter.
|
|
@@ -2389,6 +2421,27 @@ class PersistMeasureUtils {
|
|
|
2389
2421
|
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
|
|
2390
2422
|
this.PersistMeasureFactory = Ctor;
|
|
2391
2423
|
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Async generator yielding all non-removed entity keys for a given bucket.
|
|
2426
|
+
* Used by `CacheFileInstance.clear()` to iterate and soft-delete all entries.
|
|
2427
|
+
*
|
|
2428
|
+
* @param bucket - Storage bucket
|
|
2429
|
+
* @returns AsyncGenerator yielding entity keys
|
|
2430
|
+
*/
|
|
2431
|
+
async *listMeasureData(bucket) {
|
|
2432
|
+
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA, { bucket });
|
|
2433
|
+
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2434
|
+
const stateStorage = this.getMeasureStorage(bucket);
|
|
2435
|
+
await stateStorage.waitForInit(isInitial);
|
|
2436
|
+
for await (const key of stateStorage.keys()) {
|
|
2437
|
+
const data = await stateStorage.readValue(String(key));
|
|
2438
|
+
if (data === null || data.removed) {
|
|
2439
|
+
continue;
|
|
2440
|
+
}
|
|
2441
|
+
yield String(key);
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
;
|
|
2392
2445
|
/**
|
|
2393
2446
|
* Clears the memoized storage cache.
|
|
2394
2447
|
* Call this when process.cwd() changes between strategy iterations
|
|
@@ -2418,6 +2471,140 @@ class PersistMeasureUtils {
|
|
|
2418
2471
|
* Used by Cache.file for persistent caching of external API responses.
|
|
2419
2472
|
*/
|
|
2420
2473
|
const PersistMeasureAdapter = new PersistMeasureUtils();
|
|
2474
|
+
/**
|
|
2475
|
+
* Persistence layer for Interval.file once-per-interval signal firing.
|
|
2476
|
+
*
|
|
2477
|
+
* Stores fired-interval markers under `./dump/data/interval/`.
|
|
2478
|
+
* A record's presence means the interval has already fired for that bucket+key;
|
|
2479
|
+
* absence means the function has not yet fired (or returned null last time).
|
|
2480
|
+
*/
|
|
2481
|
+
class PersistIntervalUtils {
|
|
2482
|
+
constructor() {
|
|
2483
|
+
this.PersistIntervalFactory = PersistBase;
|
|
2484
|
+
this.getIntervalStorage = memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistIntervalFactory, [
|
|
2485
|
+
bucket,
|
|
2486
|
+
`./dump/data/interval/`,
|
|
2487
|
+
]));
|
|
2488
|
+
/**
|
|
2489
|
+
* Reads interval data for a given bucket and key.
|
|
2490
|
+
*
|
|
2491
|
+
* @param bucket - Storage bucket (instance name + interval + index)
|
|
2492
|
+
* @param key - Entity key within the bucket (symbol + aligned timestamp)
|
|
2493
|
+
* @returns Promise resolving to stored value or null if not found
|
|
2494
|
+
*/
|
|
2495
|
+
this.readIntervalData = async (bucket, key) => {
|
|
2496
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, {
|
|
2497
|
+
bucket,
|
|
2498
|
+
key,
|
|
2499
|
+
});
|
|
2500
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2501
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2502
|
+
await stateStorage.waitForInit(isInitial);
|
|
2503
|
+
if (await stateStorage.hasValue(key)) {
|
|
2504
|
+
const data = await stateStorage.readValue(key);
|
|
2505
|
+
return data.removed ? null : data;
|
|
2506
|
+
}
|
|
2507
|
+
return null;
|
|
2508
|
+
};
|
|
2509
|
+
/**
|
|
2510
|
+
* Writes interval data to disk.
|
|
2511
|
+
*
|
|
2512
|
+
* @param data - Data to store
|
|
2513
|
+
* @param bucket - Storage bucket
|
|
2514
|
+
* @param key - Entity key within the bucket
|
|
2515
|
+
* @returns Promise that resolves when write is complete
|
|
2516
|
+
*/
|
|
2517
|
+
this.writeIntervalData = async (data, bucket, key) => {
|
|
2518
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, {
|
|
2519
|
+
bucket,
|
|
2520
|
+
key,
|
|
2521
|
+
});
|
|
2522
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2523
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2524
|
+
await stateStorage.waitForInit(isInitial);
|
|
2525
|
+
await stateStorage.writeValue(key, data);
|
|
2526
|
+
};
|
|
2527
|
+
/**
|
|
2528
|
+
* Marks an interval entry as removed (soft delete — file is kept on disk).
|
|
2529
|
+
* After this call `readIntervalData` for the same key returns `null`,
|
|
2530
|
+
* so the function will fire again on the next `IntervalFileInstance.run` call.
|
|
2531
|
+
*
|
|
2532
|
+
* @param bucket - Storage bucket
|
|
2533
|
+
* @param key - Entity key within the bucket
|
|
2534
|
+
* @returns Promise that resolves when removal is complete
|
|
2535
|
+
*/
|
|
2536
|
+
this.removeIntervalData = async (bucket, key) => {
|
|
2537
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, {
|
|
2538
|
+
bucket,
|
|
2539
|
+
key,
|
|
2540
|
+
});
|
|
2541
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2542
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2543
|
+
await stateStorage.waitForInit(isInitial);
|
|
2544
|
+
const data = await stateStorage.readValue(key);
|
|
2545
|
+
if (data) {
|
|
2546
|
+
await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
2547
|
+
}
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
/**
|
|
2551
|
+
* Registers a custom persistence adapter.
|
|
2552
|
+
*
|
|
2553
|
+
* @param Ctor - Custom PersistBase constructor
|
|
2554
|
+
*/
|
|
2555
|
+
usePersistIntervalAdapter(Ctor) {
|
|
2556
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER);
|
|
2557
|
+
this.PersistIntervalFactory = Ctor;
|
|
2558
|
+
}
|
|
2559
|
+
/**
|
|
2560
|
+
* Async generator yielding all non-removed entity keys for a given bucket.
|
|
2561
|
+
* Used by `IntervalFileInstance.clear()` to iterate and soft-delete all entries.
|
|
2562
|
+
*
|
|
2563
|
+
* @param bucket - Storage bucket
|
|
2564
|
+
* @returns AsyncGenerator yielding entity keys
|
|
2565
|
+
*/
|
|
2566
|
+
async *listIntervalData(bucket) {
|
|
2567
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA, { bucket });
|
|
2568
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2569
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2570
|
+
await stateStorage.waitForInit(isInitial);
|
|
2571
|
+
for await (const key of stateStorage.keys()) {
|
|
2572
|
+
const data = await stateStorage.readValue(String(key));
|
|
2573
|
+
if (data === null || data.removed) {
|
|
2574
|
+
continue;
|
|
2575
|
+
}
|
|
2576
|
+
yield String(key);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
;
|
|
2580
|
+
/**
|
|
2581
|
+
* Clears the memoized storage cache.
|
|
2582
|
+
* Call this when process.cwd() changes between strategy iterations.
|
|
2583
|
+
*/
|
|
2584
|
+
clear() {
|
|
2585
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR);
|
|
2586
|
+
this.getIntervalStorage.clear();
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* Switches to the default JSON persist adapter.
|
|
2590
|
+
*/
|
|
2591
|
+
useJson() {
|
|
2592
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON);
|
|
2593
|
+
this.usePersistIntervalAdapter(PersistBase);
|
|
2594
|
+
}
|
|
2595
|
+
/**
|
|
2596
|
+
* Switches to a dummy persist adapter that discards all writes.
|
|
2597
|
+
*/
|
|
2598
|
+
useDummy() {
|
|
2599
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2600
|
+
this.usePersistIntervalAdapter(PersistDummy);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
/**
|
|
2604
|
+
* Global singleton instance of PersistIntervalUtils.
|
|
2605
|
+
* Used by Interval.file for persistent once-per-interval signal firing.
|
|
2606
|
+
*/
|
|
2607
|
+
const PersistIntervalAdapter = new PersistIntervalUtils();
|
|
2421
2608
|
/**
|
|
2422
2609
|
* Utility class for managing memory entry persistence.
|
|
2423
2610
|
*
|
|
@@ -2765,8 +2952,8 @@ class CandleUtils {
|
|
|
2765
2952
|
}
|
|
2766
2953
|
const Candle = new CandleUtils();
|
|
2767
2954
|
|
|
2768
|
-
const MS_PER_MINUTE$
|
|
2769
|
-
const INTERVAL_MINUTES$
|
|
2955
|
+
const MS_PER_MINUTE$6 = 60000;
|
|
2956
|
+
const INTERVAL_MINUTES$8 = {
|
|
2770
2957
|
"1m": 1,
|
|
2771
2958
|
"3m": 3,
|
|
2772
2959
|
"5m": 5,
|
|
@@ -2798,7 +2985,7 @@ const INTERVAL_MINUTES$7 = {
|
|
|
2798
2985
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
2799
2986
|
*/
|
|
2800
2987
|
const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
|
|
2801
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
2988
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$6;
|
|
2802
2989
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
2803
2990
|
};
|
|
2804
2991
|
/**
|
|
@@ -2952,9 +3139,9 @@ const WRITE_CANDLES_CACHE_FN$1 = trycatch(queued(async (candles, dto, self) => {
|
|
|
2952
3139
|
* @returns Promise resolving to array of candle data
|
|
2953
3140
|
*/
|
|
2954
3141
|
const GET_CANDLES_FN$1 = async (dto, since, self) => {
|
|
2955
|
-
const step = INTERVAL_MINUTES$
|
|
3142
|
+
const step = INTERVAL_MINUTES$8[dto.interval];
|
|
2956
3143
|
const sinceTimestamp = since.getTime();
|
|
2957
|
-
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$
|
|
3144
|
+
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$6;
|
|
2958
3145
|
await Candle.acquireLock(`ClientExchange GET_CANDLES_FN symbol=${dto.symbol} interval=${dto.interval} limit=${dto.limit}`);
|
|
2959
3146
|
try {
|
|
2960
3147
|
// Try to read from cache first
|
|
@@ -3068,11 +3255,11 @@ class ClientExchange {
|
|
|
3068
3255
|
interval,
|
|
3069
3256
|
limit,
|
|
3070
3257
|
});
|
|
3071
|
-
const step = INTERVAL_MINUTES$
|
|
3258
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3072
3259
|
if (!step) {
|
|
3073
3260
|
throw new Error(`ClientExchange unknown interval=${interval}`);
|
|
3074
3261
|
}
|
|
3075
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3262
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3076
3263
|
// Align when down to interval boundary
|
|
3077
3264
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3078
3265
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
@@ -3149,11 +3336,11 @@ class ClientExchange {
|
|
|
3149
3336
|
if (!this.params.execution.context.backtest) {
|
|
3150
3337
|
throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
|
|
3151
3338
|
}
|
|
3152
|
-
const step = INTERVAL_MINUTES$
|
|
3339
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3153
3340
|
if (!step) {
|
|
3154
3341
|
throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
|
|
3155
3342
|
}
|
|
3156
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3343
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3157
3344
|
const now = Date.now();
|
|
3158
3345
|
// Align when down to interval boundary
|
|
3159
3346
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
@@ -3317,11 +3504,11 @@ class ClientExchange {
|
|
|
3317
3504
|
sDate,
|
|
3318
3505
|
eDate,
|
|
3319
3506
|
});
|
|
3320
|
-
const step = INTERVAL_MINUTES$
|
|
3507
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3321
3508
|
if (!step) {
|
|
3322
3509
|
throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
|
|
3323
3510
|
}
|
|
3324
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3511
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3325
3512
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3326
3513
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
3327
3514
|
let sinceTimestamp;
|
|
@@ -3450,7 +3637,7 @@ class ClientExchange {
|
|
|
3450
3637
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
3451
3638
|
const to = new Date(alignedTo);
|
|
3452
3639
|
const from = new Date(alignedTo -
|
|
3453
|
-
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
3640
|
+
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$6);
|
|
3454
3641
|
return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
|
|
3455
3642
|
}
|
|
3456
3643
|
/**
|
|
@@ -3479,7 +3666,7 @@ class ClientExchange {
|
|
|
3479
3666
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3480
3667
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
3481
3668
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, 1);
|
|
3482
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
3669
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$6 - MS_PER_MINUTE$6;
|
|
3483
3670
|
// No limit: fetch a single window and return as-is
|
|
3484
3671
|
if (limit === undefined) {
|
|
3485
3672
|
const to = new Date(alignedTo);
|
|
@@ -4458,7 +4645,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
|
|
|
4458
4645
|
}
|
|
4459
4646
|
};
|
|
4460
4647
|
|
|
4461
|
-
const INTERVAL_MINUTES$
|
|
4648
|
+
const INTERVAL_MINUTES$7 = {
|
|
4462
4649
|
"1m": 1,
|
|
4463
4650
|
"3m": 3,
|
|
4464
4651
|
"5m": 5,
|
|
@@ -4842,7 +5029,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
4842
5029
|
}
|
|
4843
5030
|
const currentTime = self.params.execution.context.when.getTime();
|
|
4844
5031
|
{
|
|
4845
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
5032
|
+
const intervalMinutes = INTERVAL_MINUTES$7[self.params.interval];
|
|
4846
5033
|
const intervalMs = intervalMinutes * 60 * 1000;
|
|
4847
5034
|
const alignedTime = Math.floor(currentTime / intervalMs) * intervalMs;
|
|
4848
5035
|
// Проверяем что наступил новый интервал (по aligned timestamp)
|
|
@@ -9866,7 +10053,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
9866
10053
|
* @param backtest - Whether running in backtest mode
|
|
9867
10054
|
* @returns Unique string key for memoization
|
|
9868
10055
|
*/
|
|
9869
|
-
const CREATE_KEY_FN$
|
|
10056
|
+
const CREATE_KEY_FN$t = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
9870
10057
|
const parts = [symbol, strategyName, exchangeName];
|
|
9871
10058
|
if (frameName)
|
|
9872
10059
|
parts.push(frameName);
|
|
@@ -10133,7 +10320,7 @@ class StrategyConnectionService {
|
|
|
10133
10320
|
* @param backtest - Whether running in backtest mode
|
|
10134
10321
|
* @returns Configured ClientStrategy instance
|
|
10135
10322
|
*/
|
|
10136
|
-
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
10323
|
+
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10137
10324
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10138
10325
|
return new ClientStrategy({
|
|
10139
10326
|
symbol,
|
|
@@ -10928,7 +11115,7 @@ class StrategyConnectionService {
|
|
|
10928
11115
|
}
|
|
10929
11116
|
return;
|
|
10930
11117
|
}
|
|
10931
|
-
const key = CREATE_KEY_FN$
|
|
11118
|
+
const key = CREATE_KEY_FN$t(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
10932
11119
|
if (!this.getStrategy.has(key)) {
|
|
10933
11120
|
return;
|
|
10934
11121
|
}
|
|
@@ -11330,7 +11517,7 @@ class StrategyConnectionService {
|
|
|
11330
11517
|
* Maps FrameInterval to minutes for timestamp calculation.
|
|
11331
11518
|
* Used to generate timeframe arrays with proper spacing.
|
|
11332
11519
|
*/
|
|
11333
|
-
const INTERVAL_MINUTES$
|
|
11520
|
+
const INTERVAL_MINUTES$6 = {
|
|
11334
11521
|
"1m": 1,
|
|
11335
11522
|
"3m": 3,
|
|
11336
11523
|
"5m": 5,
|
|
@@ -11386,7 +11573,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
11386
11573
|
symbol,
|
|
11387
11574
|
});
|
|
11388
11575
|
const { interval, startDate, endDate } = self.params;
|
|
11389
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
11576
|
+
const intervalMinutes = INTERVAL_MINUTES$6[interval];
|
|
11390
11577
|
if (!intervalMinutes) {
|
|
11391
11578
|
throw new Error(`ClientFrame unknown interval: ${interval}`);
|
|
11392
11579
|
}
|
|
@@ -11751,8 +11938,8 @@ const get = (object, path) => {
|
|
|
11751
11938
|
return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
|
|
11752
11939
|
};
|
|
11753
11940
|
|
|
11754
|
-
const MS_PER_MINUTE$
|
|
11755
|
-
const INTERVAL_MINUTES$
|
|
11941
|
+
const MS_PER_MINUTE$5 = 60000;
|
|
11942
|
+
const INTERVAL_MINUTES$5 = {
|
|
11756
11943
|
"1m": 1,
|
|
11757
11944
|
"3m": 3,
|
|
11758
11945
|
"5m": 5,
|
|
@@ -11784,11 +11971,11 @@ const INTERVAL_MINUTES$4 = {
|
|
|
11784
11971
|
* @returns New Date aligned down to interval boundary
|
|
11785
11972
|
*/
|
|
11786
11973
|
const alignToInterval = (date, interval) => {
|
|
11787
|
-
const minutes = INTERVAL_MINUTES$
|
|
11974
|
+
const minutes = INTERVAL_MINUTES$5[interval];
|
|
11788
11975
|
if (minutes === undefined) {
|
|
11789
11976
|
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
11790
11977
|
}
|
|
11791
|
-
const intervalMs = minutes * MS_PER_MINUTE$
|
|
11978
|
+
const intervalMs = minutes * MS_PER_MINUTE$5;
|
|
11792
11979
|
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
11793
11980
|
};
|
|
11794
11981
|
|
|
@@ -12097,7 +12284,7 @@ class ClientRisk {
|
|
|
12097
12284
|
* @param backtest - Whether running in backtest mode
|
|
12098
12285
|
* @returns Unique string key for memoization
|
|
12099
12286
|
*/
|
|
12100
|
-
const CREATE_KEY_FN$
|
|
12287
|
+
const CREATE_KEY_FN$s = (riskName, exchangeName, frameName, backtest) => {
|
|
12101
12288
|
const parts = [riskName, exchangeName];
|
|
12102
12289
|
if (frameName)
|
|
12103
12290
|
parts.push(frameName);
|
|
@@ -12197,7 +12384,7 @@ class RiskConnectionService {
|
|
|
12197
12384
|
* @param backtest - True if backtest mode, false if live mode
|
|
12198
12385
|
* @returns Configured ClientRisk instance
|
|
12199
12386
|
*/
|
|
12200
|
-
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12387
|
+
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
12201
12388
|
const schema = this.riskSchemaService.get(riskName);
|
|
12202
12389
|
return new ClientRisk({
|
|
12203
12390
|
...schema,
|
|
@@ -12266,7 +12453,7 @@ class RiskConnectionService {
|
|
|
12266
12453
|
payload,
|
|
12267
12454
|
});
|
|
12268
12455
|
if (payload) {
|
|
12269
|
-
const key = CREATE_KEY_FN$
|
|
12456
|
+
const key = CREATE_KEY_FN$s(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
12270
12457
|
this.getRisk.clear(key);
|
|
12271
12458
|
}
|
|
12272
12459
|
else {
|
|
@@ -13310,7 +13497,7 @@ class ClientAction {
|
|
|
13310
13497
|
* @param backtest - Whether running in backtest mode
|
|
13311
13498
|
* @returns Unique string key for memoization
|
|
13312
13499
|
*/
|
|
13313
|
-
const CREATE_KEY_FN$
|
|
13500
|
+
const CREATE_KEY_FN$r = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13314
13501
|
const parts = [actionName, strategyName, exchangeName];
|
|
13315
13502
|
if (frameName)
|
|
13316
13503
|
parts.push(frameName);
|
|
@@ -13362,7 +13549,7 @@ class ActionConnectionService {
|
|
|
13362
13549
|
* @param backtest - True if backtest mode, false if live mode
|
|
13363
13550
|
* @returns Configured ClientAction instance
|
|
13364
13551
|
*/
|
|
13365
|
-
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
13552
|
+
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13366
13553
|
const schema = this.actionSchemaService.get(actionName);
|
|
13367
13554
|
return new ClientAction({
|
|
13368
13555
|
...schema,
|
|
@@ -13573,7 +13760,7 @@ class ActionConnectionService {
|
|
|
13573
13760
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
13574
13761
|
return;
|
|
13575
13762
|
}
|
|
13576
|
-
const key = CREATE_KEY_FN$
|
|
13763
|
+
const key = CREATE_KEY_FN$r(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
13577
13764
|
if (!this.getAction.has(key)) {
|
|
13578
13765
|
return;
|
|
13579
13766
|
}
|
|
@@ -13591,7 +13778,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
13591
13778
|
* @param exchangeName - Exchange name
|
|
13592
13779
|
* @returns Unique string key for memoization
|
|
13593
13780
|
*/
|
|
13594
|
-
const CREATE_KEY_FN$
|
|
13781
|
+
const CREATE_KEY_FN$q = (exchangeName) => {
|
|
13595
13782
|
return exchangeName;
|
|
13596
13783
|
};
|
|
13597
13784
|
/**
|
|
@@ -13615,7 +13802,7 @@ class ExchangeCoreService {
|
|
|
13615
13802
|
* @param exchangeName - Name of the exchange to validate
|
|
13616
13803
|
* @returns Promise that resolves when validation is complete
|
|
13617
13804
|
*/
|
|
13618
|
-
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
13805
|
+
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$q(exchangeName), async (exchangeName) => {
|
|
13619
13806
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
13620
13807
|
exchangeName,
|
|
13621
13808
|
});
|
|
@@ -13867,7 +14054,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
13867
14054
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13868
14055
|
* @returns Unique string key for memoization
|
|
13869
14056
|
*/
|
|
13870
|
-
const CREATE_KEY_FN$
|
|
14057
|
+
const CREATE_KEY_FN$p = (context) => {
|
|
13871
14058
|
const parts = [context.strategyName, context.exchangeName];
|
|
13872
14059
|
if (context.frameName)
|
|
13873
14060
|
parts.push(context.frameName);
|
|
@@ -13899,7 +14086,7 @@ class StrategyCoreService {
|
|
|
13899
14086
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13900
14087
|
* @returns Promise that resolves when validation is complete
|
|
13901
14088
|
*/
|
|
13902
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
14089
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
|
|
13903
14090
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
13904
14091
|
context,
|
|
13905
14092
|
});
|
|
@@ -15123,7 +15310,7 @@ class SizingGlobalService {
|
|
|
15123
15310
|
* @param context - Context with riskName, exchangeName, frameName
|
|
15124
15311
|
* @returns Unique string key for memoization
|
|
15125
15312
|
*/
|
|
15126
|
-
const CREATE_KEY_FN$
|
|
15313
|
+
const CREATE_KEY_FN$o = (context) => {
|
|
15127
15314
|
const parts = [context.riskName, context.exchangeName];
|
|
15128
15315
|
if (context.frameName)
|
|
15129
15316
|
parts.push(context.frameName);
|
|
@@ -15149,7 +15336,7 @@ class RiskGlobalService {
|
|
|
15149
15336
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
15150
15337
|
* @returns Promise that resolves when validation is complete
|
|
15151
15338
|
*/
|
|
15152
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
15339
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
|
|
15153
15340
|
this.loggerService.log("riskGlobalService validate", {
|
|
15154
15341
|
context,
|
|
15155
15342
|
});
|
|
@@ -15227,7 +15414,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
15227
15414
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15228
15415
|
* @returns Unique string key for memoization
|
|
15229
15416
|
*/
|
|
15230
|
-
const CREATE_KEY_FN$
|
|
15417
|
+
const CREATE_KEY_FN$n = (context) => {
|
|
15231
15418
|
const parts = [context.strategyName, context.exchangeName];
|
|
15232
15419
|
if (context.frameName)
|
|
15233
15420
|
parts.push(context.frameName);
|
|
@@ -15271,7 +15458,7 @@ class ActionCoreService {
|
|
|
15271
15458
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
15272
15459
|
* @returns Promise that resolves when all validations complete
|
|
15273
15460
|
*/
|
|
15274
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
15461
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
|
|
15275
15462
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
15276
15463
|
context,
|
|
15277
15464
|
});
|
|
@@ -16866,8 +17053,8 @@ class BacktestLogicPrivateService {
|
|
|
16866
17053
|
}
|
|
16867
17054
|
|
|
16868
17055
|
const EMITTER_CHECK_INTERVAL = 5000;
|
|
16869
|
-
const MS_PER_MINUTE$
|
|
16870
|
-
const INTERVAL_MINUTES$
|
|
17056
|
+
const MS_PER_MINUTE$4 = 60000;
|
|
17057
|
+
const INTERVAL_MINUTES$4 = {
|
|
16871
17058
|
"1m": 1,
|
|
16872
17059
|
"3m": 3,
|
|
16873
17060
|
"5m": 5,
|
|
@@ -16883,7 +17070,7 @@ const INTERVAL_MINUTES$3 = {
|
|
|
16883
17070
|
};
|
|
16884
17071
|
const createEmitter = memoize(([interval]) => `${interval}`, (interval) => {
|
|
16885
17072
|
const tickSubject = new Subject();
|
|
16886
|
-
const intervalMs = INTERVAL_MINUTES$
|
|
17073
|
+
const intervalMs = INTERVAL_MINUTES$4[interval] * MS_PER_MINUTE$4;
|
|
16887
17074
|
{
|
|
16888
17075
|
let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
16889
17076
|
Source.fromInterval(EMITTER_CHECK_INTERVAL)
|
|
@@ -20267,7 +20454,7 @@ const ReportWriter = new ReportWriterAdapter();
|
|
|
20267
20454
|
* @param backtest - Whether running in backtest mode
|
|
20268
20455
|
* @returns Unique string key for memoization
|
|
20269
20456
|
*/
|
|
20270
|
-
const CREATE_KEY_FN$
|
|
20457
|
+
const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20271
20458
|
const parts = [symbol, strategyName, exchangeName];
|
|
20272
20459
|
if (frameName)
|
|
20273
20460
|
parts.push(frameName);
|
|
@@ -20513,7 +20700,7 @@ class BacktestMarkdownService {
|
|
|
20513
20700
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20514
20701
|
* Each combination gets its own isolated storage instance.
|
|
20515
20702
|
*/
|
|
20516
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20703
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
|
|
20517
20704
|
/**
|
|
20518
20705
|
* Processes tick events and accumulates closed signals.
|
|
20519
20706
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -20670,7 +20857,7 @@ class BacktestMarkdownService {
|
|
|
20670
20857
|
payload,
|
|
20671
20858
|
});
|
|
20672
20859
|
if (payload) {
|
|
20673
|
-
const key = CREATE_KEY_FN$
|
|
20860
|
+
const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20674
20861
|
this.getStorage.clear(key);
|
|
20675
20862
|
}
|
|
20676
20863
|
else {
|
|
@@ -20732,7 +20919,7 @@ class BacktestMarkdownService {
|
|
|
20732
20919
|
* @param backtest - Whether running in backtest mode
|
|
20733
20920
|
* @returns Unique string key for memoization
|
|
20734
20921
|
*/
|
|
20735
|
-
const CREATE_KEY_FN$
|
|
20922
|
+
const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20736
20923
|
const parts = [symbol, strategyName, exchangeName];
|
|
20737
20924
|
if (frameName)
|
|
20738
20925
|
parts.push(frameName);
|
|
@@ -21227,7 +21414,7 @@ class LiveMarkdownService {
|
|
|
21227
21414
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21228
21415
|
* Each combination gets its own isolated storage instance.
|
|
21229
21416
|
*/
|
|
21230
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21417
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
|
|
21231
21418
|
/**
|
|
21232
21419
|
* Subscribes to live signal emitter to receive tick events.
|
|
21233
21420
|
* Protected against multiple subscriptions.
|
|
@@ -21445,7 +21632,7 @@ class LiveMarkdownService {
|
|
|
21445
21632
|
payload,
|
|
21446
21633
|
});
|
|
21447
21634
|
if (payload) {
|
|
21448
|
-
const key = CREATE_KEY_FN$
|
|
21635
|
+
const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21449
21636
|
this.getStorage.clear(key);
|
|
21450
21637
|
}
|
|
21451
21638
|
else {
|
|
@@ -21465,7 +21652,7 @@ class LiveMarkdownService {
|
|
|
21465
21652
|
* @param backtest - Whether running in backtest mode
|
|
21466
21653
|
* @returns Unique string key for memoization
|
|
21467
21654
|
*/
|
|
21468
|
-
const CREATE_KEY_FN$
|
|
21655
|
+
const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21469
21656
|
const parts = [symbol, strategyName, exchangeName];
|
|
21470
21657
|
if (frameName)
|
|
21471
21658
|
parts.push(frameName);
|
|
@@ -21754,7 +21941,7 @@ class ScheduleMarkdownService {
|
|
|
21754
21941
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21755
21942
|
* Each combination gets its own isolated storage instance.
|
|
21756
21943
|
*/
|
|
21757
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21944
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
|
|
21758
21945
|
/**
|
|
21759
21946
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
21760
21947
|
* Protected against multiple subscriptions.
|
|
@@ -21957,7 +22144,7 @@ class ScheduleMarkdownService {
|
|
|
21957
22144
|
payload,
|
|
21958
22145
|
});
|
|
21959
22146
|
if (payload) {
|
|
21960
|
-
const key = CREATE_KEY_FN$
|
|
22147
|
+
const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21961
22148
|
this.getStorage.clear(key);
|
|
21962
22149
|
}
|
|
21963
22150
|
else {
|
|
@@ -21977,7 +22164,7 @@ class ScheduleMarkdownService {
|
|
|
21977
22164
|
* @param backtest - Whether running in backtest mode
|
|
21978
22165
|
* @returns Unique string key for memoization
|
|
21979
22166
|
*/
|
|
21980
|
-
const CREATE_KEY_FN$
|
|
22167
|
+
const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21981
22168
|
const parts = [symbol, strategyName, exchangeName];
|
|
21982
22169
|
if (frameName)
|
|
21983
22170
|
parts.push(frameName);
|
|
@@ -22222,7 +22409,7 @@ class PerformanceMarkdownService {
|
|
|
22222
22409
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
22223
22410
|
* Each combination gets its own isolated storage instance.
|
|
22224
22411
|
*/
|
|
22225
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22412
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
|
|
22226
22413
|
/**
|
|
22227
22414
|
* Subscribes to performance emitter to receive performance events.
|
|
22228
22415
|
* Protected against multiple subscriptions.
|
|
@@ -22389,7 +22576,7 @@ class PerformanceMarkdownService {
|
|
|
22389
22576
|
payload,
|
|
22390
22577
|
});
|
|
22391
22578
|
if (payload) {
|
|
22392
|
-
const key = CREATE_KEY_FN$
|
|
22579
|
+
const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
22393
22580
|
this.getStorage.clear(key);
|
|
22394
22581
|
}
|
|
22395
22582
|
else {
|
|
@@ -22868,7 +23055,7 @@ class WalkerMarkdownService {
|
|
|
22868
23055
|
* @param backtest - Whether running in backtest mode
|
|
22869
23056
|
* @returns Unique string key for memoization
|
|
22870
23057
|
*/
|
|
22871
|
-
const CREATE_KEY_FN$
|
|
23058
|
+
const CREATE_KEY_FN$i = (exchangeName, frameName, backtest) => {
|
|
22872
23059
|
const parts = [exchangeName];
|
|
22873
23060
|
if (frameName)
|
|
22874
23061
|
parts.push(frameName);
|
|
@@ -23315,7 +23502,7 @@ class HeatMarkdownService {
|
|
|
23315
23502
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
23316
23503
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
23317
23504
|
*/
|
|
23318
|
-
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
23505
|
+
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
23319
23506
|
/**
|
|
23320
23507
|
* Subscribes to signal emitter to receive tick events.
|
|
23321
23508
|
* Protected against multiple subscriptions.
|
|
@@ -23533,7 +23720,7 @@ class HeatMarkdownService {
|
|
|
23533
23720
|
payload,
|
|
23534
23721
|
});
|
|
23535
23722
|
if (payload) {
|
|
23536
|
-
const key = CREATE_KEY_FN$
|
|
23723
|
+
const key = CREATE_KEY_FN$i(payload.exchangeName, payload.frameName, payload.backtest);
|
|
23537
23724
|
this.getStorage.clear(key);
|
|
23538
23725
|
}
|
|
23539
23726
|
else {
|
|
@@ -24564,7 +24751,7 @@ class ClientPartial {
|
|
|
24564
24751
|
* @param backtest - Whether running in backtest mode
|
|
24565
24752
|
* @returns Unique string key for memoization
|
|
24566
24753
|
*/
|
|
24567
|
-
const CREATE_KEY_FN$
|
|
24754
|
+
const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
24568
24755
|
/**
|
|
24569
24756
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
24570
24757
|
*
|
|
@@ -24686,7 +24873,7 @@ class PartialConnectionService {
|
|
|
24686
24873
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
24687
24874
|
* Value: ClientPartial instance with logger and event emitters
|
|
24688
24875
|
*/
|
|
24689
|
-
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
24876
|
+
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
|
|
24690
24877
|
return new ClientPartial({
|
|
24691
24878
|
signalId,
|
|
24692
24879
|
logger: this.loggerService,
|
|
@@ -24776,7 +24963,7 @@ class PartialConnectionService {
|
|
|
24776
24963
|
const partial = this.getPartial(data.id, backtest);
|
|
24777
24964
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
24778
24965
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
24779
|
-
const key = CREATE_KEY_FN$
|
|
24966
|
+
const key = CREATE_KEY_FN$h(data.id, backtest);
|
|
24780
24967
|
this.getPartial.clear(key);
|
|
24781
24968
|
};
|
|
24782
24969
|
}
|
|
@@ -24792,7 +24979,7 @@ class PartialConnectionService {
|
|
|
24792
24979
|
* @param backtest - Whether running in backtest mode
|
|
24793
24980
|
* @returns Unique string key for memoization
|
|
24794
24981
|
*/
|
|
24795
|
-
const CREATE_KEY_FN$
|
|
24982
|
+
const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24796
24983
|
const parts = [symbol, strategyName, exchangeName];
|
|
24797
24984
|
if (frameName)
|
|
24798
24985
|
parts.push(frameName);
|
|
@@ -25015,7 +25202,7 @@ class PartialMarkdownService {
|
|
|
25015
25202
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
25016
25203
|
* Each combination gets its own isolated storage instance.
|
|
25017
25204
|
*/
|
|
25018
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
25205
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
|
|
25019
25206
|
/**
|
|
25020
25207
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
25021
25208
|
* Protected against multiple subscriptions.
|
|
@@ -25225,7 +25412,7 @@ class PartialMarkdownService {
|
|
|
25225
25412
|
payload,
|
|
25226
25413
|
});
|
|
25227
25414
|
if (payload) {
|
|
25228
|
-
const key = CREATE_KEY_FN$
|
|
25415
|
+
const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
25229
25416
|
this.getStorage.clear(key);
|
|
25230
25417
|
}
|
|
25231
25418
|
else {
|
|
@@ -25241,7 +25428,7 @@ class PartialMarkdownService {
|
|
|
25241
25428
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
25242
25429
|
* @returns Unique string key for memoization
|
|
25243
25430
|
*/
|
|
25244
|
-
const CREATE_KEY_FN$
|
|
25431
|
+
const CREATE_KEY_FN$f = (context) => {
|
|
25245
25432
|
const parts = [context.strategyName, context.exchangeName];
|
|
25246
25433
|
if (context.frameName)
|
|
25247
25434
|
parts.push(context.frameName);
|
|
@@ -25315,7 +25502,7 @@ class PartialGlobalService {
|
|
|
25315
25502
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
25316
25503
|
* @param methodName - Name of the calling method for error tracking
|
|
25317
25504
|
*/
|
|
25318
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
25505
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
|
|
25319
25506
|
this.loggerService.log("partialGlobalService validate", {
|
|
25320
25507
|
context,
|
|
25321
25508
|
methodName,
|
|
@@ -25770,7 +25957,7 @@ class ClientBreakeven {
|
|
|
25770
25957
|
* @param backtest - Whether running in backtest mode
|
|
25771
25958
|
* @returns Unique string key for memoization
|
|
25772
25959
|
*/
|
|
25773
|
-
const CREATE_KEY_FN$
|
|
25960
|
+
const CREATE_KEY_FN$e = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
25774
25961
|
/**
|
|
25775
25962
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
25776
25963
|
*
|
|
@@ -25856,7 +26043,7 @@ class BreakevenConnectionService {
|
|
|
25856
26043
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
25857
26044
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
25858
26045
|
*/
|
|
25859
|
-
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
26046
|
+
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$e(signalId, backtest), (signalId, backtest) => {
|
|
25860
26047
|
return new ClientBreakeven({
|
|
25861
26048
|
signalId,
|
|
25862
26049
|
logger: this.loggerService,
|
|
@@ -25917,7 +26104,7 @@ class BreakevenConnectionService {
|
|
|
25917
26104
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
25918
26105
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
25919
26106
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
25920
|
-
const key = CREATE_KEY_FN$
|
|
26107
|
+
const key = CREATE_KEY_FN$e(data.id, backtest);
|
|
25921
26108
|
this.getBreakeven.clear(key);
|
|
25922
26109
|
};
|
|
25923
26110
|
}
|
|
@@ -25933,7 +26120,7 @@ class BreakevenConnectionService {
|
|
|
25933
26120
|
* @param backtest - Whether running in backtest mode
|
|
25934
26121
|
* @returns Unique string key for memoization
|
|
25935
26122
|
*/
|
|
25936
|
-
const CREATE_KEY_FN$
|
|
26123
|
+
const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
25937
26124
|
const parts = [symbol, strategyName, exchangeName];
|
|
25938
26125
|
if (frameName)
|
|
25939
26126
|
parts.push(frameName);
|
|
@@ -26108,7 +26295,7 @@ class BreakevenMarkdownService {
|
|
|
26108
26295
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26109
26296
|
* Each combination gets its own isolated storage instance.
|
|
26110
26297
|
*/
|
|
26111
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
26298
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
|
|
26112
26299
|
/**
|
|
26113
26300
|
* Subscribes to breakeven signal emitter to receive events.
|
|
26114
26301
|
* Protected against multiple subscriptions.
|
|
@@ -26297,7 +26484,7 @@ class BreakevenMarkdownService {
|
|
|
26297
26484
|
payload,
|
|
26298
26485
|
});
|
|
26299
26486
|
if (payload) {
|
|
26300
|
-
const key = CREATE_KEY_FN$
|
|
26487
|
+
const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26301
26488
|
this.getStorage.clear(key);
|
|
26302
26489
|
}
|
|
26303
26490
|
else {
|
|
@@ -26313,7 +26500,7 @@ class BreakevenMarkdownService {
|
|
|
26313
26500
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
26314
26501
|
* @returns Unique string key for memoization
|
|
26315
26502
|
*/
|
|
26316
|
-
const CREATE_KEY_FN$
|
|
26503
|
+
const CREATE_KEY_FN$c = (context) => {
|
|
26317
26504
|
const parts = [context.strategyName, context.exchangeName];
|
|
26318
26505
|
if (context.frameName)
|
|
26319
26506
|
parts.push(context.frameName);
|
|
@@ -26387,7 +26574,7 @@ class BreakevenGlobalService {
|
|
|
26387
26574
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
26388
26575
|
* @param methodName - Name of the calling method for error tracking
|
|
26389
26576
|
*/
|
|
26390
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
26577
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$c(context), (context, methodName) => {
|
|
26391
26578
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
26392
26579
|
context,
|
|
26393
26580
|
methodName,
|
|
@@ -26608,7 +26795,7 @@ class ConfigValidationService {
|
|
|
26608
26795
|
* @param backtest - Whether running in backtest mode
|
|
26609
26796
|
* @returns Unique string key for memoization
|
|
26610
26797
|
*/
|
|
26611
|
-
const CREATE_KEY_FN$
|
|
26798
|
+
const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
26612
26799
|
const parts = [symbol, strategyName, exchangeName];
|
|
26613
26800
|
if (frameName)
|
|
26614
26801
|
parts.push(frameName);
|
|
@@ -26775,7 +26962,7 @@ class RiskMarkdownService {
|
|
|
26775
26962
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26776
26963
|
* Each combination gets its own isolated storage instance.
|
|
26777
26964
|
*/
|
|
26778
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
26965
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
|
|
26779
26966
|
/**
|
|
26780
26967
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
26781
26968
|
* Protected against multiple subscriptions.
|
|
@@ -26964,7 +27151,7 @@ class RiskMarkdownService {
|
|
|
26964
27151
|
payload,
|
|
26965
27152
|
});
|
|
26966
27153
|
if (payload) {
|
|
26967
|
-
const key = CREATE_KEY_FN$
|
|
27154
|
+
const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26968
27155
|
this.getStorage.clear(key);
|
|
26969
27156
|
}
|
|
26970
27157
|
else {
|
|
@@ -29343,7 +29530,7 @@ class HighestProfitReportService {
|
|
|
29343
29530
|
* @returns Colon-separated key string for memoization
|
|
29344
29531
|
* @internal
|
|
29345
29532
|
*/
|
|
29346
|
-
const CREATE_KEY_FN$
|
|
29533
|
+
const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29347
29534
|
const parts = [symbol, strategyName, exchangeName];
|
|
29348
29535
|
if (frameName)
|
|
29349
29536
|
parts.push(frameName);
|
|
@@ -29585,7 +29772,7 @@ class StrategyMarkdownService {
|
|
|
29585
29772
|
*
|
|
29586
29773
|
* @internal
|
|
29587
29774
|
*/
|
|
29588
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
29775
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
|
|
29589
29776
|
/**
|
|
29590
29777
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
29591
29778
|
*
|
|
@@ -30153,7 +30340,7 @@ class StrategyMarkdownService {
|
|
|
30153
30340
|
this.clear = async (payload) => {
|
|
30154
30341
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
30155
30342
|
if (payload) {
|
|
30156
|
-
const key = CREATE_KEY_FN$
|
|
30343
|
+
const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
30157
30344
|
this.getStorage.clear(key);
|
|
30158
30345
|
}
|
|
30159
30346
|
else {
|
|
@@ -30261,7 +30448,7 @@ class StrategyMarkdownService {
|
|
|
30261
30448
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
30262
30449
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
30263
30450
|
*/
|
|
30264
|
-
const CREATE_KEY_FN$
|
|
30451
|
+
const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
30265
30452
|
const parts = [symbol, strategyName, exchangeName];
|
|
30266
30453
|
if (frameName)
|
|
30267
30454
|
parts.push(frameName);
|
|
@@ -30454,7 +30641,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
30454
30641
|
class SyncMarkdownService {
|
|
30455
30642
|
constructor() {
|
|
30456
30643
|
this.loggerService = inject(TYPES.loggerService);
|
|
30457
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
30644
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
|
|
30458
30645
|
/**
|
|
30459
30646
|
* Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
|
|
30460
30647
|
* Protected against multiple subscriptions via `singleshot` — subsequent calls
|
|
@@ -30650,7 +30837,7 @@ class SyncMarkdownService {
|
|
|
30650
30837
|
this.clear = async (payload) => {
|
|
30651
30838
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
30652
30839
|
if (payload) {
|
|
30653
|
-
const key = CREATE_KEY_FN$
|
|
30840
|
+
const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
30654
30841
|
this.getStorage.clear(key);
|
|
30655
30842
|
}
|
|
30656
30843
|
else {
|
|
@@ -30663,7 +30850,7 @@ class SyncMarkdownService {
|
|
|
30663
30850
|
/**
|
|
30664
30851
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
30665
30852
|
*/
|
|
30666
|
-
const CREATE_KEY_FN$
|
|
30853
|
+
const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
30667
30854
|
const parts = [symbol, strategyName, exchangeName];
|
|
30668
30855
|
if (frameName)
|
|
30669
30856
|
parts.push(frameName);
|
|
@@ -30839,7 +31026,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
30839
31026
|
class HighestProfitMarkdownService {
|
|
30840
31027
|
constructor() {
|
|
30841
31028
|
this.loggerService = inject(TYPES.loggerService);
|
|
30842
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31029
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
|
|
30843
31030
|
/**
|
|
30844
31031
|
* Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
|
|
30845
31032
|
* events. Protected against multiple subscriptions via `singleshot` — subsequent
|
|
@@ -31005,7 +31192,7 @@ class HighestProfitMarkdownService {
|
|
|
31005
31192
|
this.clear = async (payload) => {
|
|
31006
31193
|
this.loggerService.log("highestProfitMarkdownService clear", { payload });
|
|
31007
31194
|
if (payload) {
|
|
31008
|
-
const key = CREATE_KEY_FN$
|
|
31195
|
+
const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31009
31196
|
this.getStorage.clear(key);
|
|
31010
31197
|
}
|
|
31011
31198
|
else {
|
|
@@ -31027,7 +31214,7 @@ const LISTEN_TIMEOUT$1 = 120000;
|
|
|
31027
31214
|
* @param backtest - Whether running in backtest mode
|
|
31028
31215
|
* @returns Unique string key for memoization
|
|
31029
31216
|
*/
|
|
31030
|
-
const CREATE_KEY_FN$
|
|
31217
|
+
const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31031
31218
|
const parts = [symbol, strategyName, exchangeName];
|
|
31032
31219
|
if (frameName)
|
|
31033
31220
|
parts.push(frameName);
|
|
@@ -31070,7 +31257,7 @@ class PriceMetaService {
|
|
|
31070
31257
|
* Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
|
|
31071
31258
|
* Instances are cached until clear() is called.
|
|
31072
31259
|
*/
|
|
31073
|
-
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31260
|
+
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
|
|
31074
31261
|
/**
|
|
31075
31262
|
* Returns the current market price for the given symbol and context.
|
|
31076
31263
|
*
|
|
@@ -31099,10 +31286,10 @@ class PriceMetaService {
|
|
|
31099
31286
|
if (source.data) {
|
|
31100
31287
|
return source.data;
|
|
31101
31288
|
}
|
|
31102
|
-
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$
|
|
31289
|
+
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
31103
31290
|
const currentPrice = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
|
|
31104
31291
|
if (typeof currentPrice === "symbol") {
|
|
31105
|
-
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$
|
|
31292
|
+
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31106
31293
|
}
|
|
31107
31294
|
return currentPrice;
|
|
31108
31295
|
};
|
|
@@ -31144,7 +31331,7 @@ class PriceMetaService {
|
|
|
31144
31331
|
this.getSource.clear();
|
|
31145
31332
|
return;
|
|
31146
31333
|
}
|
|
31147
|
-
const key = CREATE_KEY_FN$
|
|
31334
|
+
const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31148
31335
|
this.getSource.clear(key);
|
|
31149
31336
|
};
|
|
31150
31337
|
}
|
|
@@ -31162,7 +31349,7 @@ const LISTEN_TIMEOUT = 120000;
|
|
|
31162
31349
|
* @param backtest - Whether running in backtest mode
|
|
31163
31350
|
* @returns Unique string key for memoization
|
|
31164
31351
|
*/
|
|
31165
|
-
const CREATE_KEY_FN$
|
|
31352
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31166
31353
|
const parts = [symbol, strategyName, exchangeName];
|
|
31167
31354
|
if (frameName)
|
|
31168
31355
|
parts.push(frameName);
|
|
@@ -31205,7 +31392,7 @@ class TimeMetaService {
|
|
|
31205
31392
|
* Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
|
|
31206
31393
|
* Instances are cached until clear() is called.
|
|
31207
31394
|
*/
|
|
31208
|
-
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31395
|
+
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
|
|
31209
31396
|
/**
|
|
31210
31397
|
* Returns the current candle timestamp (in milliseconds) for the given symbol and context.
|
|
31211
31398
|
*
|
|
@@ -31233,10 +31420,10 @@ class TimeMetaService {
|
|
|
31233
31420
|
if (source.data) {
|
|
31234
31421
|
return source.data;
|
|
31235
31422
|
}
|
|
31236
|
-
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$
|
|
31423
|
+
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
31237
31424
|
const timestamp = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
|
|
31238
31425
|
if (typeof timestamp === "symbol") {
|
|
31239
|
-
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$
|
|
31426
|
+
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31240
31427
|
}
|
|
31241
31428
|
return timestamp;
|
|
31242
31429
|
};
|
|
@@ -31278,7 +31465,7 @@ class TimeMetaService {
|
|
|
31278
31465
|
this.getSource.clear();
|
|
31279
31466
|
return;
|
|
31280
31467
|
}
|
|
31281
|
-
const key = CREATE_KEY_FN$
|
|
31468
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31282
31469
|
this.getSource.clear(key);
|
|
31283
31470
|
};
|
|
31284
31471
|
}
|
|
@@ -31374,7 +31561,7 @@ class MaxDrawdownReportService {
|
|
|
31374
31561
|
/**
|
|
31375
31562
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
31376
31563
|
*/
|
|
31377
|
-
const CREATE_KEY_FN$
|
|
31564
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31378
31565
|
const parts = [symbol, strategyName, exchangeName];
|
|
31379
31566
|
if (frameName)
|
|
31380
31567
|
parts.push(frameName);
|
|
@@ -31498,7 +31685,7 @@ class ReportStorage {
|
|
|
31498
31685
|
class MaxDrawdownMarkdownService {
|
|
31499
31686
|
constructor() {
|
|
31500
31687
|
this.loggerService = inject(TYPES.loggerService);
|
|
31501
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31688
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
|
|
31502
31689
|
/**
|
|
31503
31690
|
* Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
|
|
31504
31691
|
* events. Protected against multiple subscriptions via `singleshot`.
|
|
@@ -31577,7 +31764,7 @@ class MaxDrawdownMarkdownService {
|
|
|
31577
31764
|
this.clear = async (payload) => {
|
|
31578
31765
|
this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
|
|
31579
31766
|
if (payload) {
|
|
31580
|
-
const key = CREATE_KEY_FN$
|
|
31767
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31581
31768
|
this.getStorage.clear(key);
|
|
31582
31769
|
}
|
|
31583
31770
|
else {
|
|
@@ -31831,7 +32018,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
|
31831
32018
|
const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
|
|
31832
32019
|
const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
|
|
31833
32020
|
const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
|
|
31834
|
-
const MS_PER_MINUTE$
|
|
32021
|
+
const MS_PER_MINUTE$3 = 60000;
|
|
31835
32022
|
/**
|
|
31836
32023
|
* Gets current timestamp from execution context if available.
|
|
31837
32024
|
* Returns current Date() if no execution context exists (non-trading GUI).
|
|
@@ -31898,7 +32085,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
|
|
|
31898
32085
|
const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
|
|
31899
32086
|
throw new Error(`getAggregatedTrades is not implemented for this exchange`);
|
|
31900
32087
|
};
|
|
31901
|
-
const INTERVAL_MINUTES$
|
|
32088
|
+
const INTERVAL_MINUTES$3 = {
|
|
31902
32089
|
"1m": 1,
|
|
31903
32090
|
"3m": 3,
|
|
31904
32091
|
"5m": 5,
|
|
@@ -31930,7 +32117,7 @@ const INTERVAL_MINUTES$2 = {
|
|
|
31930
32117
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
31931
32118
|
*/
|
|
31932
32119
|
const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
|
|
31933
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
32120
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
|
|
31934
32121
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
31935
32122
|
};
|
|
31936
32123
|
/**
|
|
@@ -32070,11 +32257,11 @@ class ExchangeInstance {
|
|
|
32070
32257
|
limit,
|
|
32071
32258
|
});
|
|
32072
32259
|
const getCandles = this._methods.getCandles;
|
|
32073
|
-
const step = INTERVAL_MINUTES$
|
|
32260
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
32074
32261
|
if (!step) {
|
|
32075
32262
|
throw new Error(`ExchangeInstance unknown interval=${interval}`);
|
|
32076
32263
|
}
|
|
32077
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32264
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
32078
32265
|
// Align when down to interval boundary
|
|
32079
32266
|
const when = await GET_TIMESTAMP_FN();
|
|
32080
32267
|
const whenTimestamp = when.getTime();
|
|
@@ -32259,7 +32446,7 @@ class ExchangeInstance {
|
|
|
32259
32446
|
const when = await GET_TIMESTAMP_FN();
|
|
32260
32447
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
32261
32448
|
const to = new Date(alignedTo);
|
|
32262
|
-
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
32449
|
+
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
|
|
32263
32450
|
const isBacktest = await GET_BACKTEST_FN();
|
|
32264
32451
|
return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
|
|
32265
32452
|
};
|
|
@@ -32291,7 +32478,7 @@ class ExchangeInstance {
|
|
|
32291
32478
|
const when = await GET_TIMESTAMP_FN();
|
|
32292
32479
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
32293
32480
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
|
|
32294
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
32481
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$3 - MS_PER_MINUTE$3;
|
|
32295
32482
|
const isBacktest = await GET_BACKTEST_FN();
|
|
32296
32483
|
// No limit: fetch a single window and return as-is
|
|
32297
32484
|
if (limit === undefined) {
|
|
@@ -32354,11 +32541,11 @@ class ExchangeInstance {
|
|
|
32354
32541
|
sDate,
|
|
32355
32542
|
eDate,
|
|
32356
32543
|
});
|
|
32357
|
-
const step = INTERVAL_MINUTES$
|
|
32544
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
32358
32545
|
if (!step) {
|
|
32359
32546
|
throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
|
|
32360
32547
|
}
|
|
32361
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32548
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
32362
32549
|
const when = await GET_TIMESTAMP_FN();
|
|
32363
32550
|
const nowTimestamp = when.getTime();
|
|
32364
32551
|
const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
|
|
@@ -32648,8 +32835,8 @@ const Exchange = new ExchangeUtils();
|
|
|
32648
32835
|
|
|
32649
32836
|
const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
|
|
32650
32837
|
const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
|
|
32651
|
-
const MS_PER_MINUTE$
|
|
32652
|
-
const INTERVAL_MINUTES$
|
|
32838
|
+
const MS_PER_MINUTE$2 = 60000;
|
|
32839
|
+
const INTERVAL_MINUTES$2 = {
|
|
32653
32840
|
"1m": 1,
|
|
32654
32841
|
"3m": 3,
|
|
32655
32842
|
"5m": 5,
|
|
@@ -32664,7 +32851,7 @@ const INTERVAL_MINUTES$1 = {
|
|
|
32664
32851
|
"1w": 10080,
|
|
32665
32852
|
};
|
|
32666
32853
|
const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
|
|
32667
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
32854
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
|
|
32668
32855
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
32669
32856
|
};
|
|
32670
32857
|
const BAR_LENGTH = 30;
|
|
@@ -32689,11 +32876,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
|
|
|
32689
32876
|
async function checkCandles(params) {
|
|
32690
32877
|
const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
|
|
32691
32878
|
backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
|
|
32692
|
-
const step = INTERVAL_MINUTES$
|
|
32879
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
32693
32880
|
if (!step) {
|
|
32694
32881
|
throw new Error(`checkCandles: unsupported interval=${interval}`);
|
|
32695
32882
|
}
|
|
32696
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32883
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
32697
32884
|
const dir = join(baseDir, exchangeName, symbol, interval);
|
|
32698
32885
|
const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
32699
32886
|
const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -32763,11 +32950,11 @@ async function warmCandles(params) {
|
|
|
32763
32950
|
from,
|
|
32764
32951
|
to,
|
|
32765
32952
|
});
|
|
32766
|
-
const step = INTERVAL_MINUTES$
|
|
32953
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
32767
32954
|
if (!step) {
|
|
32768
32955
|
throw new Error(`warmCandles: unsupported interval=${interval}`);
|
|
32769
32956
|
}
|
|
32770
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32957
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
32771
32958
|
const instance = new ExchangeInstance(exchangeName);
|
|
32772
32959
|
const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
32773
32960
|
const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -44176,7 +44363,7 @@ const createSearchIndex = () => {
|
|
|
44176
44363
|
return { upsert, remove, list, search, read };
|
|
44177
44364
|
};
|
|
44178
44365
|
|
|
44179
|
-
const CREATE_KEY_FN$
|
|
44366
|
+
const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}-${bucketName}`;
|
|
44180
44367
|
const LIST_MEMORY_FN = ({ id, content }) => ({
|
|
44181
44368
|
memoryId: id,
|
|
44182
44369
|
content: content,
|
|
@@ -44498,7 +44685,7 @@ class MemoryDummyInstance {
|
|
|
44498
44685
|
class MemoryAdapter {
|
|
44499
44686
|
constructor() {
|
|
44500
44687
|
this.MemoryFactory = MemoryPersistInstance;
|
|
44501
|
-
this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$
|
|
44688
|
+
this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
|
|
44502
44689
|
/**
|
|
44503
44690
|
* Activates the adapter by subscribing to signal lifecycle events.
|
|
44504
44691
|
* Clears memoized instances for a signalId when it is cancelled or closed,
|
|
@@ -44509,7 +44696,7 @@ class MemoryAdapter {
|
|
|
44509
44696
|
this.enable = singleshot(() => {
|
|
44510
44697
|
backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_ENABLE);
|
|
44511
44698
|
const handleDispose = (signalId) => {
|
|
44512
|
-
const prefix = CREATE_KEY_FN$
|
|
44699
|
+
const prefix = CREATE_KEY_FN$4(signalId, "");
|
|
44513
44700
|
for (const key of this.getInstance.keys()) {
|
|
44514
44701
|
if (key.startsWith(prefix)) {
|
|
44515
44702
|
const instance = this.getInstance.get(key);
|
|
@@ -44554,7 +44741,7 @@ class MemoryAdapter {
|
|
|
44554
44741
|
bucketName: dto.bucketName,
|
|
44555
44742
|
memoryId: dto.memoryId,
|
|
44556
44743
|
});
|
|
44557
|
-
const key = CREATE_KEY_FN$
|
|
44744
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44558
44745
|
const isInitial = !this.getInstance.has(key);
|
|
44559
44746
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44560
44747
|
await instance.waitForInit(isInitial);
|
|
@@ -44576,7 +44763,7 @@ class MemoryAdapter {
|
|
|
44576
44763
|
bucketName: dto.bucketName,
|
|
44577
44764
|
query: dto.query,
|
|
44578
44765
|
});
|
|
44579
|
-
const key = CREATE_KEY_FN$
|
|
44766
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44580
44767
|
const isInitial = !this.getInstance.has(key);
|
|
44581
44768
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44582
44769
|
await instance.waitForInit(isInitial);
|
|
@@ -44596,7 +44783,7 @@ class MemoryAdapter {
|
|
|
44596
44783
|
signalId: dto.signalId,
|
|
44597
44784
|
bucketName: dto.bucketName,
|
|
44598
44785
|
});
|
|
44599
|
-
const key = CREATE_KEY_FN$
|
|
44786
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44600
44787
|
const isInitial = !this.getInstance.has(key);
|
|
44601
44788
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44602
44789
|
await instance.waitForInit(isInitial);
|
|
@@ -44617,7 +44804,7 @@ class MemoryAdapter {
|
|
|
44617
44804
|
bucketName: dto.bucketName,
|
|
44618
44805
|
memoryId: dto.memoryId,
|
|
44619
44806
|
});
|
|
44620
|
-
const key = CREATE_KEY_FN$
|
|
44807
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44621
44808
|
const isInitial = !this.getInstance.has(key);
|
|
44622
44809
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44623
44810
|
await instance.waitForInit(isInitial);
|
|
@@ -44640,7 +44827,7 @@ class MemoryAdapter {
|
|
|
44640
44827
|
bucketName: dto.bucketName,
|
|
44641
44828
|
memoryId: dto.memoryId,
|
|
44642
44829
|
});
|
|
44643
|
-
const key = CREATE_KEY_FN$
|
|
44830
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44644
44831
|
const isInitial = !this.getInstance.has(key);
|
|
44645
44832
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44646
44833
|
await instance.waitForInit(isInitial);
|
|
@@ -44932,7 +45119,7 @@ async function removeMemory(dto) {
|
|
|
44932
45119
|
});
|
|
44933
45120
|
}
|
|
44934
45121
|
|
|
44935
|
-
const CREATE_KEY_FN$
|
|
45122
|
+
const CREATE_KEY_FN$3 = (signalId, bucketName) => `${signalId}-${bucketName}`;
|
|
44936
45123
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_AGENT = "DumpMemoryInstance.dumpAgentAnswer";
|
|
44937
45124
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_RECORD = "DumpMemoryInstance.dumpRecord";
|
|
44938
45125
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_TABLE = "DumpMemoryInstance.dumpTable";
|
|
@@ -45552,7 +45739,7 @@ class DumpDummyInstance {
|
|
|
45552
45739
|
class DumpAdapter {
|
|
45553
45740
|
constructor() {
|
|
45554
45741
|
this.DumpFactory = DumpMarkdownInstance;
|
|
45555
|
-
this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$
|
|
45742
|
+
this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.DumpFactory, [signalId, bucketName]));
|
|
45556
45743
|
/**
|
|
45557
45744
|
* Activates the adapter by subscribing to signal lifecycle events.
|
|
45558
45745
|
* Clears memoized instances for a signalId when it is cancelled or closed,
|
|
@@ -45563,7 +45750,7 @@ class DumpAdapter {
|
|
|
45563
45750
|
this.enable = singleshot(() => {
|
|
45564
45751
|
backtest.loggerService.info(DUMP_ADAPTER_METHOD_NAME_ENABLE);
|
|
45565
45752
|
const handleDispose = (signalId) => {
|
|
45566
|
-
const prefix = CREATE_KEY_FN$
|
|
45753
|
+
const prefix = CREATE_KEY_FN$3(signalId, "");
|
|
45567
45754
|
for (const key of this.getInstance.keys()) {
|
|
45568
45755
|
if (key.startsWith(prefix)) {
|
|
45569
45756
|
const instance = this.getInstance.get(key);
|
|
@@ -48497,6 +48684,49 @@ PositionSizeUtils.atrBased = async (symbol, accountBalance, priceOpen, atr, cont
|
|
|
48497
48684
|
};
|
|
48498
48685
|
const PositionSize = PositionSizeUtils;
|
|
48499
48686
|
|
|
48687
|
+
const METHOD_NAME_MOONBAG = "Position.moonbag";
|
|
48688
|
+
const METHOD_NAME_BRACKET = "Position.bracket";
|
|
48689
|
+
/**
|
|
48690
|
+
* Utilities for calculating take profit and stop loss price levels.
|
|
48691
|
+
* Automatically inverts direction based on position type (long/short).
|
|
48692
|
+
*/
|
|
48693
|
+
class Position {
|
|
48694
|
+
}
|
|
48695
|
+
/**
|
|
48696
|
+
* Calculates levels for the "moonbag" strategy — fixed TP at 50% from the current price.
|
|
48697
|
+
* @param dto.position - position type: "long" or "short"
|
|
48698
|
+
* @param dto.currentPrice - current asset price
|
|
48699
|
+
* @param dto.percentStopLoss - stop loss percentage from 0 to 100
|
|
48700
|
+
* @returns priceTakeProfit and priceStopLoss in fiat
|
|
48701
|
+
*/
|
|
48702
|
+
Position.moonbag = (dto) => {
|
|
48703
|
+
backtest.loggerService.log(METHOD_NAME_MOONBAG, { dto });
|
|
48704
|
+
const percentTakeProfit = 50;
|
|
48705
|
+
const sign = dto.position === "long" ? 1 : -1;
|
|
48706
|
+
return {
|
|
48707
|
+
position: dto.position,
|
|
48708
|
+
priceTakeProfit: dto.currentPrice * (1 + sign * percentTakeProfit / 100),
|
|
48709
|
+
priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
|
|
48710
|
+
};
|
|
48711
|
+
};
|
|
48712
|
+
/**
|
|
48713
|
+
* Calculates levels for a bracket order with custom TP and SL.
|
|
48714
|
+
* @param dto.position - position type: "long" or "short"
|
|
48715
|
+
* @param dto.currentPrice - current asset price
|
|
48716
|
+
* @param dto.percentStopLoss - stop loss percentage from 0 to 100
|
|
48717
|
+
* @param dto.percentTakeProfit - take profit percentage from 0 to 100
|
|
48718
|
+
* @returns priceTakeProfit and priceStopLoss in fiat
|
|
48719
|
+
*/
|
|
48720
|
+
Position.bracket = (dto) => {
|
|
48721
|
+
backtest.loggerService.log(METHOD_NAME_BRACKET, { dto });
|
|
48722
|
+
const sign = dto.position === "long" ? 1 : -1;
|
|
48723
|
+
return {
|
|
48724
|
+
position: dto.position,
|
|
48725
|
+
priceTakeProfit: dto.currentPrice * (1 + sign * dto.percentTakeProfit / 100),
|
|
48726
|
+
priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
|
|
48727
|
+
};
|
|
48728
|
+
};
|
|
48729
|
+
|
|
48500
48730
|
const PARTIAL_METHOD_NAME_GET_DATA = "PartialUtils.getData";
|
|
48501
48731
|
const PARTIAL_METHOD_NAME_GET_REPORT = "PartialUtils.getReport";
|
|
48502
48732
|
const PARTIAL_METHOD_NAME_DUMP = "PartialUtils.dump";
|
|
@@ -50699,7 +50929,7 @@ const StorageBacktest = new StorageBacktestAdapter();
|
|
|
50699
50929
|
* Generates a unique key for notification identification.
|
|
50700
50930
|
* @returns Random string identifier
|
|
50701
50931
|
*/
|
|
50702
|
-
const CREATE_KEY_FN$
|
|
50932
|
+
const CREATE_KEY_FN$2 = () => randomString();
|
|
50703
50933
|
/**
|
|
50704
50934
|
* Creates a notification model from signal tick result.
|
|
50705
50935
|
* Handles opened, closed, scheduled, and cancelled signal actions.
|
|
@@ -50710,7 +50940,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50710
50940
|
if (data.action === "opened") {
|
|
50711
50941
|
return {
|
|
50712
50942
|
type: "signal.opened",
|
|
50713
|
-
id: CREATE_KEY_FN$
|
|
50943
|
+
id: CREATE_KEY_FN$2(),
|
|
50714
50944
|
timestamp: data.signal.pendingAt,
|
|
50715
50945
|
backtest: data.backtest,
|
|
50716
50946
|
symbol: data.symbol,
|
|
@@ -50744,7 +50974,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50744
50974
|
const durationMin = Math.round(durationMs / 60000);
|
|
50745
50975
|
return {
|
|
50746
50976
|
type: "signal.closed",
|
|
50747
|
-
id: CREATE_KEY_FN$
|
|
50977
|
+
id: CREATE_KEY_FN$2(),
|
|
50748
50978
|
timestamp: data.closeTimestamp,
|
|
50749
50979
|
backtest: data.backtest,
|
|
50750
50980
|
symbol: data.symbol,
|
|
@@ -50778,7 +51008,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50778
51008
|
if (data.action === "scheduled") {
|
|
50779
51009
|
return {
|
|
50780
51010
|
type: "signal.scheduled",
|
|
50781
|
-
id: CREATE_KEY_FN$
|
|
51011
|
+
id: CREATE_KEY_FN$2(),
|
|
50782
51012
|
timestamp: data.signal.scheduledAt,
|
|
50783
51013
|
backtest: data.backtest,
|
|
50784
51014
|
symbol: data.symbol,
|
|
@@ -50811,7 +51041,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50811
51041
|
const durationMin = Math.round(durationMs / 60000);
|
|
50812
51042
|
return {
|
|
50813
51043
|
type: "signal.cancelled",
|
|
50814
|
-
id: CREATE_KEY_FN$
|
|
51044
|
+
id: CREATE_KEY_FN$2(),
|
|
50815
51045
|
timestamp: data.closeTimestamp,
|
|
50816
51046
|
backtest: data.backtest,
|
|
50817
51047
|
symbol: data.symbol,
|
|
@@ -50844,7 +51074,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50844
51074
|
*/
|
|
50845
51075
|
const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
50846
51076
|
type: "partial_profit.available",
|
|
50847
|
-
id: CREATE_KEY_FN$
|
|
51077
|
+
id: CREATE_KEY_FN$2(),
|
|
50848
51078
|
timestamp: data.timestamp,
|
|
50849
51079
|
backtest: data.backtest,
|
|
50850
51080
|
symbol: data.symbol,
|
|
@@ -50879,7 +51109,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
|
50879
51109
|
*/
|
|
50880
51110
|
const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
50881
51111
|
type: "partial_loss.available",
|
|
50882
|
-
id: CREATE_KEY_FN$
|
|
51112
|
+
id: CREATE_KEY_FN$2(),
|
|
50883
51113
|
timestamp: data.timestamp,
|
|
50884
51114
|
backtest: data.backtest,
|
|
50885
51115
|
symbol: data.symbol,
|
|
@@ -50914,7 +51144,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
|
50914
51144
|
*/
|
|
50915
51145
|
const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
|
|
50916
51146
|
type: "breakeven.available",
|
|
50917
|
-
id: CREATE_KEY_FN$
|
|
51147
|
+
id: CREATE_KEY_FN$2(),
|
|
50918
51148
|
timestamp: data.timestamp,
|
|
50919
51149
|
backtest: data.backtest,
|
|
50920
51150
|
symbol: data.symbol,
|
|
@@ -50952,7 +51182,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
50952
51182
|
if (data.action === "partial-profit") {
|
|
50953
51183
|
return {
|
|
50954
51184
|
type: "partial_profit.commit",
|
|
50955
|
-
id: CREATE_KEY_FN$
|
|
51185
|
+
id: CREATE_KEY_FN$2(),
|
|
50956
51186
|
timestamp: data.timestamp,
|
|
50957
51187
|
backtest: data.backtest,
|
|
50958
51188
|
symbol: data.symbol,
|
|
@@ -50984,7 +51214,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
50984
51214
|
if (data.action === "partial-loss") {
|
|
50985
51215
|
return {
|
|
50986
51216
|
type: "partial_loss.commit",
|
|
50987
|
-
id: CREATE_KEY_FN$
|
|
51217
|
+
id: CREATE_KEY_FN$2(),
|
|
50988
51218
|
timestamp: data.timestamp,
|
|
50989
51219
|
backtest: data.backtest,
|
|
50990
51220
|
symbol: data.symbol,
|
|
@@ -51016,7 +51246,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51016
51246
|
if (data.action === "breakeven") {
|
|
51017
51247
|
return {
|
|
51018
51248
|
type: "breakeven.commit",
|
|
51019
|
-
id: CREATE_KEY_FN$
|
|
51249
|
+
id: CREATE_KEY_FN$2(),
|
|
51020
51250
|
timestamp: data.timestamp,
|
|
51021
51251
|
backtest: data.backtest,
|
|
51022
51252
|
symbol: data.symbol,
|
|
@@ -51047,7 +51277,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51047
51277
|
if (data.action === "trailing-stop") {
|
|
51048
51278
|
return {
|
|
51049
51279
|
type: "trailing_stop.commit",
|
|
51050
|
-
id: CREATE_KEY_FN$
|
|
51280
|
+
id: CREATE_KEY_FN$2(),
|
|
51051
51281
|
timestamp: data.timestamp,
|
|
51052
51282
|
backtest: data.backtest,
|
|
51053
51283
|
symbol: data.symbol,
|
|
@@ -51079,7 +51309,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51079
51309
|
if (data.action === "trailing-take") {
|
|
51080
51310
|
return {
|
|
51081
51311
|
type: "trailing_take.commit",
|
|
51082
|
-
id: CREATE_KEY_FN$
|
|
51312
|
+
id: CREATE_KEY_FN$2(),
|
|
51083
51313
|
timestamp: data.timestamp,
|
|
51084
51314
|
backtest: data.backtest,
|
|
51085
51315
|
symbol: data.symbol,
|
|
@@ -51111,7 +51341,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51111
51341
|
if (data.action === "activate-scheduled") {
|
|
51112
51342
|
return {
|
|
51113
51343
|
type: "activate_scheduled.commit",
|
|
51114
|
-
id: CREATE_KEY_FN$
|
|
51344
|
+
id: CREATE_KEY_FN$2(),
|
|
51115
51345
|
timestamp: data.timestamp,
|
|
51116
51346
|
backtest: data.backtest,
|
|
51117
51347
|
symbol: data.symbol,
|
|
@@ -51143,7 +51373,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51143
51373
|
if (data.action === "average-buy") {
|
|
51144
51374
|
return {
|
|
51145
51375
|
type: "average_buy.commit",
|
|
51146
|
-
id: CREATE_KEY_FN$
|
|
51376
|
+
id: CREATE_KEY_FN$2(),
|
|
51147
51377
|
timestamp: data.timestamp,
|
|
51148
51378
|
backtest: data.backtest,
|
|
51149
51379
|
symbol: data.symbol,
|
|
@@ -51176,7 +51406,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51176
51406
|
if (data.action === "cancel-scheduled") {
|
|
51177
51407
|
return {
|
|
51178
51408
|
type: "cancel_scheduled.commit",
|
|
51179
|
-
id: CREATE_KEY_FN$
|
|
51409
|
+
id: CREATE_KEY_FN$2(),
|
|
51180
51410
|
timestamp: data.timestamp,
|
|
51181
51411
|
backtest: data.backtest,
|
|
51182
51412
|
symbol: data.symbol,
|
|
@@ -51199,7 +51429,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51199
51429
|
if (data.action === "close-pending") {
|
|
51200
51430
|
return {
|
|
51201
51431
|
type: "close_pending.commit",
|
|
51202
|
-
id: CREATE_KEY_FN$
|
|
51432
|
+
id: CREATE_KEY_FN$2(),
|
|
51203
51433
|
timestamp: data.timestamp,
|
|
51204
51434
|
backtest: data.backtest,
|
|
51205
51435
|
symbol: data.symbol,
|
|
@@ -51231,7 +51461,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51231
51461
|
if (data.action === "signal-open") {
|
|
51232
51462
|
return {
|
|
51233
51463
|
type: "signal_sync.open",
|
|
51234
|
-
id: CREATE_KEY_FN$
|
|
51464
|
+
id: CREATE_KEY_FN$2(),
|
|
51235
51465
|
timestamp: data.timestamp,
|
|
51236
51466
|
backtest: data.backtest,
|
|
51237
51467
|
symbol: data.symbol,
|
|
@@ -51263,7 +51493,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51263
51493
|
if (data.action === "signal-close") {
|
|
51264
51494
|
return {
|
|
51265
51495
|
type: "signal_sync.close",
|
|
51266
|
-
id: CREATE_KEY_FN$
|
|
51496
|
+
id: CREATE_KEY_FN$2(),
|
|
51267
51497
|
timestamp: data.timestamp,
|
|
51268
51498
|
backtest: data.backtest,
|
|
51269
51499
|
symbol: data.symbol,
|
|
@@ -51301,7 +51531,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51301
51531
|
*/
|
|
51302
51532
|
const CREATE_RISK_NOTIFICATION_FN = (data) => ({
|
|
51303
51533
|
type: "risk.rejection",
|
|
51304
|
-
id: CREATE_KEY_FN$
|
|
51534
|
+
id: CREATE_KEY_FN$2(),
|
|
51305
51535
|
timestamp: data.timestamp,
|
|
51306
51536
|
backtest: data.backtest,
|
|
51307
51537
|
symbol: data.symbol,
|
|
@@ -51327,7 +51557,7 @@ const CREATE_RISK_NOTIFICATION_FN = (data) => ({
|
|
|
51327
51557
|
*/
|
|
51328
51558
|
const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51329
51559
|
type: "error.info",
|
|
51330
|
-
id: CREATE_KEY_FN$
|
|
51560
|
+
id: CREATE_KEY_FN$2(),
|
|
51331
51561
|
error: errorData(error),
|
|
51332
51562
|
message: getErrorMessage(error),
|
|
51333
51563
|
backtest: false,
|
|
@@ -51339,7 +51569,7 @@ const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
|
|
|
51339
51569
|
*/
|
|
51340
51570
|
const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51341
51571
|
type: "error.critical",
|
|
51342
|
-
id: CREATE_KEY_FN$
|
|
51572
|
+
id: CREATE_KEY_FN$2(),
|
|
51343
51573
|
error: errorData(error),
|
|
51344
51574
|
message: getErrorMessage(error),
|
|
51345
51575
|
backtest: false,
|
|
@@ -51351,7 +51581,7 @@ const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
|
|
|
51351
51581
|
*/
|
|
51352
51582
|
const CREATE_VALIDATION_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51353
51583
|
type: "error.validation",
|
|
51354
|
-
id: CREATE_KEY_FN$
|
|
51584
|
+
id: CREATE_KEY_FN$2(),
|
|
51355
51585
|
error: errorData(error),
|
|
51356
51586
|
message: getErrorMessage(error),
|
|
51357
51587
|
backtest: false,
|
|
@@ -52780,7 +53010,7 @@ const NotificationLive = new NotificationLiveAdapter();
|
|
|
52780
53010
|
*/
|
|
52781
53011
|
const NotificationBacktest = new NotificationBacktestAdapter();
|
|
52782
53012
|
|
|
52783
|
-
const CACHE_METHOD_NAME_RUN = "
|
|
53013
|
+
const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
|
|
52784
53014
|
const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
|
|
52785
53015
|
const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
|
|
52786
53016
|
const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
|
|
@@ -52789,8 +53019,8 @@ const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
|
|
|
52789
53019
|
const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
|
|
52790
53020
|
const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
|
|
52791
53021
|
const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
|
|
52792
|
-
const MS_PER_MINUTE = 60000;
|
|
52793
|
-
const INTERVAL_MINUTES = {
|
|
53022
|
+
const MS_PER_MINUTE$1 = 60000;
|
|
53023
|
+
const INTERVAL_MINUTES$1 = {
|
|
52794
53024
|
"1m": 1,
|
|
52795
53025
|
"3m": 3,
|
|
52796
53026
|
"5m": 5,
|
|
@@ -52824,12 +53054,12 @@ const INTERVAL_MINUTES = {
|
|
|
52824
53054
|
* // Returns timestamp for 2025-10-01T01:00:00Z
|
|
52825
53055
|
* ```
|
|
52826
53056
|
*/
|
|
52827
|
-
const align = (timestamp, interval) => {
|
|
52828
|
-
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
53057
|
+
const align$1 = (timestamp, interval) => {
|
|
53058
|
+
const intervalMinutes = INTERVAL_MINUTES$1[interval];
|
|
52829
53059
|
if (!intervalMinutes) {
|
|
52830
53060
|
throw new Error(`align: unknown interval=${interval}`);
|
|
52831
53061
|
}
|
|
52832
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
53062
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
|
|
52833
53063
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
52834
53064
|
};
|
|
52835
53065
|
/**
|
|
@@ -52840,7 +53070,7 @@ const align = (timestamp, interval) => {
|
|
|
52840
53070
|
* @param backtest - Whether running in backtest mode
|
|
52841
53071
|
* @returns Cache key string
|
|
52842
53072
|
*/
|
|
52843
|
-
const CREATE_KEY_FN = (strategyName, exchangeName, frameName, backtest) => {
|
|
53073
|
+
const CREATE_KEY_FN$1 = (strategyName, exchangeName, frameName, backtest) => {
|
|
52844
53074
|
const parts = [strategyName, exchangeName];
|
|
52845
53075
|
if (frameName)
|
|
52846
53076
|
parts.push(frameName);
|
|
@@ -52864,16 +53094,16 @@ const NEVER_VALUE = Symbol("never");
|
|
|
52864
53094
|
*
|
|
52865
53095
|
* @example
|
|
52866
53096
|
* ```typescript
|
|
52867
|
-
* const instance = new
|
|
53097
|
+
* const instance = new CacheFnInstance(myExpensiveFunction, "1h");
|
|
52868
53098
|
* const result = instance.run(arg1, arg2); // Computed
|
|
52869
53099
|
* const result2 = instance.run(arg1, arg2); // Cached (within same hour)
|
|
52870
53100
|
* // After 1 hour passes
|
|
52871
53101
|
* const result3 = instance.run(arg1, arg2); // Recomputed
|
|
52872
53102
|
* ```
|
|
52873
53103
|
*/
|
|
52874
|
-
class
|
|
53104
|
+
class CacheFnInstance {
|
|
52875
53105
|
/**
|
|
52876
|
-
* Creates a new
|
|
53106
|
+
* Creates a new CacheFnInstance for a specific function and interval.
|
|
52877
53107
|
*
|
|
52878
53108
|
* @param fn - Function to cache
|
|
52879
53109
|
* @param interval - Candle interval for cache invalidation (e.g., "1m", "1h")
|
|
@@ -52910,7 +53140,7 @@ class CacheInstance {
|
|
|
52910
53140
|
*
|
|
52911
53141
|
* @example
|
|
52912
53142
|
* ```typescript
|
|
52913
|
-
* const instance = new
|
|
53143
|
+
* const instance = new CacheFnInstance(calculateIndicator, "15m");
|
|
52914
53144
|
* const result = instance.run("BTCUSDT", 100);
|
|
52915
53145
|
* console.log(result.value); // Calculated value
|
|
52916
53146
|
* console.log(result.when); // Cache timestamp
|
|
@@ -52918,26 +53148,26 @@ class CacheInstance {
|
|
|
52918
53148
|
*/
|
|
52919
53149
|
this.run = (...args) => {
|
|
52920
53150
|
backtest.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
|
|
52921
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
53151
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
52922
53152
|
{
|
|
52923
53153
|
if (!MethodContextService.hasContext()) {
|
|
52924
|
-
throw new Error("
|
|
53154
|
+
throw new Error("CacheFnInstance run requires method context");
|
|
52925
53155
|
}
|
|
52926
53156
|
if (!ExecutionContextService.hasContext()) {
|
|
52927
|
-
throw new Error("
|
|
53157
|
+
throw new Error("CacheFnInstance run requires execution context");
|
|
52928
53158
|
}
|
|
52929
53159
|
if (!step) {
|
|
52930
|
-
throw new Error(`
|
|
53160
|
+
throw new Error(`CacheFnInstance unknown cache ttl interval=${this.interval}`);
|
|
52931
53161
|
}
|
|
52932
53162
|
}
|
|
52933
|
-
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53163
|
+
const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
52934
53164
|
const argKey = String(this.key(args));
|
|
52935
53165
|
const key = `${contextKey}:${argKey}`;
|
|
52936
53166
|
const currentWhen = backtest.executionContextService.context.when;
|
|
52937
53167
|
const cached = this._cacheMap.get(key);
|
|
52938
53168
|
if (cached) {
|
|
52939
|
-
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
52940
|
-
const cachedAligned = align(cached.when.getTime(), this.interval);
|
|
53169
|
+
const currentAligned = align$1(currentWhen.getTime(), this.interval);
|
|
53170
|
+
const cachedAligned = align$1(cached.when.getTime(), this.interval);
|
|
52941
53171
|
if (currentAligned === cachedAligned) {
|
|
52942
53172
|
return cached;
|
|
52943
53173
|
}
|
|
@@ -52960,7 +53190,7 @@ class CacheInstance {
|
|
|
52960
53190
|
*
|
|
52961
53191
|
* @example
|
|
52962
53192
|
* ```typescript
|
|
52963
|
-
* const instance = new
|
|
53193
|
+
* const instance = new CacheFnInstance(calculateIndicator, "1h");
|
|
52964
53194
|
* const result1 = instance.run("BTCUSDT", 14); // Computed
|
|
52965
53195
|
* const result2 = instance.run("BTCUSDT", 14); // Cached
|
|
52966
53196
|
*
|
|
@@ -52970,7 +53200,7 @@ class CacheInstance {
|
|
|
52970
53200
|
* ```
|
|
52971
53201
|
*/
|
|
52972
53202
|
this.clear = () => {
|
|
52973
|
-
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53203
|
+
const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
52974
53204
|
const prefix = `${contextKey}:`;
|
|
52975
53205
|
for (const key of this._cacheMap.keys()) {
|
|
52976
53206
|
if (key.startsWith(prefix)) {
|
|
@@ -52990,7 +53220,7 @@ class CacheInstance {
|
|
|
52990
53220
|
*
|
|
52991
53221
|
* @example
|
|
52992
53222
|
* ```typescript
|
|
52993
|
-
* const instance = new
|
|
53223
|
+
* const instance = new CacheFnInstance(calculateIndicator, "1h");
|
|
52994
53224
|
* instance.run("BTCUSDT", 14); // Cached at 10:00
|
|
52995
53225
|
* instance.run("ETHUSDT", 14); // Cached at 10:00
|
|
52996
53226
|
* // Time passes to 11:00
|
|
@@ -52999,10 +53229,10 @@ class CacheInstance {
|
|
|
52999
53229
|
*/
|
|
53000
53230
|
this.gc = () => {
|
|
53001
53231
|
const currentWhen = backtest.executionContextService.context.when;
|
|
53002
|
-
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
53232
|
+
const currentAligned = align$1(currentWhen.getTime(), this.interval);
|
|
53003
53233
|
let removed = 0;
|
|
53004
53234
|
for (const [key, cached] of this._cacheMap.entries()) {
|
|
53005
|
-
const cachedAligned = align(cached.when.getTime(), this.interval);
|
|
53235
|
+
const cachedAligned = align$1(cached.when.getTime(), this.interval);
|
|
53006
53236
|
if (currentAligned !== cachedAligned) {
|
|
53007
53237
|
this._cacheMap.delete(key);
|
|
53008
53238
|
removed++;
|
|
@@ -53077,7 +53307,7 @@ class CacheFileInstance {
|
|
|
53077
53307
|
*/
|
|
53078
53308
|
this.run = async (...args) => {
|
|
53079
53309
|
backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
53080
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
53310
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
53081
53311
|
{
|
|
53082
53312
|
if (!MethodContextService.hasContext()) {
|
|
53083
53313
|
throw new Error("CacheFileInstance run requires method context");
|
|
@@ -53091,7 +53321,7 @@ class CacheFileInstance {
|
|
|
53091
53321
|
}
|
|
53092
53322
|
const [symbol, ...rest] = args;
|
|
53093
53323
|
const { when } = backtest.executionContextService.context;
|
|
53094
|
-
const alignedTs = align(when.getTime(), this.interval);
|
|
53324
|
+
const alignedTs = align$1(when.getTime(), this.interval);
|
|
53095
53325
|
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53096
53326
|
const entityKey = this.key([symbol, alignedTs, ...rest]);
|
|
53097
53327
|
const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
|
|
@@ -53099,9 +53329,19 @@ class CacheFileInstance {
|
|
|
53099
53329
|
return cached.data;
|
|
53100
53330
|
}
|
|
53101
53331
|
const result = await this.fn.call(null, ...args);
|
|
53102
|
-
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result }, bucket, entityKey);
|
|
53332
|
+
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
53103
53333
|
return result;
|
|
53104
53334
|
};
|
|
53335
|
+
/**
|
|
53336
|
+
* Soft-delete all persisted records for this instance's bucket.
|
|
53337
|
+
* After this call the next `run()` will recompute and re-cache the value.
|
|
53338
|
+
*/
|
|
53339
|
+
this.clear = async () => {
|
|
53340
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53341
|
+
for await (const key of PersistMeasureAdapter.listMeasureData(bucket)) {
|
|
53342
|
+
await PersistMeasureAdapter.removeMeasureData(bucket, key);
|
|
53343
|
+
}
|
|
53344
|
+
};
|
|
53105
53345
|
this.index = CacheFileInstance.createIndex();
|
|
53106
53346
|
}
|
|
53107
53347
|
}
|
|
@@ -53125,10 +53365,10 @@ CacheFileInstance._indexCounter = 0;
|
|
|
53125
53365
|
class CacheUtils {
|
|
53126
53366
|
constructor() {
|
|
53127
53367
|
/**
|
|
53128
|
-
* Memoized function to get or create
|
|
53368
|
+
* Memoized function to get or create CacheFnInstance for a function.
|
|
53129
53369
|
* Each function gets its own isolated cache instance.
|
|
53130
53370
|
*/
|
|
53131
|
-
this._getFnInstance = memoize(([run]) => run, (run, interval, key) => new
|
|
53371
|
+
this._getFnInstance = memoize(([run]) => run, (run, interval, key) => new CacheFnInstance(run, interval, key));
|
|
53132
53372
|
/**
|
|
53133
53373
|
* Memoized function to get or create CacheFileInstance for an async function.
|
|
53134
53374
|
* Each function gets its own isolated file-cache instance.
|
|
@@ -53237,31 +53477,34 @@ class CacheUtils {
|
|
|
53237
53477
|
*/
|
|
53238
53478
|
this.file = (run, context) => {
|
|
53239
53479
|
backtest.loggerService.info(CACHE_METHOD_NAME_FILE, { context });
|
|
53480
|
+
{
|
|
53481
|
+
this._getFileInstance(run, context.interval, context.name, context.key);
|
|
53482
|
+
}
|
|
53240
53483
|
const wrappedFn = (...args) => {
|
|
53241
53484
|
const instance = this._getFileInstance(run, context.interval, context.name, context.key);
|
|
53242
53485
|
return instance.run(...args);
|
|
53243
53486
|
};
|
|
53244
|
-
wrappedFn.clear = () => {
|
|
53487
|
+
wrappedFn.clear = async () => {
|
|
53245
53488
|
backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
|
|
53246
|
-
this._getFileInstance.
|
|
53489
|
+
await this._getFileInstance.get(run)?.clear();
|
|
53247
53490
|
};
|
|
53248
53491
|
return wrappedFn;
|
|
53249
53492
|
};
|
|
53250
53493
|
/**
|
|
53251
|
-
* Dispose (remove) the memoized
|
|
53494
|
+
* Dispose (remove) the memoized CacheFnInstance for a specific function.
|
|
53252
53495
|
*
|
|
53253
|
-
* Removes the
|
|
53496
|
+
* Removes the CacheFnInstance from the internal memoization cache, discarding all cached
|
|
53254
53497
|
* results across all contexts (all strategy/exchange/mode combinations) for that function.
|
|
53255
|
-
* The next call to the wrapped function will create a fresh
|
|
53498
|
+
* The next call to the wrapped function will create a fresh CacheFnInstance.
|
|
53256
53499
|
*
|
|
53257
53500
|
* @template T - Function type
|
|
53258
|
-
* @param run - Function whose
|
|
53501
|
+
* @param run - Function whose CacheFnInstance should be disposed.
|
|
53259
53502
|
*
|
|
53260
53503
|
* @example
|
|
53261
53504
|
* ```typescript
|
|
53262
53505
|
* const cachedFn = Cache.fn(calculateIndicator, { interval: "1h" });
|
|
53263
53506
|
*
|
|
53264
|
-
* // Dispose
|
|
53507
|
+
* // Dispose CacheFnInstance for a specific function
|
|
53265
53508
|
* Cache.dispose(calculateIndicator);
|
|
53266
53509
|
* ```
|
|
53267
53510
|
*/
|
|
@@ -53275,7 +53518,7 @@ class CacheUtils {
|
|
|
53275
53518
|
}
|
|
53276
53519
|
};
|
|
53277
53520
|
/**
|
|
53278
|
-
* Clears all memoized
|
|
53521
|
+
* Clears all memoized CacheFnInstance and CacheFileInstance objects.
|
|
53279
53522
|
* Call this when process.cwd() changes between strategy iterations
|
|
53280
53523
|
* so new instances are created with the updated base path.
|
|
53281
53524
|
*/
|
|
@@ -53305,6 +53548,426 @@ class CacheUtils {
|
|
|
53305
53548
|
*/
|
|
53306
53549
|
const Cache = new CacheUtils();
|
|
53307
53550
|
|
|
53551
|
+
const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
|
|
53552
|
+
const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
|
|
53553
|
+
const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
|
|
53554
|
+
const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
|
|
53555
|
+
const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
|
|
53556
|
+
const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
|
|
53557
|
+
const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
|
|
53558
|
+
const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
|
|
53559
|
+
const MS_PER_MINUTE = 60000;
|
|
53560
|
+
const INTERVAL_MINUTES = {
|
|
53561
|
+
"1m": 1,
|
|
53562
|
+
"3m": 3,
|
|
53563
|
+
"5m": 5,
|
|
53564
|
+
"15m": 15,
|
|
53565
|
+
"30m": 30,
|
|
53566
|
+
"1h": 60,
|
|
53567
|
+
"2h": 120,
|
|
53568
|
+
"4h": 240,
|
|
53569
|
+
"6h": 360,
|
|
53570
|
+
"8h": 480,
|
|
53571
|
+
"1d": 1440,
|
|
53572
|
+
"1w": 10080,
|
|
53573
|
+
};
|
|
53574
|
+
/**
|
|
53575
|
+
* Aligns timestamp down to the nearest interval boundary.
|
|
53576
|
+
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
53577
|
+
*
|
|
53578
|
+
* @param timestamp - Timestamp in milliseconds
|
|
53579
|
+
* @param interval - Candle interval
|
|
53580
|
+
* @returns Aligned timestamp rounded down to interval boundary
|
|
53581
|
+
* @throws Error if interval is unknown
|
|
53582
|
+
*
|
|
53583
|
+
* @example
|
|
53584
|
+
* ```typescript
|
|
53585
|
+
* // Align to 15-minute boundary
|
|
53586
|
+
* const aligned = align(new Date("2025-10-01T00:35:00Z").getTime(), "15m");
|
|
53587
|
+
* // Returns timestamp for 2025-10-01T00:30:00Z
|
|
53588
|
+
* ```
|
|
53589
|
+
*/
|
|
53590
|
+
const align = (timestamp, interval) => {
|
|
53591
|
+
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
53592
|
+
if (!intervalMinutes) {
|
|
53593
|
+
throw new Error(`align: unknown interval=${interval}`);
|
|
53594
|
+
}
|
|
53595
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
53596
|
+
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
53597
|
+
};
|
|
53598
|
+
/**
|
|
53599
|
+
* Build a context key string from strategy name, exchange name, frame name, and execution mode.
|
|
53600
|
+
*
|
|
53601
|
+
* @param strategyName - Name of the strategy
|
|
53602
|
+
* @param exchangeName - Name of the exchange
|
|
53603
|
+
* @param frameName - Name of the backtest frame (omitted in live mode)
|
|
53604
|
+
* @param isBacktest - Whether running in backtest mode
|
|
53605
|
+
* @returns Context key string
|
|
53606
|
+
*/
|
|
53607
|
+
const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
|
|
53608
|
+
const parts = [strategyName, exchangeName];
|
|
53609
|
+
if (frameName)
|
|
53610
|
+
parts.push(frameName);
|
|
53611
|
+
parts.push(isBacktest ? "backtest" : "live");
|
|
53612
|
+
return parts.join(":");
|
|
53613
|
+
};
|
|
53614
|
+
/**
|
|
53615
|
+
* Instance class for firing a function exactly once per interval boundary.
|
|
53616
|
+
*
|
|
53617
|
+
* On the first call within a new interval the wrapped function is invoked and
|
|
53618
|
+
* its result is returned. Every subsequent call within the same interval returns
|
|
53619
|
+
* `null` without invoking the function again.
|
|
53620
|
+
* If the function itself returns `null`, the interval countdown does not start —
|
|
53621
|
+
* the next call will retry the function.
|
|
53622
|
+
*
|
|
53623
|
+
* State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
|
|
53624
|
+
*
|
|
53625
|
+
* @example
|
|
53626
|
+
* ```typescript
|
|
53627
|
+
* const instance = new IntervalFnInstance(mySignalFn, "1h");
|
|
53628
|
+
* await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called)
|
|
53629
|
+
* await instance.run("BTCUSDT"); // → null (skipped, same interval)
|
|
53630
|
+
* // After 1 hour passes:
|
|
53631
|
+
* await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called again)
|
|
53632
|
+
* ```
|
|
53633
|
+
*/
|
|
53634
|
+
class IntervalFnInstance {
|
|
53635
|
+
/**
|
|
53636
|
+
* Creates a new IntervalFnInstance.
|
|
53637
|
+
*
|
|
53638
|
+
* @param fn - Signal function to fire once per interval
|
|
53639
|
+
* @param interval - Candle interval that controls the firing boundary
|
|
53640
|
+
*/
|
|
53641
|
+
constructor(fn, interval) {
|
|
53642
|
+
this.fn = fn;
|
|
53643
|
+
this.interval = interval;
|
|
53644
|
+
/** Stores the last aligned timestamp per context+symbol key. */
|
|
53645
|
+
this._stateMap = new Map();
|
|
53646
|
+
/**
|
|
53647
|
+
* Execute the signal function with once-per-interval enforcement.
|
|
53648
|
+
*
|
|
53649
|
+
* Algorithm:
|
|
53650
|
+
* 1. Align the current execution context `when` to the interval boundary.
|
|
53651
|
+
* 2. If the stored aligned timestamp for this context+symbol equals the current one → return `null`.
|
|
53652
|
+
* 3. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
|
|
53653
|
+
* the signal. If it returns `null`, leave state unchanged so the next call retries.
|
|
53654
|
+
*
|
|
53655
|
+
* Requires active method context and execution context.
|
|
53656
|
+
*
|
|
53657
|
+
* @param symbol - Trading pair symbol (e.g. "BTCUSDT")
|
|
53658
|
+
* @returns The signal returned by `fn` on the first non-null fire, `null` on all subsequent calls
|
|
53659
|
+
* within the same interval or when `fn` itself returned `null`
|
|
53660
|
+
* @throws Error if method context, execution context, or interval is missing
|
|
53661
|
+
*/
|
|
53662
|
+
this.run = async (symbol) => {
|
|
53663
|
+
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { symbol });
|
|
53664
|
+
const step = INTERVAL_MINUTES[this.interval];
|
|
53665
|
+
{
|
|
53666
|
+
if (!MethodContextService.hasContext()) {
|
|
53667
|
+
throw new Error("IntervalFnInstance run requires method context");
|
|
53668
|
+
}
|
|
53669
|
+
if (!ExecutionContextService.hasContext()) {
|
|
53670
|
+
throw new Error("IntervalFnInstance run requires execution context");
|
|
53671
|
+
}
|
|
53672
|
+
if (!step) {
|
|
53673
|
+
throw new Error(`IntervalFnInstance unknown interval=${this.interval}`);
|
|
53674
|
+
}
|
|
53675
|
+
}
|
|
53676
|
+
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53677
|
+
const key = `${contextKey}:${symbol}`;
|
|
53678
|
+
const currentWhen = backtest.executionContextService.context.when;
|
|
53679
|
+
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
53680
|
+
if (this._stateMap.get(key) === currentAligned) {
|
|
53681
|
+
return null;
|
|
53682
|
+
}
|
|
53683
|
+
const result = await this.fn(symbol, currentWhen);
|
|
53684
|
+
if (result !== null) {
|
|
53685
|
+
this._stateMap.set(key, currentAligned);
|
|
53686
|
+
}
|
|
53687
|
+
return result;
|
|
53688
|
+
};
|
|
53689
|
+
/**
|
|
53690
|
+
* Clear fired-interval state for the current execution context.
|
|
53691
|
+
*
|
|
53692
|
+
* Removes all entries for the current strategy/exchange/frame/mode combination
|
|
53693
|
+
* from this instance's state map. The next `run()` call will invoke the function again.
|
|
53694
|
+
*
|
|
53695
|
+
* Requires active method context and execution context.
|
|
53696
|
+
*/
|
|
53697
|
+
this.clear = () => {
|
|
53698
|
+
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53699
|
+
const prefix = `${contextKey}:`;
|
|
53700
|
+
for (const key of this._stateMap.keys()) {
|
|
53701
|
+
if (key.startsWith(prefix)) {
|
|
53702
|
+
this._stateMap.delete(key);
|
|
53703
|
+
}
|
|
53704
|
+
}
|
|
53705
|
+
};
|
|
53706
|
+
}
|
|
53707
|
+
}
|
|
53708
|
+
/**
|
|
53709
|
+
* Instance class for firing an async function exactly once per interval boundary,
|
|
53710
|
+
* with the fired state persisted to disk via `PersistIntervalAdapter`.
|
|
53711
|
+
*
|
|
53712
|
+
* On the first call within a new interval the wrapped function is invoked.
|
|
53713
|
+
* If it returns a non-null signal, that result is written to disk and returned.
|
|
53714
|
+
* Every subsequent call within the same interval returns `null` (record exists on disk).
|
|
53715
|
+
* If the function returns `null`, nothing is written and the next call retries.
|
|
53716
|
+
*
|
|
53717
|
+
* Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
|
|
53718
|
+
*
|
|
53719
|
+
* @template T - Async function type: `(symbol: string, ...args) => Promise<ISignalIntervalDto | null>`
|
|
53720
|
+
*
|
|
53721
|
+
* @example
|
|
53722
|
+
* ```typescript
|
|
53723
|
+
* const instance = new IntervalFileInstance(fetchSignal, "1h", "mySignal");
|
|
53724
|
+
* await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called, result written to disk)
|
|
53725
|
+
* await instance.run("BTCUSDT"); // → null (record exists, already fired)
|
|
53726
|
+
* ```
|
|
53727
|
+
*/
|
|
53728
|
+
class IntervalFileInstance {
|
|
53729
|
+
/**
|
|
53730
|
+
* Allocates a new unique index. Called once in the constructor to give each
|
|
53731
|
+
* IntervalFileInstance its own namespace in the persistent key space.
|
|
53732
|
+
*/
|
|
53733
|
+
static createIndex() {
|
|
53734
|
+
return IntervalFileInstance._indexCounter++;
|
|
53735
|
+
}
|
|
53736
|
+
/**
|
|
53737
|
+
* Resets the index counter to zero.
|
|
53738
|
+
* Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
|
|
53739
|
+
*/
|
|
53740
|
+
static clearCounter() {
|
|
53741
|
+
IntervalFileInstance._indexCounter = 0;
|
|
53742
|
+
}
|
|
53743
|
+
/**
|
|
53744
|
+
* Creates a new IntervalFileInstance.
|
|
53745
|
+
*
|
|
53746
|
+
* @param fn - Async signal function to fire once per interval
|
|
53747
|
+
* @param interval - Candle interval that controls the firing boundary
|
|
53748
|
+
* @param name - Human-readable bucket name used as the directory prefix
|
|
53749
|
+
*/
|
|
53750
|
+
constructor(fn, interval, name) {
|
|
53751
|
+
this.fn = fn;
|
|
53752
|
+
this.interval = interval;
|
|
53753
|
+
this.name = name;
|
|
53754
|
+
/**
|
|
53755
|
+
* Execute the async signal function with persistent once-per-interval enforcement.
|
|
53756
|
+
*
|
|
53757
|
+
* Algorithm:
|
|
53758
|
+
* 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
|
|
53759
|
+
* 2. Align execution context `when` to interval boundary → `alignedTs`.
|
|
53760
|
+
* 3. Build entity key = `${symbol}_${alignedTs}`.
|
|
53761
|
+
* 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
|
|
53762
|
+
* 5. On hit — return `null` (interval already fired).
|
|
53763
|
+
* 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
|
|
53764
|
+
*
|
|
53765
|
+
* Requires active method context and execution context.
|
|
53766
|
+
*
|
|
53767
|
+
* @param args - Arguments forwarded to the wrapped function (first must be `symbol: string`)
|
|
53768
|
+
* @returns The signal on the first non-null fire, `null` if already fired this interval
|
|
53769
|
+
* or if `fn` itself returned `null`
|
|
53770
|
+
* @throws Error if method context, execution context, or interval is missing
|
|
53771
|
+
*/
|
|
53772
|
+
this.run = async (...args) => {
|
|
53773
|
+
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
53774
|
+
const step = INTERVAL_MINUTES[this.interval];
|
|
53775
|
+
{
|
|
53776
|
+
if (!MethodContextService.hasContext()) {
|
|
53777
|
+
throw new Error("IntervalFileInstance run requires method context");
|
|
53778
|
+
}
|
|
53779
|
+
if (!ExecutionContextService.hasContext()) {
|
|
53780
|
+
throw new Error("IntervalFileInstance run requires execution context");
|
|
53781
|
+
}
|
|
53782
|
+
if (!step) {
|
|
53783
|
+
throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
|
|
53784
|
+
}
|
|
53785
|
+
}
|
|
53786
|
+
const [symbol] = args;
|
|
53787
|
+
const { when } = backtest.executionContextService.context;
|
|
53788
|
+
const alignedTs = align(when.getTime(), this.interval);
|
|
53789
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53790
|
+
const entityKey = `${symbol}_${alignedTs}`;
|
|
53791
|
+
const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
|
|
53792
|
+
if (cached !== null) {
|
|
53793
|
+
return null;
|
|
53794
|
+
}
|
|
53795
|
+
const result = await this.fn.call(null, ...args);
|
|
53796
|
+
if (result !== null) {
|
|
53797
|
+
await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
53798
|
+
}
|
|
53799
|
+
return result;
|
|
53800
|
+
};
|
|
53801
|
+
/**
|
|
53802
|
+
* Soft-delete all persisted records for this instance's bucket.
|
|
53803
|
+
* After this call the function will fire again on the next `run()`.
|
|
53804
|
+
*/
|
|
53805
|
+
this.clear = async () => {
|
|
53806
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53807
|
+
for await (const key of PersistIntervalAdapter.listIntervalData(bucket)) {
|
|
53808
|
+
await PersistIntervalAdapter.removeIntervalData(bucket, key);
|
|
53809
|
+
}
|
|
53810
|
+
};
|
|
53811
|
+
this.index = IntervalFileInstance.createIndex();
|
|
53812
|
+
}
|
|
53813
|
+
}
|
|
53814
|
+
/** Global counter — incremented once per IntervalFileInstance construction. */
|
|
53815
|
+
IntervalFileInstance._indexCounter = 0;
|
|
53816
|
+
/**
|
|
53817
|
+
* Utility class for wrapping signal functions with once-per-interval firing.
|
|
53818
|
+
* Provides two modes: in-memory (`fn`) and persistent file-based (`file`).
|
|
53819
|
+
* Exported as singleton instance `Interval` for convenient usage.
|
|
53820
|
+
*
|
|
53821
|
+
* @example
|
|
53822
|
+
* ```typescript
|
|
53823
|
+
* import { Interval } from "./classes/Interval";
|
|
53824
|
+
*
|
|
53825
|
+
* const fireOncePerHour = Interval.fn(mySignalFn, { interval: "1h" });
|
|
53826
|
+
* await fireOncePerHour("BTCUSDT", when); // fn called — returns its result
|
|
53827
|
+
* await fireOncePerHour("BTCUSDT", when); // returns null (same interval)
|
|
53828
|
+
* ```
|
|
53829
|
+
*/
|
|
53830
|
+
class IntervalUtils {
|
|
53831
|
+
constructor() {
|
|
53832
|
+
/**
|
|
53833
|
+
* Memoized factory to get or create an `IntervalFnInstance` for a function.
|
|
53834
|
+
* Each function reference gets its own isolated instance.
|
|
53835
|
+
*/
|
|
53836
|
+
this._getInstance = memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
|
|
53837
|
+
/**
|
|
53838
|
+
* Memoized factory to get or create an `IntervalFileInstance` for an async function.
|
|
53839
|
+
* Each function reference gets its own isolated persistent instance.
|
|
53840
|
+
*/
|
|
53841
|
+
this._getFileInstance = memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
|
|
53842
|
+
/**
|
|
53843
|
+
* Wrap a signal function with in-memory once-per-interval firing.
|
|
53844
|
+
*
|
|
53845
|
+
* Returns a wrapped version of the function that fires at most once per interval boundary.
|
|
53846
|
+
* If the function returns `null`, the countdown does not start and the next call retries.
|
|
53847
|
+
*
|
|
53848
|
+
* The `run` function reference is used as the memoization key for the underlying
|
|
53849
|
+
* `IntervalFnInstance`, so each unique function reference gets its own isolated instance.
|
|
53850
|
+
*
|
|
53851
|
+
* @param run - Signal function to wrap
|
|
53852
|
+
* @param context.interval - Candle interval that controls the firing boundary
|
|
53853
|
+
* @returns Wrapped function with the same signature as `TIntervalFn`, plus a `clear()` method
|
|
53854
|
+
*
|
|
53855
|
+
* @example
|
|
53856
|
+
* ```typescript
|
|
53857
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
|
|
53858
|
+
*
|
|
53859
|
+
* await fireOnce("BTCUSDT", when); // → signal or null (fn called)
|
|
53860
|
+
* await fireOnce("BTCUSDT", when); // → null (same interval, skipped)
|
|
53861
|
+
* ```
|
|
53862
|
+
*/
|
|
53863
|
+
this.fn = (run, context) => {
|
|
53864
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
|
|
53865
|
+
const wrappedFn = (symbol, _when) => {
|
|
53866
|
+
const instance = this._getInstance(run, context.interval);
|
|
53867
|
+
return instance.run(symbol);
|
|
53868
|
+
};
|
|
53869
|
+
wrappedFn.clear = () => {
|
|
53870
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
|
|
53871
|
+
if (!MethodContextService.hasContext()) {
|
|
53872
|
+
backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
|
|
53873
|
+
return;
|
|
53874
|
+
}
|
|
53875
|
+
if (!ExecutionContextService.hasContext()) {
|
|
53876
|
+
backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
|
|
53877
|
+
return;
|
|
53878
|
+
}
|
|
53879
|
+
this._getInstance.get(run)?.clear();
|
|
53880
|
+
};
|
|
53881
|
+
return wrappedFn;
|
|
53882
|
+
};
|
|
53883
|
+
/**
|
|
53884
|
+
* Wrap an async signal function with persistent file-based once-per-interval firing.
|
|
53885
|
+
*
|
|
53886
|
+
* Returns a wrapped version of the function that reads from disk on hit (returns `null`)
|
|
53887
|
+
* and writes the fired signal to disk on the first successful fire.
|
|
53888
|
+
* Fired state survives process restarts.
|
|
53889
|
+
*
|
|
53890
|
+
* The `run` function reference is used as the memoization key for the underlying
|
|
53891
|
+
* `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
|
|
53892
|
+
*
|
|
53893
|
+
* @template T - Async function type to wrap
|
|
53894
|
+
* @param run - Async signal function to wrap with persistent once-per-interval firing
|
|
53895
|
+
* @param context.interval - Candle interval that controls the firing boundary
|
|
53896
|
+
* @param context.name - Human-readable bucket name; becomes the directory prefix
|
|
53897
|
+
* @returns Wrapped function with the same signature as `T`, plus an async `clear()` method
|
|
53898
|
+
* that deletes persisted records from disk and disposes the memoized instance
|
|
53899
|
+
*
|
|
53900
|
+
* @example
|
|
53901
|
+
* ```typescript
|
|
53902
|
+
* const fetchSignal = async (symbol: string, period: number) => { ... };
|
|
53903
|
+
* const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
|
|
53904
|
+
* await fireOnce.clear(); // delete disk records so the function fires again next call
|
|
53905
|
+
* ```
|
|
53906
|
+
*/
|
|
53907
|
+
this.file = (run, context) => {
|
|
53908
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
|
|
53909
|
+
{
|
|
53910
|
+
this._getFileInstance(run, context.interval, context.name);
|
|
53911
|
+
}
|
|
53912
|
+
const wrappedFn = (...args) => {
|
|
53913
|
+
const instance = this._getFileInstance(run, context.interval, context.name);
|
|
53914
|
+
return instance.run(...args);
|
|
53915
|
+
};
|
|
53916
|
+
wrappedFn.clear = async () => {
|
|
53917
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
|
|
53918
|
+
await this._getFileInstance.get(run)?.clear();
|
|
53919
|
+
};
|
|
53920
|
+
return wrappedFn;
|
|
53921
|
+
};
|
|
53922
|
+
/**
|
|
53923
|
+
* Dispose (remove) the memoized `IntervalFnInstance` for a specific function.
|
|
53924
|
+
*
|
|
53925
|
+
* Removes the instance from the internal memoization cache, discarding all in-memory
|
|
53926
|
+
* fired-interval state across all contexts for that function.
|
|
53927
|
+
* The next call to the wrapped function will create a fresh `IntervalFnInstance`.
|
|
53928
|
+
*
|
|
53929
|
+
* @param run - Function whose `IntervalFnInstance` should be disposed
|
|
53930
|
+
*
|
|
53931
|
+
* @example
|
|
53932
|
+
* ```typescript
|
|
53933
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
|
|
53934
|
+
* Interval.dispose(mySignalFn);
|
|
53935
|
+
* ```
|
|
53936
|
+
*/
|
|
53937
|
+
this.dispose = (run) => {
|
|
53938
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
|
|
53939
|
+
this._getInstance.clear(run);
|
|
53940
|
+
};
|
|
53941
|
+
/**
|
|
53942
|
+
* Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects and
|
|
53943
|
+
* resets the `IntervalFileInstance` index counter.
|
|
53944
|
+
* Call this when `process.cwd()` changes between strategy iterations
|
|
53945
|
+
* so new instances are created with the updated base path.
|
|
53946
|
+
*/
|
|
53947
|
+
this.clear = () => {
|
|
53948
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
|
|
53949
|
+
this._getInstance.clear();
|
|
53950
|
+
this._getFileInstance.clear();
|
|
53951
|
+
IntervalFileInstance.clearCounter();
|
|
53952
|
+
};
|
|
53953
|
+
}
|
|
53954
|
+
}
|
|
53955
|
+
/**
|
|
53956
|
+
* Singleton instance of `IntervalUtils` for convenient once-per-interval signal firing.
|
|
53957
|
+
*
|
|
53958
|
+
* @example
|
|
53959
|
+
* ```typescript
|
|
53960
|
+
* import { Interval } from "./classes/Interval";
|
|
53961
|
+
*
|
|
53962
|
+
* // In-memory: fires once per hour, resets on process restart
|
|
53963
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
|
|
53964
|
+
*
|
|
53965
|
+
* // Persistent: fired state survives restarts
|
|
53966
|
+
* const fireOncePersist = Interval.file(mySignalFn, { interval: "1h", name: "mySignal" });
|
|
53967
|
+
* ```
|
|
53968
|
+
*/
|
|
53969
|
+
const Interval = new IntervalUtils();
|
|
53970
|
+
|
|
53308
53971
|
const BREAKEVEN_METHOD_NAME_GET_DATA = "BreakevenUtils.getData";
|
|
53309
53972
|
const BREAKEVEN_METHOD_NAME_GET_REPORT = "BreakevenUtils.getReport";
|
|
53310
53973
|
const BREAKEVEN_METHOD_NAME_DUMP = "BreakevenUtils.dump";
|
|
@@ -54419,4 +55082,4 @@ const validateSignal = (signal, currentPrice) => {
|
|
|
54419
55082
|
return !errors.length;
|
|
54420
55083
|
};
|
|
54421
55084
|
|
|
54422
|
-
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|
|
55085
|
+
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|