backtest-kit 6.8.0 → 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 +856 -188
- package/build/index.mjs +852 -189
- package/package.json +1 -1
- package/types.d.ts +420 -9
package/build/index.cjs
CHANGED
|
@@ -908,7 +908,7 @@ const LOGGER_SERVICE$7 = new LoggerService();
|
|
|
908
908
|
/** Symbol key for the singleshot waitForInit function on PersistBase instances. */
|
|
909
909
|
const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
|
|
910
910
|
// Calculate step in milliseconds for candle close time validation
|
|
911
|
-
const INTERVAL_MINUTES$
|
|
911
|
+
const INTERVAL_MINUTES$9 = {
|
|
912
912
|
"1m": 1,
|
|
913
913
|
"3m": 3,
|
|
914
914
|
"5m": 5,
|
|
@@ -922,7 +922,7 @@ const INTERVAL_MINUTES$8 = {
|
|
|
922
922
|
"1d": 1440,
|
|
923
923
|
"1w": 10080,
|
|
924
924
|
};
|
|
925
|
-
const MS_PER_MINUTE$
|
|
925
|
+
const MS_PER_MINUTE$7 = 60000;
|
|
926
926
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
|
|
927
927
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
|
|
928
928
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistSignalUtils.writeSignalData";
|
|
@@ -981,8 +981,18 @@ const PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA = "PersistMeasureUtils.readMea
|
|
|
981
981
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA = "PersistMeasureUtils.writeMeasureData";
|
|
982
982
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON = "PersistMeasureUtils.useJson";
|
|
983
983
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY = "PersistMeasureUtils.useDummy";
|
|
984
|
+
const PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA = "PersistMeasureUtils.removeMeasureData";
|
|
985
|
+
const PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA = "PersistMeasureUtils.listMeasureData";
|
|
984
986
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_CLEAR = "PersistMeasureUtils.clear";
|
|
985
987
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER = "PersistMeasureUtils.usePersistMeasureAdapter";
|
|
988
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA = "PersistIntervalUtils.readIntervalData";
|
|
989
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistIntervalUtils.writeIntervalData";
|
|
990
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON = "PersistIntervalUtils.useJson";
|
|
991
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY = "PersistIntervalUtils.useDummy";
|
|
992
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA = "PersistIntervalUtils.removeIntervalData";
|
|
993
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA = "PersistIntervalUtils.listIntervalData";
|
|
994
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR = "PersistIntervalUtils.clear";
|
|
995
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER = "PersistIntervalUtils.usePersistIntervalAdapter";
|
|
986
996
|
const PERSIST_CANDLE_UTILS_METHOD_NAME_CLEAR = "PersistCandleUtils.clear";
|
|
987
997
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_USE_PERSIST_MEMORY_ADAPTER = "PersistMemoryUtils.usePersistMemoryAdapter";
|
|
988
998
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA = "PersistMemoryUtils.readMemoryData";
|
|
@@ -1895,7 +1905,7 @@ class PersistCandleUtils {
|
|
|
1895
1905
|
const isInitial = !this.getCandlesStorage.has(key);
|
|
1896
1906
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1897
1907
|
await stateStorage.waitForInit(isInitial);
|
|
1898
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1908
|
+
const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
|
|
1899
1909
|
// Calculate expected timestamps and fetch each candle directly
|
|
1900
1910
|
const cachedCandles = [];
|
|
1901
1911
|
for (let i = 0; i < limit; i++) {
|
|
@@ -1951,7 +1961,7 @@ class PersistCandleUtils {
|
|
|
1951
1961
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1952
1962
|
await stateStorage.waitForInit(isInitial);
|
|
1953
1963
|
// Calculate step in milliseconds to determine candle close time
|
|
1954
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1964
|
+
const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
|
|
1955
1965
|
const now = Date.now();
|
|
1956
1966
|
// Write each candle as a separate file, skipping incomplete candles
|
|
1957
1967
|
for (const candle of candles) {
|
|
@@ -2377,7 +2387,8 @@ class PersistMeasureUtils {
|
|
|
2377
2387
|
const stateStorage = this.getMeasureStorage(bucket);
|
|
2378
2388
|
await stateStorage.waitForInit(isInitial);
|
|
2379
2389
|
if (await stateStorage.hasValue(key)) {
|
|
2380
|
-
|
|
2390
|
+
const data = await stateStorage.readValue(key);
|
|
2391
|
+
return data.removed ? null : data;
|
|
2381
2392
|
}
|
|
2382
2393
|
return null;
|
|
2383
2394
|
};
|
|
@@ -2399,6 +2410,27 @@ class PersistMeasureUtils {
|
|
|
2399
2410
|
await stateStorage.waitForInit(isInitial);
|
|
2400
2411
|
await stateStorage.writeValue(key, data);
|
|
2401
2412
|
};
|
|
2413
|
+
/**
|
|
2414
|
+
* Marks a cached entry as removed (soft delete — file is kept on disk).
|
|
2415
|
+
* After this call `readMeasureData` for the same key returns `null`.
|
|
2416
|
+
*
|
|
2417
|
+
* @param bucket - Storage bucket
|
|
2418
|
+
* @param key - Dynamic cache key within the bucket
|
|
2419
|
+
* @returns Promise that resolves when removal is complete
|
|
2420
|
+
*/
|
|
2421
|
+
this.removeMeasureData = async (bucket, key) => {
|
|
2422
|
+
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, {
|
|
2423
|
+
bucket,
|
|
2424
|
+
key,
|
|
2425
|
+
});
|
|
2426
|
+
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2427
|
+
const stateStorage = this.getMeasureStorage(bucket);
|
|
2428
|
+
await stateStorage.waitForInit(isInitial);
|
|
2429
|
+
const data = await stateStorage.readValue(key);
|
|
2430
|
+
if (data) {
|
|
2431
|
+
await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
2432
|
+
}
|
|
2433
|
+
};
|
|
2402
2434
|
}
|
|
2403
2435
|
/**
|
|
2404
2436
|
* Registers a custom persistence adapter.
|
|
@@ -2409,6 +2441,27 @@ class PersistMeasureUtils {
|
|
|
2409
2441
|
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
|
|
2410
2442
|
this.PersistMeasureFactory = Ctor;
|
|
2411
2443
|
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Async generator yielding all non-removed entity keys for a given bucket.
|
|
2446
|
+
* Used by `CacheFileInstance.clear()` to iterate and soft-delete all entries.
|
|
2447
|
+
*
|
|
2448
|
+
* @param bucket - Storage bucket
|
|
2449
|
+
* @returns AsyncGenerator yielding entity keys
|
|
2450
|
+
*/
|
|
2451
|
+
async *listMeasureData(bucket) {
|
|
2452
|
+
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA, { bucket });
|
|
2453
|
+
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2454
|
+
const stateStorage = this.getMeasureStorage(bucket);
|
|
2455
|
+
await stateStorage.waitForInit(isInitial);
|
|
2456
|
+
for await (const key of stateStorage.keys()) {
|
|
2457
|
+
const data = await stateStorage.readValue(String(key));
|
|
2458
|
+
if (data === null || data.removed) {
|
|
2459
|
+
continue;
|
|
2460
|
+
}
|
|
2461
|
+
yield String(key);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
;
|
|
2412
2465
|
/**
|
|
2413
2466
|
* Clears the memoized storage cache.
|
|
2414
2467
|
* Call this when process.cwd() changes between strategy iterations
|
|
@@ -2438,6 +2491,140 @@ class PersistMeasureUtils {
|
|
|
2438
2491
|
* Used by Cache.file for persistent caching of external API responses.
|
|
2439
2492
|
*/
|
|
2440
2493
|
const PersistMeasureAdapter = new PersistMeasureUtils();
|
|
2494
|
+
/**
|
|
2495
|
+
* Persistence layer for Interval.file once-per-interval signal firing.
|
|
2496
|
+
*
|
|
2497
|
+
* Stores fired-interval markers under `./dump/data/interval/`.
|
|
2498
|
+
* A record's presence means the interval has already fired for that bucket+key;
|
|
2499
|
+
* absence means the function has not yet fired (or returned null last time).
|
|
2500
|
+
*/
|
|
2501
|
+
class PersistIntervalUtils {
|
|
2502
|
+
constructor() {
|
|
2503
|
+
this.PersistIntervalFactory = PersistBase;
|
|
2504
|
+
this.getIntervalStorage = functoolsKit.memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistIntervalFactory, [
|
|
2505
|
+
bucket,
|
|
2506
|
+
`./dump/data/interval/`,
|
|
2507
|
+
]));
|
|
2508
|
+
/**
|
|
2509
|
+
* Reads interval data for a given bucket and key.
|
|
2510
|
+
*
|
|
2511
|
+
* @param bucket - Storage bucket (instance name + interval + index)
|
|
2512
|
+
* @param key - Entity key within the bucket (symbol + aligned timestamp)
|
|
2513
|
+
* @returns Promise resolving to stored value or null if not found
|
|
2514
|
+
*/
|
|
2515
|
+
this.readIntervalData = async (bucket, key) => {
|
|
2516
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, {
|
|
2517
|
+
bucket,
|
|
2518
|
+
key,
|
|
2519
|
+
});
|
|
2520
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2521
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2522
|
+
await stateStorage.waitForInit(isInitial);
|
|
2523
|
+
if (await stateStorage.hasValue(key)) {
|
|
2524
|
+
const data = await stateStorage.readValue(key);
|
|
2525
|
+
return data.removed ? null : data;
|
|
2526
|
+
}
|
|
2527
|
+
return null;
|
|
2528
|
+
};
|
|
2529
|
+
/**
|
|
2530
|
+
* Writes interval data to disk.
|
|
2531
|
+
*
|
|
2532
|
+
* @param data - Data to store
|
|
2533
|
+
* @param bucket - Storage bucket
|
|
2534
|
+
* @param key - Entity key within the bucket
|
|
2535
|
+
* @returns Promise that resolves when write is complete
|
|
2536
|
+
*/
|
|
2537
|
+
this.writeIntervalData = async (data, bucket, key) => {
|
|
2538
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, {
|
|
2539
|
+
bucket,
|
|
2540
|
+
key,
|
|
2541
|
+
});
|
|
2542
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2543
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2544
|
+
await stateStorage.waitForInit(isInitial);
|
|
2545
|
+
await stateStorage.writeValue(key, data);
|
|
2546
|
+
};
|
|
2547
|
+
/**
|
|
2548
|
+
* Marks an interval entry as removed (soft delete — file is kept on disk).
|
|
2549
|
+
* After this call `readIntervalData` for the same key returns `null`,
|
|
2550
|
+
* so the function will fire again on the next `IntervalFileInstance.run` call.
|
|
2551
|
+
*
|
|
2552
|
+
* @param bucket - Storage bucket
|
|
2553
|
+
* @param key - Entity key within the bucket
|
|
2554
|
+
* @returns Promise that resolves when removal is complete
|
|
2555
|
+
*/
|
|
2556
|
+
this.removeIntervalData = async (bucket, key) => {
|
|
2557
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, {
|
|
2558
|
+
bucket,
|
|
2559
|
+
key,
|
|
2560
|
+
});
|
|
2561
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2562
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2563
|
+
await stateStorage.waitForInit(isInitial);
|
|
2564
|
+
const data = await stateStorage.readValue(key);
|
|
2565
|
+
if (data) {
|
|
2566
|
+
await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
2567
|
+
}
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Registers a custom persistence adapter.
|
|
2572
|
+
*
|
|
2573
|
+
* @param Ctor - Custom PersistBase constructor
|
|
2574
|
+
*/
|
|
2575
|
+
usePersistIntervalAdapter(Ctor) {
|
|
2576
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER);
|
|
2577
|
+
this.PersistIntervalFactory = Ctor;
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* Async generator yielding all non-removed entity keys for a given bucket.
|
|
2581
|
+
* Used by `IntervalFileInstance.clear()` to iterate and soft-delete all entries.
|
|
2582
|
+
*
|
|
2583
|
+
* @param bucket - Storage bucket
|
|
2584
|
+
* @returns AsyncGenerator yielding entity keys
|
|
2585
|
+
*/
|
|
2586
|
+
async *listIntervalData(bucket) {
|
|
2587
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA, { bucket });
|
|
2588
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2589
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2590
|
+
await stateStorage.waitForInit(isInitial);
|
|
2591
|
+
for await (const key of stateStorage.keys()) {
|
|
2592
|
+
const data = await stateStorage.readValue(String(key));
|
|
2593
|
+
if (data === null || data.removed) {
|
|
2594
|
+
continue;
|
|
2595
|
+
}
|
|
2596
|
+
yield String(key);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
;
|
|
2600
|
+
/**
|
|
2601
|
+
* Clears the memoized storage cache.
|
|
2602
|
+
* Call this when process.cwd() changes between strategy iterations.
|
|
2603
|
+
*/
|
|
2604
|
+
clear() {
|
|
2605
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR);
|
|
2606
|
+
this.getIntervalStorage.clear();
|
|
2607
|
+
}
|
|
2608
|
+
/**
|
|
2609
|
+
* Switches to the default JSON persist adapter.
|
|
2610
|
+
*/
|
|
2611
|
+
useJson() {
|
|
2612
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON);
|
|
2613
|
+
this.usePersistIntervalAdapter(PersistBase);
|
|
2614
|
+
}
|
|
2615
|
+
/**
|
|
2616
|
+
* Switches to a dummy persist adapter that discards all writes.
|
|
2617
|
+
*/
|
|
2618
|
+
useDummy() {
|
|
2619
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2620
|
+
this.usePersistIntervalAdapter(PersistDummy);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* Global singleton instance of PersistIntervalUtils.
|
|
2625
|
+
* Used by Interval.file for persistent once-per-interval signal firing.
|
|
2626
|
+
*/
|
|
2627
|
+
const PersistIntervalAdapter = new PersistIntervalUtils();
|
|
2441
2628
|
/**
|
|
2442
2629
|
* Utility class for managing memory entry persistence.
|
|
2443
2630
|
*
|
|
@@ -2785,8 +2972,8 @@ class CandleUtils {
|
|
|
2785
2972
|
}
|
|
2786
2973
|
const Candle = new CandleUtils();
|
|
2787
2974
|
|
|
2788
|
-
const MS_PER_MINUTE$
|
|
2789
|
-
const INTERVAL_MINUTES$
|
|
2975
|
+
const MS_PER_MINUTE$6 = 60000;
|
|
2976
|
+
const INTERVAL_MINUTES$8 = {
|
|
2790
2977
|
"1m": 1,
|
|
2791
2978
|
"3m": 3,
|
|
2792
2979
|
"5m": 5,
|
|
@@ -2818,7 +3005,7 @@ const INTERVAL_MINUTES$7 = {
|
|
|
2818
3005
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
2819
3006
|
*/
|
|
2820
3007
|
const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
|
|
2821
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
3008
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$6;
|
|
2822
3009
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
2823
3010
|
};
|
|
2824
3011
|
/**
|
|
@@ -2972,9 +3159,9 @@ const WRITE_CANDLES_CACHE_FN$1 = functoolsKit.trycatch(functoolsKit.queued(async
|
|
|
2972
3159
|
* @returns Promise resolving to array of candle data
|
|
2973
3160
|
*/
|
|
2974
3161
|
const GET_CANDLES_FN$1 = async (dto, since, self) => {
|
|
2975
|
-
const step = INTERVAL_MINUTES$
|
|
3162
|
+
const step = INTERVAL_MINUTES$8[dto.interval];
|
|
2976
3163
|
const sinceTimestamp = since.getTime();
|
|
2977
|
-
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$
|
|
3164
|
+
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$6;
|
|
2978
3165
|
await Candle.acquireLock(`ClientExchange GET_CANDLES_FN symbol=${dto.symbol} interval=${dto.interval} limit=${dto.limit}`);
|
|
2979
3166
|
try {
|
|
2980
3167
|
// Try to read from cache first
|
|
@@ -3088,11 +3275,11 @@ class ClientExchange {
|
|
|
3088
3275
|
interval,
|
|
3089
3276
|
limit,
|
|
3090
3277
|
});
|
|
3091
|
-
const step = INTERVAL_MINUTES$
|
|
3278
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3092
3279
|
if (!step) {
|
|
3093
3280
|
throw new Error(`ClientExchange unknown interval=${interval}`);
|
|
3094
3281
|
}
|
|
3095
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3282
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3096
3283
|
// Align when down to interval boundary
|
|
3097
3284
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3098
3285
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
@@ -3169,11 +3356,11 @@ class ClientExchange {
|
|
|
3169
3356
|
if (!this.params.execution.context.backtest) {
|
|
3170
3357
|
throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
|
|
3171
3358
|
}
|
|
3172
|
-
const step = INTERVAL_MINUTES$
|
|
3359
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3173
3360
|
if (!step) {
|
|
3174
3361
|
throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
|
|
3175
3362
|
}
|
|
3176
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3363
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3177
3364
|
const now = Date.now();
|
|
3178
3365
|
// Align when down to interval boundary
|
|
3179
3366
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
@@ -3337,11 +3524,11 @@ class ClientExchange {
|
|
|
3337
3524
|
sDate,
|
|
3338
3525
|
eDate,
|
|
3339
3526
|
});
|
|
3340
|
-
const step = INTERVAL_MINUTES$
|
|
3527
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3341
3528
|
if (!step) {
|
|
3342
3529
|
throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
|
|
3343
3530
|
}
|
|
3344
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3531
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3345
3532
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3346
3533
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
3347
3534
|
let sinceTimestamp;
|
|
@@ -3470,7 +3657,7 @@ class ClientExchange {
|
|
|
3470
3657
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
3471
3658
|
const to = new Date(alignedTo);
|
|
3472
3659
|
const from = new Date(alignedTo -
|
|
3473
|
-
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
3660
|
+
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$6);
|
|
3474
3661
|
return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
|
|
3475
3662
|
}
|
|
3476
3663
|
/**
|
|
@@ -3499,7 +3686,7 @@ class ClientExchange {
|
|
|
3499
3686
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3500
3687
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
3501
3688
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, 1);
|
|
3502
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
3689
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$6 - MS_PER_MINUTE$6;
|
|
3503
3690
|
// No limit: fetch a single window and return as-is
|
|
3504
3691
|
if (limit === undefined) {
|
|
3505
3692
|
const to = new Date(alignedTo);
|
|
@@ -4478,7 +4665,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
|
|
|
4478
4665
|
}
|
|
4479
4666
|
};
|
|
4480
4667
|
|
|
4481
|
-
const INTERVAL_MINUTES$
|
|
4668
|
+
const INTERVAL_MINUTES$7 = {
|
|
4482
4669
|
"1m": 1,
|
|
4483
4670
|
"3m": 3,
|
|
4484
4671
|
"5m": 5,
|
|
@@ -4862,7 +5049,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
4862
5049
|
}
|
|
4863
5050
|
const currentTime = self.params.execution.context.when.getTime();
|
|
4864
5051
|
{
|
|
4865
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
5052
|
+
const intervalMinutes = INTERVAL_MINUTES$7[self.params.interval];
|
|
4866
5053
|
const intervalMs = intervalMinutes * 60 * 1000;
|
|
4867
5054
|
const alignedTime = Math.floor(currentTime / intervalMs) * intervalMs;
|
|
4868
5055
|
// Проверяем что наступил новый интервал (по aligned timestamp)
|
|
@@ -9886,7 +10073,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
9886
10073
|
* @param backtest - Whether running in backtest mode
|
|
9887
10074
|
* @returns Unique string key for memoization
|
|
9888
10075
|
*/
|
|
9889
|
-
const CREATE_KEY_FN$
|
|
10076
|
+
const CREATE_KEY_FN$t = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
9890
10077
|
const parts = [symbol, strategyName, exchangeName];
|
|
9891
10078
|
if (frameName)
|
|
9892
10079
|
parts.push(frameName);
|
|
@@ -10153,7 +10340,7 @@ class StrategyConnectionService {
|
|
|
10153
10340
|
* @param backtest - Whether running in backtest mode
|
|
10154
10341
|
* @returns Configured ClientStrategy instance
|
|
10155
10342
|
*/
|
|
10156
|
-
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
10343
|
+
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10157
10344
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10158
10345
|
return new ClientStrategy({
|
|
10159
10346
|
symbol,
|
|
@@ -10948,7 +11135,7 @@ class StrategyConnectionService {
|
|
|
10948
11135
|
}
|
|
10949
11136
|
return;
|
|
10950
11137
|
}
|
|
10951
|
-
const key = CREATE_KEY_FN$
|
|
11138
|
+
const key = CREATE_KEY_FN$t(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
10952
11139
|
if (!this.getStrategy.has(key)) {
|
|
10953
11140
|
return;
|
|
10954
11141
|
}
|
|
@@ -11350,7 +11537,7 @@ class StrategyConnectionService {
|
|
|
11350
11537
|
* Maps FrameInterval to minutes for timestamp calculation.
|
|
11351
11538
|
* Used to generate timeframe arrays with proper spacing.
|
|
11352
11539
|
*/
|
|
11353
|
-
const INTERVAL_MINUTES$
|
|
11540
|
+
const INTERVAL_MINUTES$6 = {
|
|
11354
11541
|
"1m": 1,
|
|
11355
11542
|
"3m": 3,
|
|
11356
11543
|
"5m": 5,
|
|
@@ -11406,7 +11593,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
11406
11593
|
symbol,
|
|
11407
11594
|
});
|
|
11408
11595
|
const { interval, startDate, endDate } = self.params;
|
|
11409
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
11596
|
+
const intervalMinutes = INTERVAL_MINUTES$6[interval];
|
|
11410
11597
|
if (!intervalMinutes) {
|
|
11411
11598
|
throw new Error(`ClientFrame unknown interval: ${interval}`);
|
|
11412
11599
|
}
|
|
@@ -11771,8 +11958,8 @@ const get = (object, path) => {
|
|
|
11771
11958
|
return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
|
|
11772
11959
|
};
|
|
11773
11960
|
|
|
11774
|
-
const MS_PER_MINUTE$
|
|
11775
|
-
const INTERVAL_MINUTES$
|
|
11961
|
+
const MS_PER_MINUTE$5 = 60000;
|
|
11962
|
+
const INTERVAL_MINUTES$5 = {
|
|
11776
11963
|
"1m": 1,
|
|
11777
11964
|
"3m": 3,
|
|
11778
11965
|
"5m": 5,
|
|
@@ -11804,11 +11991,11 @@ const INTERVAL_MINUTES$4 = {
|
|
|
11804
11991
|
* @returns New Date aligned down to interval boundary
|
|
11805
11992
|
*/
|
|
11806
11993
|
const alignToInterval = (date, interval) => {
|
|
11807
|
-
const minutes = INTERVAL_MINUTES$
|
|
11994
|
+
const minutes = INTERVAL_MINUTES$5[interval];
|
|
11808
11995
|
if (minutes === undefined) {
|
|
11809
11996
|
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
11810
11997
|
}
|
|
11811
|
-
const intervalMs = minutes * MS_PER_MINUTE$
|
|
11998
|
+
const intervalMs = minutes * MS_PER_MINUTE$5;
|
|
11812
11999
|
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
11813
12000
|
};
|
|
11814
12001
|
|
|
@@ -12117,7 +12304,7 @@ class ClientRisk {
|
|
|
12117
12304
|
* @param backtest - Whether running in backtest mode
|
|
12118
12305
|
* @returns Unique string key for memoization
|
|
12119
12306
|
*/
|
|
12120
|
-
const CREATE_KEY_FN$
|
|
12307
|
+
const CREATE_KEY_FN$s = (riskName, exchangeName, frameName, backtest) => {
|
|
12121
12308
|
const parts = [riskName, exchangeName];
|
|
12122
12309
|
if (frameName)
|
|
12123
12310
|
parts.push(frameName);
|
|
@@ -12217,7 +12404,7 @@ class RiskConnectionService {
|
|
|
12217
12404
|
* @param backtest - True if backtest mode, false if live mode
|
|
12218
12405
|
* @returns Configured ClientRisk instance
|
|
12219
12406
|
*/
|
|
12220
|
-
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12407
|
+
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
12221
12408
|
const schema = this.riskSchemaService.get(riskName);
|
|
12222
12409
|
return new ClientRisk({
|
|
12223
12410
|
...schema,
|
|
@@ -12286,7 +12473,7 @@ class RiskConnectionService {
|
|
|
12286
12473
|
payload,
|
|
12287
12474
|
});
|
|
12288
12475
|
if (payload) {
|
|
12289
|
-
const key = CREATE_KEY_FN$
|
|
12476
|
+
const key = CREATE_KEY_FN$s(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
12290
12477
|
this.getRisk.clear(key);
|
|
12291
12478
|
}
|
|
12292
12479
|
else {
|
|
@@ -13330,7 +13517,7 @@ class ClientAction {
|
|
|
13330
13517
|
* @param backtest - Whether running in backtest mode
|
|
13331
13518
|
* @returns Unique string key for memoization
|
|
13332
13519
|
*/
|
|
13333
|
-
const CREATE_KEY_FN$
|
|
13520
|
+
const CREATE_KEY_FN$r = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13334
13521
|
const parts = [actionName, strategyName, exchangeName];
|
|
13335
13522
|
if (frameName)
|
|
13336
13523
|
parts.push(frameName);
|
|
@@ -13382,7 +13569,7 @@ class ActionConnectionService {
|
|
|
13382
13569
|
* @param backtest - True if backtest mode, false if live mode
|
|
13383
13570
|
* @returns Configured ClientAction instance
|
|
13384
13571
|
*/
|
|
13385
|
-
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
13572
|
+
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13386
13573
|
const schema = this.actionSchemaService.get(actionName);
|
|
13387
13574
|
return new ClientAction({
|
|
13388
13575
|
...schema,
|
|
@@ -13593,7 +13780,7 @@ class ActionConnectionService {
|
|
|
13593
13780
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
13594
13781
|
return;
|
|
13595
13782
|
}
|
|
13596
|
-
const key = CREATE_KEY_FN$
|
|
13783
|
+
const key = CREATE_KEY_FN$r(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
13597
13784
|
if (!this.getAction.has(key)) {
|
|
13598
13785
|
return;
|
|
13599
13786
|
}
|
|
@@ -13611,7 +13798,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
13611
13798
|
* @param exchangeName - Exchange name
|
|
13612
13799
|
* @returns Unique string key for memoization
|
|
13613
13800
|
*/
|
|
13614
|
-
const CREATE_KEY_FN$
|
|
13801
|
+
const CREATE_KEY_FN$q = (exchangeName) => {
|
|
13615
13802
|
return exchangeName;
|
|
13616
13803
|
};
|
|
13617
13804
|
/**
|
|
@@ -13635,7 +13822,7 @@ class ExchangeCoreService {
|
|
|
13635
13822
|
* @param exchangeName - Name of the exchange to validate
|
|
13636
13823
|
* @returns Promise that resolves when validation is complete
|
|
13637
13824
|
*/
|
|
13638
|
-
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
13825
|
+
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$q(exchangeName), async (exchangeName) => {
|
|
13639
13826
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
13640
13827
|
exchangeName,
|
|
13641
13828
|
});
|
|
@@ -13887,7 +14074,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
13887
14074
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13888
14075
|
* @returns Unique string key for memoization
|
|
13889
14076
|
*/
|
|
13890
|
-
const CREATE_KEY_FN$
|
|
14077
|
+
const CREATE_KEY_FN$p = (context) => {
|
|
13891
14078
|
const parts = [context.strategyName, context.exchangeName];
|
|
13892
14079
|
if (context.frameName)
|
|
13893
14080
|
parts.push(context.frameName);
|
|
@@ -13919,7 +14106,7 @@ class StrategyCoreService {
|
|
|
13919
14106
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13920
14107
|
* @returns Promise that resolves when validation is complete
|
|
13921
14108
|
*/
|
|
13922
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
14109
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
|
|
13923
14110
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
13924
14111
|
context,
|
|
13925
14112
|
});
|
|
@@ -15143,7 +15330,7 @@ class SizingGlobalService {
|
|
|
15143
15330
|
* @param context - Context with riskName, exchangeName, frameName
|
|
15144
15331
|
* @returns Unique string key for memoization
|
|
15145
15332
|
*/
|
|
15146
|
-
const CREATE_KEY_FN$
|
|
15333
|
+
const CREATE_KEY_FN$o = (context) => {
|
|
15147
15334
|
const parts = [context.riskName, context.exchangeName];
|
|
15148
15335
|
if (context.frameName)
|
|
15149
15336
|
parts.push(context.frameName);
|
|
@@ -15169,7 +15356,7 @@ class RiskGlobalService {
|
|
|
15169
15356
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
15170
15357
|
* @returns Promise that resolves when validation is complete
|
|
15171
15358
|
*/
|
|
15172
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
15359
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
|
|
15173
15360
|
this.loggerService.log("riskGlobalService validate", {
|
|
15174
15361
|
context,
|
|
15175
15362
|
});
|
|
@@ -15247,7 +15434,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
15247
15434
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15248
15435
|
* @returns Unique string key for memoization
|
|
15249
15436
|
*/
|
|
15250
|
-
const CREATE_KEY_FN$
|
|
15437
|
+
const CREATE_KEY_FN$n = (context) => {
|
|
15251
15438
|
const parts = [context.strategyName, context.exchangeName];
|
|
15252
15439
|
if (context.frameName)
|
|
15253
15440
|
parts.push(context.frameName);
|
|
@@ -15291,7 +15478,7 @@ class ActionCoreService {
|
|
|
15291
15478
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
15292
15479
|
* @returns Promise that resolves when all validations complete
|
|
15293
15480
|
*/
|
|
15294
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
15481
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
|
|
15295
15482
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
15296
15483
|
context,
|
|
15297
15484
|
});
|
|
@@ -16886,8 +17073,8 @@ class BacktestLogicPrivateService {
|
|
|
16886
17073
|
}
|
|
16887
17074
|
|
|
16888
17075
|
const EMITTER_CHECK_INTERVAL = 5000;
|
|
16889
|
-
const MS_PER_MINUTE$
|
|
16890
|
-
const INTERVAL_MINUTES$
|
|
17076
|
+
const MS_PER_MINUTE$4 = 60000;
|
|
17077
|
+
const INTERVAL_MINUTES$4 = {
|
|
16891
17078
|
"1m": 1,
|
|
16892
17079
|
"3m": 3,
|
|
16893
17080
|
"5m": 5,
|
|
@@ -16903,7 +17090,7 @@ const INTERVAL_MINUTES$3 = {
|
|
|
16903
17090
|
};
|
|
16904
17091
|
const createEmitter = functoolsKit.memoize(([interval]) => `${interval}`, (interval) => {
|
|
16905
17092
|
const tickSubject = new functoolsKit.Subject();
|
|
16906
|
-
const intervalMs = INTERVAL_MINUTES$
|
|
17093
|
+
const intervalMs = INTERVAL_MINUTES$4[interval] * MS_PER_MINUTE$4;
|
|
16907
17094
|
{
|
|
16908
17095
|
let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
16909
17096
|
functoolsKit.Source.fromInterval(EMITTER_CHECK_INTERVAL)
|
|
@@ -20287,7 +20474,7 @@ const ReportWriter = new ReportWriterAdapter();
|
|
|
20287
20474
|
* @param backtest - Whether running in backtest mode
|
|
20288
20475
|
* @returns Unique string key for memoization
|
|
20289
20476
|
*/
|
|
20290
|
-
const CREATE_KEY_FN$
|
|
20477
|
+
const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20291
20478
|
const parts = [symbol, strategyName, exchangeName];
|
|
20292
20479
|
if (frameName)
|
|
20293
20480
|
parts.push(frameName);
|
|
@@ -20533,7 +20720,7 @@ class BacktestMarkdownService {
|
|
|
20533
20720
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20534
20721
|
* Each combination gets its own isolated storage instance.
|
|
20535
20722
|
*/
|
|
20536
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20723
|
+
this.getStorage = functoolsKit.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));
|
|
20537
20724
|
/**
|
|
20538
20725
|
* Processes tick events and accumulates closed signals.
|
|
20539
20726
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -20690,7 +20877,7 @@ class BacktestMarkdownService {
|
|
|
20690
20877
|
payload,
|
|
20691
20878
|
});
|
|
20692
20879
|
if (payload) {
|
|
20693
|
-
const key = CREATE_KEY_FN$
|
|
20880
|
+
const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20694
20881
|
this.getStorage.clear(key);
|
|
20695
20882
|
}
|
|
20696
20883
|
else {
|
|
@@ -20752,7 +20939,7 @@ class BacktestMarkdownService {
|
|
|
20752
20939
|
* @param backtest - Whether running in backtest mode
|
|
20753
20940
|
* @returns Unique string key for memoization
|
|
20754
20941
|
*/
|
|
20755
|
-
const CREATE_KEY_FN$
|
|
20942
|
+
const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20756
20943
|
const parts = [symbol, strategyName, exchangeName];
|
|
20757
20944
|
if (frameName)
|
|
20758
20945
|
parts.push(frameName);
|
|
@@ -21247,7 +21434,7 @@ class LiveMarkdownService {
|
|
|
21247
21434
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21248
21435
|
* Each combination gets its own isolated storage instance.
|
|
21249
21436
|
*/
|
|
21250
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21437
|
+
this.getStorage = functoolsKit.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));
|
|
21251
21438
|
/**
|
|
21252
21439
|
* Subscribes to live signal emitter to receive tick events.
|
|
21253
21440
|
* Protected against multiple subscriptions.
|
|
@@ -21465,7 +21652,7 @@ class LiveMarkdownService {
|
|
|
21465
21652
|
payload,
|
|
21466
21653
|
});
|
|
21467
21654
|
if (payload) {
|
|
21468
|
-
const key = CREATE_KEY_FN$
|
|
21655
|
+
const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21469
21656
|
this.getStorage.clear(key);
|
|
21470
21657
|
}
|
|
21471
21658
|
else {
|
|
@@ -21485,7 +21672,7 @@ class LiveMarkdownService {
|
|
|
21485
21672
|
* @param backtest - Whether running in backtest mode
|
|
21486
21673
|
* @returns Unique string key for memoization
|
|
21487
21674
|
*/
|
|
21488
|
-
const CREATE_KEY_FN$
|
|
21675
|
+
const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21489
21676
|
const parts = [symbol, strategyName, exchangeName];
|
|
21490
21677
|
if (frameName)
|
|
21491
21678
|
parts.push(frameName);
|
|
@@ -21774,7 +21961,7 @@ class ScheduleMarkdownService {
|
|
|
21774
21961
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21775
21962
|
* Each combination gets its own isolated storage instance.
|
|
21776
21963
|
*/
|
|
21777
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21964
|
+
this.getStorage = functoolsKit.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));
|
|
21778
21965
|
/**
|
|
21779
21966
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
21780
21967
|
* Protected against multiple subscriptions.
|
|
@@ -21977,7 +22164,7 @@ class ScheduleMarkdownService {
|
|
|
21977
22164
|
payload,
|
|
21978
22165
|
});
|
|
21979
22166
|
if (payload) {
|
|
21980
|
-
const key = CREATE_KEY_FN$
|
|
22167
|
+
const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21981
22168
|
this.getStorage.clear(key);
|
|
21982
22169
|
}
|
|
21983
22170
|
else {
|
|
@@ -21997,7 +22184,7 @@ class ScheduleMarkdownService {
|
|
|
21997
22184
|
* @param backtest - Whether running in backtest mode
|
|
21998
22185
|
* @returns Unique string key for memoization
|
|
21999
22186
|
*/
|
|
22000
|
-
const CREATE_KEY_FN$
|
|
22187
|
+
const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
22001
22188
|
const parts = [symbol, strategyName, exchangeName];
|
|
22002
22189
|
if (frameName)
|
|
22003
22190
|
parts.push(frameName);
|
|
@@ -22242,7 +22429,7 @@ class PerformanceMarkdownService {
|
|
|
22242
22429
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
22243
22430
|
* Each combination gets its own isolated storage instance.
|
|
22244
22431
|
*/
|
|
22245
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22432
|
+
this.getStorage = functoolsKit.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));
|
|
22246
22433
|
/**
|
|
22247
22434
|
* Subscribes to performance emitter to receive performance events.
|
|
22248
22435
|
* Protected against multiple subscriptions.
|
|
@@ -22409,7 +22596,7 @@ class PerformanceMarkdownService {
|
|
|
22409
22596
|
payload,
|
|
22410
22597
|
});
|
|
22411
22598
|
if (payload) {
|
|
22412
|
-
const key = CREATE_KEY_FN$
|
|
22599
|
+
const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
22413
22600
|
this.getStorage.clear(key);
|
|
22414
22601
|
}
|
|
22415
22602
|
else {
|
|
@@ -22888,7 +23075,7 @@ class WalkerMarkdownService {
|
|
|
22888
23075
|
* @param backtest - Whether running in backtest mode
|
|
22889
23076
|
* @returns Unique string key for memoization
|
|
22890
23077
|
*/
|
|
22891
|
-
const CREATE_KEY_FN$
|
|
23078
|
+
const CREATE_KEY_FN$i = (exchangeName, frameName, backtest) => {
|
|
22892
23079
|
const parts = [exchangeName];
|
|
22893
23080
|
if (frameName)
|
|
22894
23081
|
parts.push(frameName);
|
|
@@ -23335,7 +23522,7 @@ class HeatMarkdownService {
|
|
|
23335
23522
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
23336
23523
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
23337
23524
|
*/
|
|
23338
|
-
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
23525
|
+
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
23339
23526
|
/**
|
|
23340
23527
|
* Subscribes to signal emitter to receive tick events.
|
|
23341
23528
|
* Protected against multiple subscriptions.
|
|
@@ -23553,7 +23740,7 @@ class HeatMarkdownService {
|
|
|
23553
23740
|
payload,
|
|
23554
23741
|
});
|
|
23555
23742
|
if (payload) {
|
|
23556
|
-
const key = CREATE_KEY_FN$
|
|
23743
|
+
const key = CREATE_KEY_FN$i(payload.exchangeName, payload.frameName, payload.backtest);
|
|
23557
23744
|
this.getStorage.clear(key);
|
|
23558
23745
|
}
|
|
23559
23746
|
else {
|
|
@@ -24584,7 +24771,7 @@ class ClientPartial {
|
|
|
24584
24771
|
* @param backtest - Whether running in backtest mode
|
|
24585
24772
|
* @returns Unique string key for memoization
|
|
24586
24773
|
*/
|
|
24587
|
-
const CREATE_KEY_FN$
|
|
24774
|
+
const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
24588
24775
|
/**
|
|
24589
24776
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
24590
24777
|
*
|
|
@@ -24706,7 +24893,7 @@ class PartialConnectionService {
|
|
|
24706
24893
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
24707
24894
|
* Value: ClientPartial instance with logger and event emitters
|
|
24708
24895
|
*/
|
|
24709
|
-
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
24896
|
+
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
|
|
24710
24897
|
return new ClientPartial({
|
|
24711
24898
|
signalId,
|
|
24712
24899
|
logger: this.loggerService,
|
|
@@ -24796,7 +24983,7 @@ class PartialConnectionService {
|
|
|
24796
24983
|
const partial = this.getPartial(data.id, backtest);
|
|
24797
24984
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
24798
24985
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
24799
|
-
const key = CREATE_KEY_FN$
|
|
24986
|
+
const key = CREATE_KEY_FN$h(data.id, backtest);
|
|
24800
24987
|
this.getPartial.clear(key);
|
|
24801
24988
|
};
|
|
24802
24989
|
}
|
|
@@ -24812,7 +24999,7 @@ class PartialConnectionService {
|
|
|
24812
24999
|
* @param backtest - Whether running in backtest mode
|
|
24813
25000
|
* @returns Unique string key for memoization
|
|
24814
25001
|
*/
|
|
24815
|
-
const CREATE_KEY_FN$
|
|
25002
|
+
const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24816
25003
|
const parts = [symbol, strategyName, exchangeName];
|
|
24817
25004
|
if (frameName)
|
|
24818
25005
|
parts.push(frameName);
|
|
@@ -25035,7 +25222,7 @@ class PartialMarkdownService {
|
|
|
25035
25222
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
25036
25223
|
* Each combination gets its own isolated storage instance.
|
|
25037
25224
|
*/
|
|
25038
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
25225
|
+
this.getStorage = functoolsKit.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));
|
|
25039
25226
|
/**
|
|
25040
25227
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
25041
25228
|
* Protected against multiple subscriptions.
|
|
@@ -25245,7 +25432,7 @@ class PartialMarkdownService {
|
|
|
25245
25432
|
payload,
|
|
25246
25433
|
});
|
|
25247
25434
|
if (payload) {
|
|
25248
|
-
const key = CREATE_KEY_FN$
|
|
25435
|
+
const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
25249
25436
|
this.getStorage.clear(key);
|
|
25250
25437
|
}
|
|
25251
25438
|
else {
|
|
@@ -25261,7 +25448,7 @@ class PartialMarkdownService {
|
|
|
25261
25448
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
25262
25449
|
* @returns Unique string key for memoization
|
|
25263
25450
|
*/
|
|
25264
|
-
const CREATE_KEY_FN$
|
|
25451
|
+
const CREATE_KEY_FN$f = (context) => {
|
|
25265
25452
|
const parts = [context.strategyName, context.exchangeName];
|
|
25266
25453
|
if (context.frameName)
|
|
25267
25454
|
parts.push(context.frameName);
|
|
@@ -25335,7 +25522,7 @@ class PartialGlobalService {
|
|
|
25335
25522
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
25336
25523
|
* @param methodName - Name of the calling method for error tracking
|
|
25337
25524
|
*/
|
|
25338
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
25525
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
|
|
25339
25526
|
this.loggerService.log("partialGlobalService validate", {
|
|
25340
25527
|
context,
|
|
25341
25528
|
methodName,
|
|
@@ -25790,7 +25977,7 @@ class ClientBreakeven {
|
|
|
25790
25977
|
* @param backtest - Whether running in backtest mode
|
|
25791
25978
|
* @returns Unique string key for memoization
|
|
25792
25979
|
*/
|
|
25793
|
-
const CREATE_KEY_FN$
|
|
25980
|
+
const CREATE_KEY_FN$e = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
25794
25981
|
/**
|
|
25795
25982
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
25796
25983
|
*
|
|
@@ -25876,7 +26063,7 @@ class BreakevenConnectionService {
|
|
|
25876
26063
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
25877
26064
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
25878
26065
|
*/
|
|
25879
|
-
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
26066
|
+
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$e(signalId, backtest), (signalId, backtest) => {
|
|
25880
26067
|
return new ClientBreakeven({
|
|
25881
26068
|
signalId,
|
|
25882
26069
|
logger: this.loggerService,
|
|
@@ -25937,7 +26124,7 @@ class BreakevenConnectionService {
|
|
|
25937
26124
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
25938
26125
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
25939
26126
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
25940
|
-
const key = CREATE_KEY_FN$
|
|
26127
|
+
const key = CREATE_KEY_FN$e(data.id, backtest);
|
|
25941
26128
|
this.getBreakeven.clear(key);
|
|
25942
26129
|
};
|
|
25943
26130
|
}
|
|
@@ -25953,7 +26140,7 @@ class BreakevenConnectionService {
|
|
|
25953
26140
|
* @param backtest - Whether running in backtest mode
|
|
25954
26141
|
* @returns Unique string key for memoization
|
|
25955
26142
|
*/
|
|
25956
|
-
const CREATE_KEY_FN$
|
|
26143
|
+
const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
25957
26144
|
const parts = [symbol, strategyName, exchangeName];
|
|
25958
26145
|
if (frameName)
|
|
25959
26146
|
parts.push(frameName);
|
|
@@ -26128,7 +26315,7 @@ class BreakevenMarkdownService {
|
|
|
26128
26315
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26129
26316
|
* Each combination gets its own isolated storage instance.
|
|
26130
26317
|
*/
|
|
26131
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
26318
|
+
this.getStorage = functoolsKit.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));
|
|
26132
26319
|
/**
|
|
26133
26320
|
* Subscribes to breakeven signal emitter to receive events.
|
|
26134
26321
|
* Protected against multiple subscriptions.
|
|
@@ -26317,7 +26504,7 @@ class BreakevenMarkdownService {
|
|
|
26317
26504
|
payload,
|
|
26318
26505
|
});
|
|
26319
26506
|
if (payload) {
|
|
26320
|
-
const key = CREATE_KEY_FN$
|
|
26507
|
+
const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26321
26508
|
this.getStorage.clear(key);
|
|
26322
26509
|
}
|
|
26323
26510
|
else {
|
|
@@ -26333,7 +26520,7 @@ class BreakevenMarkdownService {
|
|
|
26333
26520
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
26334
26521
|
* @returns Unique string key for memoization
|
|
26335
26522
|
*/
|
|
26336
|
-
const CREATE_KEY_FN$
|
|
26523
|
+
const CREATE_KEY_FN$c = (context) => {
|
|
26337
26524
|
const parts = [context.strategyName, context.exchangeName];
|
|
26338
26525
|
if (context.frameName)
|
|
26339
26526
|
parts.push(context.frameName);
|
|
@@ -26407,7 +26594,7 @@ class BreakevenGlobalService {
|
|
|
26407
26594
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
26408
26595
|
* @param methodName - Name of the calling method for error tracking
|
|
26409
26596
|
*/
|
|
26410
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
26597
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$c(context), (context, methodName) => {
|
|
26411
26598
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
26412
26599
|
context,
|
|
26413
26600
|
methodName,
|
|
@@ -26628,7 +26815,7 @@ class ConfigValidationService {
|
|
|
26628
26815
|
* @param backtest - Whether running in backtest mode
|
|
26629
26816
|
* @returns Unique string key for memoization
|
|
26630
26817
|
*/
|
|
26631
|
-
const CREATE_KEY_FN$
|
|
26818
|
+
const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
26632
26819
|
const parts = [symbol, strategyName, exchangeName];
|
|
26633
26820
|
if (frameName)
|
|
26634
26821
|
parts.push(frameName);
|
|
@@ -26795,7 +26982,7 @@ class RiskMarkdownService {
|
|
|
26795
26982
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26796
26983
|
* Each combination gets its own isolated storage instance.
|
|
26797
26984
|
*/
|
|
26798
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
26985
|
+
this.getStorage = functoolsKit.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));
|
|
26799
26986
|
/**
|
|
26800
26987
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
26801
26988
|
* Protected against multiple subscriptions.
|
|
@@ -26984,7 +27171,7 @@ class RiskMarkdownService {
|
|
|
26984
27171
|
payload,
|
|
26985
27172
|
});
|
|
26986
27173
|
if (payload) {
|
|
26987
|
-
const key = CREATE_KEY_FN$
|
|
27174
|
+
const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26988
27175
|
this.getStorage.clear(key);
|
|
26989
27176
|
}
|
|
26990
27177
|
else {
|
|
@@ -29363,7 +29550,7 @@ class HighestProfitReportService {
|
|
|
29363
29550
|
* @returns Colon-separated key string for memoization
|
|
29364
29551
|
* @internal
|
|
29365
29552
|
*/
|
|
29366
|
-
const CREATE_KEY_FN$
|
|
29553
|
+
const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29367
29554
|
const parts = [symbol, strategyName, exchangeName];
|
|
29368
29555
|
if (frameName)
|
|
29369
29556
|
parts.push(frameName);
|
|
@@ -29605,7 +29792,7 @@ class StrategyMarkdownService {
|
|
|
29605
29792
|
*
|
|
29606
29793
|
* @internal
|
|
29607
29794
|
*/
|
|
29608
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
29795
|
+
this.getStorage = functoolsKit.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));
|
|
29609
29796
|
/**
|
|
29610
29797
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
29611
29798
|
*
|
|
@@ -30173,7 +30360,7 @@ class StrategyMarkdownService {
|
|
|
30173
30360
|
this.clear = async (payload) => {
|
|
30174
30361
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
30175
30362
|
if (payload) {
|
|
30176
|
-
const key = CREATE_KEY_FN$
|
|
30363
|
+
const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
30177
30364
|
this.getStorage.clear(key);
|
|
30178
30365
|
}
|
|
30179
30366
|
else {
|
|
@@ -30281,7 +30468,7 @@ class StrategyMarkdownService {
|
|
|
30281
30468
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
30282
30469
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
30283
30470
|
*/
|
|
30284
|
-
const CREATE_KEY_FN$
|
|
30471
|
+
const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
30285
30472
|
const parts = [symbol, strategyName, exchangeName];
|
|
30286
30473
|
if (frameName)
|
|
30287
30474
|
parts.push(frameName);
|
|
@@ -30474,7 +30661,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
30474
30661
|
class SyncMarkdownService {
|
|
30475
30662
|
constructor() {
|
|
30476
30663
|
this.loggerService = inject(TYPES.loggerService);
|
|
30477
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
30664
|
+
this.getStorage = functoolsKit.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));
|
|
30478
30665
|
/**
|
|
30479
30666
|
* Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
|
|
30480
30667
|
* Protected against multiple subscriptions via `singleshot` — subsequent calls
|
|
@@ -30670,7 +30857,7 @@ class SyncMarkdownService {
|
|
|
30670
30857
|
this.clear = async (payload) => {
|
|
30671
30858
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
30672
30859
|
if (payload) {
|
|
30673
|
-
const key = CREATE_KEY_FN$
|
|
30860
|
+
const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
30674
30861
|
this.getStorage.clear(key);
|
|
30675
30862
|
}
|
|
30676
30863
|
else {
|
|
@@ -30683,7 +30870,7 @@ class SyncMarkdownService {
|
|
|
30683
30870
|
/**
|
|
30684
30871
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
30685
30872
|
*/
|
|
30686
|
-
const CREATE_KEY_FN$
|
|
30873
|
+
const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
30687
30874
|
const parts = [symbol, strategyName, exchangeName];
|
|
30688
30875
|
if (frameName)
|
|
30689
30876
|
parts.push(frameName);
|
|
@@ -30859,7 +31046,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
30859
31046
|
class HighestProfitMarkdownService {
|
|
30860
31047
|
constructor() {
|
|
30861
31048
|
this.loggerService = inject(TYPES.loggerService);
|
|
30862
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31049
|
+
this.getStorage = functoolsKit.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));
|
|
30863
31050
|
/**
|
|
30864
31051
|
* Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
|
|
30865
31052
|
* events. Protected against multiple subscriptions via `singleshot` — subsequent
|
|
@@ -31025,7 +31212,7 @@ class HighestProfitMarkdownService {
|
|
|
31025
31212
|
this.clear = async (payload) => {
|
|
31026
31213
|
this.loggerService.log("highestProfitMarkdownService clear", { payload });
|
|
31027
31214
|
if (payload) {
|
|
31028
|
-
const key = CREATE_KEY_FN$
|
|
31215
|
+
const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31029
31216
|
this.getStorage.clear(key);
|
|
31030
31217
|
}
|
|
31031
31218
|
else {
|
|
@@ -31047,7 +31234,7 @@ const LISTEN_TIMEOUT$1 = 120000;
|
|
|
31047
31234
|
* @param backtest - Whether running in backtest mode
|
|
31048
31235
|
* @returns Unique string key for memoization
|
|
31049
31236
|
*/
|
|
31050
|
-
const CREATE_KEY_FN$
|
|
31237
|
+
const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31051
31238
|
const parts = [symbol, strategyName, exchangeName];
|
|
31052
31239
|
if (frameName)
|
|
31053
31240
|
parts.push(frameName);
|
|
@@ -31090,7 +31277,7 @@ class PriceMetaService {
|
|
|
31090
31277
|
* Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
|
|
31091
31278
|
* Instances are cached until clear() is called.
|
|
31092
31279
|
*/
|
|
31093
|
-
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31280
|
+
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
|
|
31094
31281
|
/**
|
|
31095
31282
|
* Returns the current market price for the given symbol and context.
|
|
31096
31283
|
*
|
|
@@ -31119,10 +31306,10 @@ class PriceMetaService {
|
|
|
31119
31306
|
if (source.data) {
|
|
31120
31307
|
return source.data;
|
|
31121
31308
|
}
|
|
31122
|
-
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$
|
|
31309
|
+
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...`);
|
|
31123
31310
|
const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
|
|
31124
31311
|
if (typeof currentPrice === "symbol") {
|
|
31125
|
-
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$
|
|
31312
|
+
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31126
31313
|
}
|
|
31127
31314
|
return currentPrice;
|
|
31128
31315
|
};
|
|
@@ -31164,7 +31351,7 @@ class PriceMetaService {
|
|
|
31164
31351
|
this.getSource.clear();
|
|
31165
31352
|
return;
|
|
31166
31353
|
}
|
|
31167
|
-
const key = CREATE_KEY_FN$
|
|
31354
|
+
const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31168
31355
|
this.getSource.clear(key);
|
|
31169
31356
|
};
|
|
31170
31357
|
}
|
|
@@ -31182,7 +31369,7 @@ const LISTEN_TIMEOUT = 120000;
|
|
|
31182
31369
|
* @param backtest - Whether running in backtest mode
|
|
31183
31370
|
* @returns Unique string key for memoization
|
|
31184
31371
|
*/
|
|
31185
|
-
const CREATE_KEY_FN$
|
|
31372
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31186
31373
|
const parts = [symbol, strategyName, exchangeName];
|
|
31187
31374
|
if (frameName)
|
|
31188
31375
|
parts.push(frameName);
|
|
@@ -31225,7 +31412,7 @@ class TimeMetaService {
|
|
|
31225
31412
|
* Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
|
|
31226
31413
|
* Instances are cached until clear() is called.
|
|
31227
31414
|
*/
|
|
31228
|
-
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31415
|
+
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
|
|
31229
31416
|
/**
|
|
31230
31417
|
* Returns the current candle timestamp (in milliseconds) for the given symbol and context.
|
|
31231
31418
|
*
|
|
@@ -31253,10 +31440,10 @@ class TimeMetaService {
|
|
|
31253
31440
|
if (source.data) {
|
|
31254
31441
|
return source.data;
|
|
31255
31442
|
}
|
|
31256
|
-
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$
|
|
31443
|
+
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...`);
|
|
31257
31444
|
const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
|
|
31258
31445
|
if (typeof timestamp === "symbol") {
|
|
31259
|
-
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$
|
|
31446
|
+
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31260
31447
|
}
|
|
31261
31448
|
return timestamp;
|
|
31262
31449
|
};
|
|
@@ -31298,7 +31485,7 @@ class TimeMetaService {
|
|
|
31298
31485
|
this.getSource.clear();
|
|
31299
31486
|
return;
|
|
31300
31487
|
}
|
|
31301
|
-
const key = CREATE_KEY_FN$
|
|
31488
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31302
31489
|
this.getSource.clear(key);
|
|
31303
31490
|
};
|
|
31304
31491
|
}
|
|
@@ -31394,7 +31581,7 @@ class MaxDrawdownReportService {
|
|
|
31394
31581
|
/**
|
|
31395
31582
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
31396
31583
|
*/
|
|
31397
|
-
const CREATE_KEY_FN$
|
|
31584
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31398
31585
|
const parts = [symbol, strategyName, exchangeName];
|
|
31399
31586
|
if (frameName)
|
|
31400
31587
|
parts.push(frameName);
|
|
@@ -31518,7 +31705,7 @@ class ReportStorage {
|
|
|
31518
31705
|
class MaxDrawdownMarkdownService {
|
|
31519
31706
|
constructor() {
|
|
31520
31707
|
this.loggerService = inject(TYPES.loggerService);
|
|
31521
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31708
|
+
this.getStorage = functoolsKit.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));
|
|
31522
31709
|
/**
|
|
31523
31710
|
* Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
|
|
31524
31711
|
* events. Protected against multiple subscriptions via `singleshot`.
|
|
@@ -31597,7 +31784,7 @@ class MaxDrawdownMarkdownService {
|
|
|
31597
31784
|
this.clear = async (payload) => {
|
|
31598
31785
|
this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
|
|
31599
31786
|
if (payload) {
|
|
31600
|
-
const key = CREATE_KEY_FN$
|
|
31787
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31601
31788
|
this.getStorage.clear(key);
|
|
31602
31789
|
}
|
|
31603
31790
|
else {
|
|
@@ -31851,7 +32038,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
|
31851
32038
|
const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
|
|
31852
32039
|
const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
|
|
31853
32040
|
const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
|
|
31854
|
-
const MS_PER_MINUTE$
|
|
32041
|
+
const MS_PER_MINUTE$3 = 60000;
|
|
31855
32042
|
/**
|
|
31856
32043
|
* Gets current timestamp from execution context if available.
|
|
31857
32044
|
* Returns current Date() if no execution context exists (non-trading GUI).
|
|
@@ -31918,7 +32105,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
|
|
|
31918
32105
|
const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
|
|
31919
32106
|
throw new Error(`getAggregatedTrades is not implemented for this exchange`);
|
|
31920
32107
|
};
|
|
31921
|
-
const INTERVAL_MINUTES$
|
|
32108
|
+
const INTERVAL_MINUTES$3 = {
|
|
31922
32109
|
"1m": 1,
|
|
31923
32110
|
"3m": 3,
|
|
31924
32111
|
"5m": 5,
|
|
@@ -31950,7 +32137,7 @@ const INTERVAL_MINUTES$2 = {
|
|
|
31950
32137
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
31951
32138
|
*/
|
|
31952
32139
|
const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
|
|
31953
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
32140
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
|
|
31954
32141
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
31955
32142
|
};
|
|
31956
32143
|
/**
|
|
@@ -32090,11 +32277,11 @@ class ExchangeInstance {
|
|
|
32090
32277
|
limit,
|
|
32091
32278
|
});
|
|
32092
32279
|
const getCandles = this._methods.getCandles;
|
|
32093
|
-
const step = INTERVAL_MINUTES$
|
|
32280
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
32094
32281
|
if (!step) {
|
|
32095
32282
|
throw new Error(`ExchangeInstance unknown interval=${interval}`);
|
|
32096
32283
|
}
|
|
32097
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32284
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
32098
32285
|
// Align when down to interval boundary
|
|
32099
32286
|
const when = await GET_TIMESTAMP_FN();
|
|
32100
32287
|
const whenTimestamp = when.getTime();
|
|
@@ -32279,7 +32466,7 @@ class ExchangeInstance {
|
|
|
32279
32466
|
const when = await GET_TIMESTAMP_FN();
|
|
32280
32467
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
32281
32468
|
const to = new Date(alignedTo);
|
|
32282
|
-
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
32469
|
+
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
|
|
32283
32470
|
const isBacktest = await GET_BACKTEST_FN();
|
|
32284
32471
|
return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
|
|
32285
32472
|
};
|
|
@@ -32311,7 +32498,7 @@ class ExchangeInstance {
|
|
|
32311
32498
|
const when = await GET_TIMESTAMP_FN();
|
|
32312
32499
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
32313
32500
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
|
|
32314
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
32501
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$3 - MS_PER_MINUTE$3;
|
|
32315
32502
|
const isBacktest = await GET_BACKTEST_FN();
|
|
32316
32503
|
// No limit: fetch a single window and return as-is
|
|
32317
32504
|
if (limit === undefined) {
|
|
@@ -32374,11 +32561,11 @@ class ExchangeInstance {
|
|
|
32374
32561
|
sDate,
|
|
32375
32562
|
eDate,
|
|
32376
32563
|
});
|
|
32377
|
-
const step = INTERVAL_MINUTES$
|
|
32564
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
32378
32565
|
if (!step) {
|
|
32379
32566
|
throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
|
|
32380
32567
|
}
|
|
32381
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32568
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
32382
32569
|
const when = await GET_TIMESTAMP_FN();
|
|
32383
32570
|
const nowTimestamp = when.getTime();
|
|
32384
32571
|
const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
|
|
@@ -32668,8 +32855,8 @@ const Exchange = new ExchangeUtils();
|
|
|
32668
32855
|
|
|
32669
32856
|
const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
|
|
32670
32857
|
const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
|
|
32671
|
-
const MS_PER_MINUTE$
|
|
32672
|
-
const INTERVAL_MINUTES$
|
|
32858
|
+
const MS_PER_MINUTE$2 = 60000;
|
|
32859
|
+
const INTERVAL_MINUTES$2 = {
|
|
32673
32860
|
"1m": 1,
|
|
32674
32861
|
"3m": 3,
|
|
32675
32862
|
"5m": 5,
|
|
@@ -32684,7 +32871,7 @@ const INTERVAL_MINUTES$1 = {
|
|
|
32684
32871
|
"1w": 10080,
|
|
32685
32872
|
};
|
|
32686
32873
|
const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
|
|
32687
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
32874
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
|
|
32688
32875
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
32689
32876
|
};
|
|
32690
32877
|
const BAR_LENGTH = 30;
|
|
@@ -32709,11 +32896,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
|
|
|
32709
32896
|
async function checkCandles(params) {
|
|
32710
32897
|
const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
|
|
32711
32898
|
backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
|
|
32712
|
-
const step = INTERVAL_MINUTES$
|
|
32899
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
32713
32900
|
if (!step) {
|
|
32714
32901
|
throw new Error(`checkCandles: unsupported interval=${interval}`);
|
|
32715
32902
|
}
|
|
32716
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32903
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
32717
32904
|
const dir = path.join(baseDir, exchangeName, symbol, interval);
|
|
32718
32905
|
const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
32719
32906
|
const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -32783,11 +32970,11 @@ async function warmCandles(params) {
|
|
|
32783
32970
|
from,
|
|
32784
32971
|
to,
|
|
32785
32972
|
});
|
|
32786
|
-
const step = INTERVAL_MINUTES$
|
|
32973
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
32787
32974
|
if (!step) {
|
|
32788
32975
|
throw new Error(`warmCandles: unsupported interval=${interval}`);
|
|
32789
32976
|
}
|
|
32790
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32977
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
32791
32978
|
const instance = new ExchangeInstance(exchangeName);
|
|
32792
32979
|
const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
32793
32980
|
const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -44196,7 +44383,7 @@ const createSearchIndex = () => {
|
|
|
44196
44383
|
return { upsert, remove, list, search, read };
|
|
44197
44384
|
};
|
|
44198
44385
|
|
|
44199
|
-
const CREATE_KEY_FN$
|
|
44386
|
+
const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}-${bucketName}`;
|
|
44200
44387
|
const LIST_MEMORY_FN = ({ id, content }) => ({
|
|
44201
44388
|
memoryId: id,
|
|
44202
44389
|
content: content,
|
|
@@ -44518,7 +44705,7 @@ class MemoryDummyInstance {
|
|
|
44518
44705
|
class MemoryAdapter {
|
|
44519
44706
|
constructor() {
|
|
44520
44707
|
this.MemoryFactory = MemoryPersistInstance;
|
|
44521
|
-
this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$
|
|
44708
|
+
this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
|
|
44522
44709
|
/**
|
|
44523
44710
|
* Activates the adapter by subscribing to signal lifecycle events.
|
|
44524
44711
|
* Clears memoized instances for a signalId when it is cancelled or closed,
|
|
@@ -44529,7 +44716,7 @@ class MemoryAdapter {
|
|
|
44529
44716
|
this.enable = functoolsKit.singleshot(() => {
|
|
44530
44717
|
backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_ENABLE);
|
|
44531
44718
|
const handleDispose = (signalId) => {
|
|
44532
|
-
const prefix = CREATE_KEY_FN$
|
|
44719
|
+
const prefix = CREATE_KEY_FN$4(signalId, "");
|
|
44533
44720
|
for (const key of this.getInstance.keys()) {
|
|
44534
44721
|
if (key.startsWith(prefix)) {
|
|
44535
44722
|
const instance = this.getInstance.get(key);
|
|
@@ -44574,7 +44761,7 @@ class MemoryAdapter {
|
|
|
44574
44761
|
bucketName: dto.bucketName,
|
|
44575
44762
|
memoryId: dto.memoryId,
|
|
44576
44763
|
});
|
|
44577
|
-
const key = CREATE_KEY_FN$
|
|
44764
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44578
44765
|
const isInitial = !this.getInstance.has(key);
|
|
44579
44766
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44580
44767
|
await instance.waitForInit(isInitial);
|
|
@@ -44596,7 +44783,7 @@ class MemoryAdapter {
|
|
|
44596
44783
|
bucketName: dto.bucketName,
|
|
44597
44784
|
query: dto.query,
|
|
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);
|
|
@@ -44616,7 +44803,7 @@ class MemoryAdapter {
|
|
|
44616
44803
|
signalId: dto.signalId,
|
|
44617
44804
|
bucketName: dto.bucketName,
|
|
44618
44805
|
});
|
|
44619
|
-
const key = CREATE_KEY_FN$
|
|
44806
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44620
44807
|
const isInitial = !this.getInstance.has(key);
|
|
44621
44808
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44622
44809
|
await instance.waitForInit(isInitial);
|
|
@@ -44637,7 +44824,7 @@ class MemoryAdapter {
|
|
|
44637
44824
|
bucketName: dto.bucketName,
|
|
44638
44825
|
memoryId: dto.memoryId,
|
|
44639
44826
|
});
|
|
44640
|
-
const key = CREATE_KEY_FN$
|
|
44827
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44641
44828
|
const isInitial = !this.getInstance.has(key);
|
|
44642
44829
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44643
44830
|
await instance.waitForInit(isInitial);
|
|
@@ -44660,7 +44847,7 @@ class MemoryAdapter {
|
|
|
44660
44847
|
bucketName: dto.bucketName,
|
|
44661
44848
|
memoryId: dto.memoryId,
|
|
44662
44849
|
});
|
|
44663
|
-
const key = CREATE_KEY_FN$
|
|
44850
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44664
44851
|
const isInitial = !this.getInstance.has(key);
|
|
44665
44852
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44666
44853
|
await instance.waitForInit(isInitial);
|
|
@@ -44952,7 +45139,7 @@ async function removeMemory(dto) {
|
|
|
44952
45139
|
});
|
|
44953
45140
|
}
|
|
44954
45141
|
|
|
44955
|
-
const CREATE_KEY_FN$
|
|
45142
|
+
const CREATE_KEY_FN$3 = (signalId, bucketName) => `${signalId}-${bucketName}`;
|
|
44956
45143
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_AGENT = "DumpMemoryInstance.dumpAgentAnswer";
|
|
44957
45144
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_RECORD = "DumpMemoryInstance.dumpRecord";
|
|
44958
45145
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_TABLE = "DumpMemoryInstance.dumpTable";
|
|
@@ -45572,7 +45759,7 @@ class DumpDummyInstance {
|
|
|
45572
45759
|
class DumpAdapter {
|
|
45573
45760
|
constructor() {
|
|
45574
45761
|
this.DumpFactory = DumpMarkdownInstance;
|
|
45575
|
-
this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$
|
|
45762
|
+
this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.DumpFactory, [signalId, bucketName]));
|
|
45576
45763
|
/**
|
|
45577
45764
|
* Activates the adapter by subscribing to signal lifecycle events.
|
|
45578
45765
|
* Clears memoized instances for a signalId when it is cancelled or closed,
|
|
@@ -45583,7 +45770,7 @@ class DumpAdapter {
|
|
|
45583
45770
|
this.enable = functoolsKit.singleshot(() => {
|
|
45584
45771
|
backtest.loggerService.info(DUMP_ADAPTER_METHOD_NAME_ENABLE);
|
|
45585
45772
|
const handleDispose = (signalId) => {
|
|
45586
|
-
const prefix = CREATE_KEY_FN$
|
|
45773
|
+
const prefix = CREATE_KEY_FN$3(signalId, "");
|
|
45587
45774
|
for (const key of this.getInstance.keys()) {
|
|
45588
45775
|
if (key.startsWith(prefix)) {
|
|
45589
45776
|
const instance = this.getInstance.get(key);
|
|
@@ -48517,6 +48704,49 @@ PositionSizeUtils.atrBased = async (symbol, accountBalance, priceOpen, atr, cont
|
|
|
48517
48704
|
};
|
|
48518
48705
|
const PositionSize = PositionSizeUtils;
|
|
48519
48706
|
|
|
48707
|
+
const METHOD_NAME_MOONBAG = "Position.moonbag";
|
|
48708
|
+
const METHOD_NAME_BRACKET = "Position.bracket";
|
|
48709
|
+
/**
|
|
48710
|
+
* Utilities for calculating take profit and stop loss price levels.
|
|
48711
|
+
* Automatically inverts direction based on position type (long/short).
|
|
48712
|
+
*/
|
|
48713
|
+
class Position {
|
|
48714
|
+
}
|
|
48715
|
+
/**
|
|
48716
|
+
* Calculates levels for the "moonbag" strategy — fixed TP at 50% from the current price.
|
|
48717
|
+
* @param dto.position - position type: "long" or "short"
|
|
48718
|
+
* @param dto.currentPrice - current asset price
|
|
48719
|
+
* @param dto.percentStopLoss - stop loss percentage from 0 to 100
|
|
48720
|
+
* @returns priceTakeProfit and priceStopLoss in fiat
|
|
48721
|
+
*/
|
|
48722
|
+
Position.moonbag = (dto) => {
|
|
48723
|
+
backtest.loggerService.log(METHOD_NAME_MOONBAG, { dto });
|
|
48724
|
+
const percentTakeProfit = 50;
|
|
48725
|
+
const sign = dto.position === "long" ? 1 : -1;
|
|
48726
|
+
return {
|
|
48727
|
+
position: dto.position,
|
|
48728
|
+
priceTakeProfit: dto.currentPrice * (1 + sign * percentTakeProfit / 100),
|
|
48729
|
+
priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
|
|
48730
|
+
};
|
|
48731
|
+
};
|
|
48732
|
+
/**
|
|
48733
|
+
* Calculates levels for a bracket order with custom TP and SL.
|
|
48734
|
+
* @param dto.position - position type: "long" or "short"
|
|
48735
|
+
* @param dto.currentPrice - current asset price
|
|
48736
|
+
* @param dto.percentStopLoss - stop loss percentage from 0 to 100
|
|
48737
|
+
* @param dto.percentTakeProfit - take profit percentage from 0 to 100
|
|
48738
|
+
* @returns priceTakeProfit and priceStopLoss in fiat
|
|
48739
|
+
*/
|
|
48740
|
+
Position.bracket = (dto) => {
|
|
48741
|
+
backtest.loggerService.log(METHOD_NAME_BRACKET, { dto });
|
|
48742
|
+
const sign = dto.position === "long" ? 1 : -1;
|
|
48743
|
+
return {
|
|
48744
|
+
position: dto.position,
|
|
48745
|
+
priceTakeProfit: dto.currentPrice * (1 + sign * dto.percentTakeProfit / 100),
|
|
48746
|
+
priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
|
|
48747
|
+
};
|
|
48748
|
+
};
|
|
48749
|
+
|
|
48520
48750
|
const PARTIAL_METHOD_NAME_GET_DATA = "PartialUtils.getData";
|
|
48521
48751
|
const PARTIAL_METHOD_NAME_GET_REPORT = "PartialUtils.getReport";
|
|
48522
48752
|
const PARTIAL_METHOD_NAME_DUMP = "PartialUtils.dump";
|
|
@@ -50719,7 +50949,7 @@ const StorageBacktest = new StorageBacktestAdapter();
|
|
|
50719
50949
|
* Generates a unique key for notification identification.
|
|
50720
50950
|
* @returns Random string identifier
|
|
50721
50951
|
*/
|
|
50722
|
-
const CREATE_KEY_FN$
|
|
50952
|
+
const CREATE_KEY_FN$2 = () => functoolsKit.randomString();
|
|
50723
50953
|
/**
|
|
50724
50954
|
* Creates a notification model from signal tick result.
|
|
50725
50955
|
* Handles opened, closed, scheduled, and cancelled signal actions.
|
|
@@ -50730,7 +50960,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50730
50960
|
if (data.action === "opened") {
|
|
50731
50961
|
return {
|
|
50732
50962
|
type: "signal.opened",
|
|
50733
|
-
id: CREATE_KEY_FN$
|
|
50963
|
+
id: CREATE_KEY_FN$2(),
|
|
50734
50964
|
timestamp: data.signal.pendingAt,
|
|
50735
50965
|
backtest: data.backtest,
|
|
50736
50966
|
symbol: data.symbol,
|
|
@@ -50764,7 +50994,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50764
50994
|
const durationMin = Math.round(durationMs / 60000);
|
|
50765
50995
|
return {
|
|
50766
50996
|
type: "signal.closed",
|
|
50767
|
-
id: CREATE_KEY_FN$
|
|
50997
|
+
id: CREATE_KEY_FN$2(),
|
|
50768
50998
|
timestamp: data.closeTimestamp,
|
|
50769
50999
|
backtest: data.backtest,
|
|
50770
51000
|
symbol: data.symbol,
|
|
@@ -50798,7 +51028,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50798
51028
|
if (data.action === "scheduled") {
|
|
50799
51029
|
return {
|
|
50800
51030
|
type: "signal.scheduled",
|
|
50801
|
-
id: CREATE_KEY_FN$
|
|
51031
|
+
id: CREATE_KEY_FN$2(),
|
|
50802
51032
|
timestamp: data.signal.scheduledAt,
|
|
50803
51033
|
backtest: data.backtest,
|
|
50804
51034
|
symbol: data.symbol,
|
|
@@ -50831,7 +51061,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50831
51061
|
const durationMin = Math.round(durationMs / 60000);
|
|
50832
51062
|
return {
|
|
50833
51063
|
type: "signal.cancelled",
|
|
50834
|
-
id: CREATE_KEY_FN$
|
|
51064
|
+
id: CREATE_KEY_FN$2(),
|
|
50835
51065
|
timestamp: data.closeTimestamp,
|
|
50836
51066
|
backtest: data.backtest,
|
|
50837
51067
|
symbol: data.symbol,
|
|
@@ -50864,7 +51094,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50864
51094
|
*/
|
|
50865
51095
|
const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
50866
51096
|
type: "partial_profit.available",
|
|
50867
|
-
id: CREATE_KEY_FN$
|
|
51097
|
+
id: CREATE_KEY_FN$2(),
|
|
50868
51098
|
timestamp: data.timestamp,
|
|
50869
51099
|
backtest: data.backtest,
|
|
50870
51100
|
symbol: data.symbol,
|
|
@@ -50899,7 +51129,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
|
50899
51129
|
*/
|
|
50900
51130
|
const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
50901
51131
|
type: "partial_loss.available",
|
|
50902
|
-
id: CREATE_KEY_FN$
|
|
51132
|
+
id: CREATE_KEY_FN$2(),
|
|
50903
51133
|
timestamp: data.timestamp,
|
|
50904
51134
|
backtest: data.backtest,
|
|
50905
51135
|
symbol: data.symbol,
|
|
@@ -50934,7 +51164,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
|
50934
51164
|
*/
|
|
50935
51165
|
const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
|
|
50936
51166
|
type: "breakeven.available",
|
|
50937
|
-
id: CREATE_KEY_FN$
|
|
51167
|
+
id: CREATE_KEY_FN$2(),
|
|
50938
51168
|
timestamp: data.timestamp,
|
|
50939
51169
|
backtest: data.backtest,
|
|
50940
51170
|
symbol: data.symbol,
|
|
@@ -50972,7 +51202,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
50972
51202
|
if (data.action === "partial-profit") {
|
|
50973
51203
|
return {
|
|
50974
51204
|
type: "partial_profit.commit",
|
|
50975
|
-
id: CREATE_KEY_FN$
|
|
51205
|
+
id: CREATE_KEY_FN$2(),
|
|
50976
51206
|
timestamp: data.timestamp,
|
|
50977
51207
|
backtest: data.backtest,
|
|
50978
51208
|
symbol: data.symbol,
|
|
@@ -51004,7 +51234,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51004
51234
|
if (data.action === "partial-loss") {
|
|
51005
51235
|
return {
|
|
51006
51236
|
type: "partial_loss.commit",
|
|
51007
|
-
id: CREATE_KEY_FN$
|
|
51237
|
+
id: CREATE_KEY_FN$2(),
|
|
51008
51238
|
timestamp: data.timestamp,
|
|
51009
51239
|
backtest: data.backtest,
|
|
51010
51240
|
symbol: data.symbol,
|
|
@@ -51036,7 +51266,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51036
51266
|
if (data.action === "breakeven") {
|
|
51037
51267
|
return {
|
|
51038
51268
|
type: "breakeven.commit",
|
|
51039
|
-
id: CREATE_KEY_FN$
|
|
51269
|
+
id: CREATE_KEY_FN$2(),
|
|
51040
51270
|
timestamp: data.timestamp,
|
|
51041
51271
|
backtest: data.backtest,
|
|
51042
51272
|
symbol: data.symbol,
|
|
@@ -51067,7 +51297,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51067
51297
|
if (data.action === "trailing-stop") {
|
|
51068
51298
|
return {
|
|
51069
51299
|
type: "trailing_stop.commit",
|
|
51070
|
-
id: CREATE_KEY_FN$
|
|
51300
|
+
id: CREATE_KEY_FN$2(),
|
|
51071
51301
|
timestamp: data.timestamp,
|
|
51072
51302
|
backtest: data.backtest,
|
|
51073
51303
|
symbol: data.symbol,
|
|
@@ -51099,7 +51329,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51099
51329
|
if (data.action === "trailing-take") {
|
|
51100
51330
|
return {
|
|
51101
51331
|
type: "trailing_take.commit",
|
|
51102
|
-
id: CREATE_KEY_FN$
|
|
51332
|
+
id: CREATE_KEY_FN$2(),
|
|
51103
51333
|
timestamp: data.timestamp,
|
|
51104
51334
|
backtest: data.backtest,
|
|
51105
51335
|
symbol: data.symbol,
|
|
@@ -51131,7 +51361,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51131
51361
|
if (data.action === "activate-scheduled") {
|
|
51132
51362
|
return {
|
|
51133
51363
|
type: "activate_scheduled.commit",
|
|
51134
|
-
id: CREATE_KEY_FN$
|
|
51364
|
+
id: CREATE_KEY_FN$2(),
|
|
51135
51365
|
timestamp: data.timestamp,
|
|
51136
51366
|
backtest: data.backtest,
|
|
51137
51367
|
symbol: data.symbol,
|
|
@@ -51163,7 +51393,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51163
51393
|
if (data.action === "average-buy") {
|
|
51164
51394
|
return {
|
|
51165
51395
|
type: "average_buy.commit",
|
|
51166
|
-
id: CREATE_KEY_FN$
|
|
51396
|
+
id: CREATE_KEY_FN$2(),
|
|
51167
51397
|
timestamp: data.timestamp,
|
|
51168
51398
|
backtest: data.backtest,
|
|
51169
51399
|
symbol: data.symbol,
|
|
@@ -51196,7 +51426,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51196
51426
|
if (data.action === "cancel-scheduled") {
|
|
51197
51427
|
return {
|
|
51198
51428
|
type: "cancel_scheduled.commit",
|
|
51199
|
-
id: CREATE_KEY_FN$
|
|
51429
|
+
id: CREATE_KEY_FN$2(),
|
|
51200
51430
|
timestamp: data.timestamp,
|
|
51201
51431
|
backtest: data.backtest,
|
|
51202
51432
|
symbol: data.symbol,
|
|
@@ -51219,7 +51449,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51219
51449
|
if (data.action === "close-pending") {
|
|
51220
51450
|
return {
|
|
51221
51451
|
type: "close_pending.commit",
|
|
51222
|
-
id: CREATE_KEY_FN$
|
|
51452
|
+
id: CREATE_KEY_FN$2(),
|
|
51223
51453
|
timestamp: data.timestamp,
|
|
51224
51454
|
backtest: data.backtest,
|
|
51225
51455
|
symbol: data.symbol,
|
|
@@ -51251,7 +51481,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51251
51481
|
if (data.action === "signal-open") {
|
|
51252
51482
|
return {
|
|
51253
51483
|
type: "signal_sync.open",
|
|
51254
|
-
id: CREATE_KEY_FN$
|
|
51484
|
+
id: CREATE_KEY_FN$2(),
|
|
51255
51485
|
timestamp: data.timestamp,
|
|
51256
51486
|
backtest: data.backtest,
|
|
51257
51487
|
symbol: data.symbol,
|
|
@@ -51283,7 +51513,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51283
51513
|
if (data.action === "signal-close") {
|
|
51284
51514
|
return {
|
|
51285
51515
|
type: "signal_sync.close",
|
|
51286
|
-
id: CREATE_KEY_FN$
|
|
51516
|
+
id: CREATE_KEY_FN$2(),
|
|
51287
51517
|
timestamp: data.timestamp,
|
|
51288
51518
|
backtest: data.backtest,
|
|
51289
51519
|
symbol: data.symbol,
|
|
@@ -51321,7 +51551,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51321
51551
|
*/
|
|
51322
51552
|
const CREATE_RISK_NOTIFICATION_FN = (data) => ({
|
|
51323
51553
|
type: "risk.rejection",
|
|
51324
|
-
id: CREATE_KEY_FN$
|
|
51554
|
+
id: CREATE_KEY_FN$2(),
|
|
51325
51555
|
timestamp: data.timestamp,
|
|
51326
51556
|
backtest: data.backtest,
|
|
51327
51557
|
symbol: data.symbol,
|
|
@@ -51347,7 +51577,7 @@ const CREATE_RISK_NOTIFICATION_FN = (data) => ({
|
|
|
51347
51577
|
*/
|
|
51348
51578
|
const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51349
51579
|
type: "error.info",
|
|
51350
|
-
id: CREATE_KEY_FN$
|
|
51580
|
+
id: CREATE_KEY_FN$2(),
|
|
51351
51581
|
error: functoolsKit.errorData(error),
|
|
51352
51582
|
message: functoolsKit.getErrorMessage(error),
|
|
51353
51583
|
backtest: false,
|
|
@@ -51359,7 +51589,7 @@ const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
|
|
|
51359
51589
|
*/
|
|
51360
51590
|
const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51361
51591
|
type: "error.critical",
|
|
51362
|
-
id: CREATE_KEY_FN$
|
|
51592
|
+
id: CREATE_KEY_FN$2(),
|
|
51363
51593
|
error: functoolsKit.errorData(error),
|
|
51364
51594
|
message: functoolsKit.getErrorMessage(error),
|
|
51365
51595
|
backtest: false,
|
|
@@ -51371,7 +51601,7 @@ const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
|
|
|
51371
51601
|
*/
|
|
51372
51602
|
const CREATE_VALIDATION_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51373
51603
|
type: "error.validation",
|
|
51374
|
-
id: CREATE_KEY_FN$
|
|
51604
|
+
id: CREATE_KEY_FN$2(),
|
|
51375
51605
|
error: functoolsKit.errorData(error),
|
|
51376
51606
|
message: functoolsKit.getErrorMessage(error),
|
|
51377
51607
|
backtest: false,
|
|
@@ -52800,7 +53030,7 @@ const NotificationLive = new NotificationLiveAdapter();
|
|
|
52800
53030
|
*/
|
|
52801
53031
|
const NotificationBacktest = new NotificationBacktestAdapter();
|
|
52802
53032
|
|
|
52803
|
-
const CACHE_METHOD_NAME_RUN = "
|
|
53033
|
+
const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
|
|
52804
53034
|
const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
|
|
52805
53035
|
const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
|
|
52806
53036
|
const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
|
|
@@ -52809,8 +53039,8 @@ const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
|
|
|
52809
53039
|
const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
|
|
52810
53040
|
const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
|
|
52811
53041
|
const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
|
|
52812
|
-
const MS_PER_MINUTE = 60000;
|
|
52813
|
-
const INTERVAL_MINUTES = {
|
|
53042
|
+
const MS_PER_MINUTE$1 = 60000;
|
|
53043
|
+
const INTERVAL_MINUTES$1 = {
|
|
52814
53044
|
"1m": 1,
|
|
52815
53045
|
"3m": 3,
|
|
52816
53046
|
"5m": 5,
|
|
@@ -52844,12 +53074,12 @@ const INTERVAL_MINUTES = {
|
|
|
52844
53074
|
* // Returns timestamp for 2025-10-01T01:00:00Z
|
|
52845
53075
|
* ```
|
|
52846
53076
|
*/
|
|
52847
|
-
const align = (timestamp, interval) => {
|
|
52848
|
-
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
53077
|
+
const align$1 = (timestamp, interval) => {
|
|
53078
|
+
const intervalMinutes = INTERVAL_MINUTES$1[interval];
|
|
52849
53079
|
if (!intervalMinutes) {
|
|
52850
53080
|
throw new Error(`align: unknown interval=${interval}`);
|
|
52851
53081
|
}
|
|
52852
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
53082
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
|
|
52853
53083
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
52854
53084
|
};
|
|
52855
53085
|
/**
|
|
@@ -52860,7 +53090,7 @@ const align = (timestamp, interval) => {
|
|
|
52860
53090
|
* @param backtest - Whether running in backtest mode
|
|
52861
53091
|
* @returns Cache key string
|
|
52862
53092
|
*/
|
|
52863
|
-
const CREATE_KEY_FN = (strategyName, exchangeName, frameName, backtest) => {
|
|
53093
|
+
const CREATE_KEY_FN$1 = (strategyName, exchangeName, frameName, backtest) => {
|
|
52864
53094
|
const parts = [strategyName, exchangeName];
|
|
52865
53095
|
if (frameName)
|
|
52866
53096
|
parts.push(frameName);
|
|
@@ -52884,16 +53114,16 @@ const NEVER_VALUE = Symbol("never");
|
|
|
52884
53114
|
*
|
|
52885
53115
|
* @example
|
|
52886
53116
|
* ```typescript
|
|
52887
|
-
* const instance = new
|
|
53117
|
+
* const instance = new CacheFnInstance(myExpensiveFunction, "1h");
|
|
52888
53118
|
* const result = instance.run(arg1, arg2); // Computed
|
|
52889
53119
|
* const result2 = instance.run(arg1, arg2); // Cached (within same hour)
|
|
52890
53120
|
* // After 1 hour passes
|
|
52891
53121
|
* const result3 = instance.run(arg1, arg2); // Recomputed
|
|
52892
53122
|
* ```
|
|
52893
53123
|
*/
|
|
52894
|
-
class
|
|
53124
|
+
class CacheFnInstance {
|
|
52895
53125
|
/**
|
|
52896
|
-
* Creates a new
|
|
53126
|
+
* Creates a new CacheFnInstance for a specific function and interval.
|
|
52897
53127
|
*
|
|
52898
53128
|
* @param fn - Function to cache
|
|
52899
53129
|
* @param interval - Candle interval for cache invalidation (e.g., "1m", "1h")
|
|
@@ -52930,7 +53160,7 @@ class CacheInstance {
|
|
|
52930
53160
|
*
|
|
52931
53161
|
* @example
|
|
52932
53162
|
* ```typescript
|
|
52933
|
-
* const instance = new
|
|
53163
|
+
* const instance = new CacheFnInstance(calculateIndicator, "15m");
|
|
52934
53164
|
* const result = instance.run("BTCUSDT", 100);
|
|
52935
53165
|
* console.log(result.value); // Calculated value
|
|
52936
53166
|
* console.log(result.when); // Cache timestamp
|
|
@@ -52938,26 +53168,26 @@ class CacheInstance {
|
|
|
52938
53168
|
*/
|
|
52939
53169
|
this.run = (...args) => {
|
|
52940
53170
|
backtest.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
|
|
52941
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
53171
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
52942
53172
|
{
|
|
52943
53173
|
if (!MethodContextService.hasContext()) {
|
|
52944
|
-
throw new Error("
|
|
53174
|
+
throw new Error("CacheFnInstance run requires method context");
|
|
52945
53175
|
}
|
|
52946
53176
|
if (!ExecutionContextService.hasContext()) {
|
|
52947
|
-
throw new Error("
|
|
53177
|
+
throw new Error("CacheFnInstance run requires execution context");
|
|
52948
53178
|
}
|
|
52949
53179
|
if (!step) {
|
|
52950
|
-
throw new Error(`
|
|
53180
|
+
throw new Error(`CacheFnInstance unknown cache ttl interval=${this.interval}`);
|
|
52951
53181
|
}
|
|
52952
53182
|
}
|
|
52953
|
-
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53183
|
+
const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
52954
53184
|
const argKey = String(this.key(args));
|
|
52955
53185
|
const key = `${contextKey}:${argKey}`;
|
|
52956
53186
|
const currentWhen = backtest.executionContextService.context.when;
|
|
52957
53187
|
const cached = this._cacheMap.get(key);
|
|
52958
53188
|
if (cached) {
|
|
52959
|
-
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
52960
|
-
const cachedAligned = align(cached.when.getTime(), this.interval);
|
|
53189
|
+
const currentAligned = align$1(currentWhen.getTime(), this.interval);
|
|
53190
|
+
const cachedAligned = align$1(cached.when.getTime(), this.interval);
|
|
52961
53191
|
if (currentAligned === cachedAligned) {
|
|
52962
53192
|
return cached;
|
|
52963
53193
|
}
|
|
@@ -52980,7 +53210,7 @@ class CacheInstance {
|
|
|
52980
53210
|
*
|
|
52981
53211
|
* @example
|
|
52982
53212
|
* ```typescript
|
|
52983
|
-
* const instance = new
|
|
53213
|
+
* const instance = new CacheFnInstance(calculateIndicator, "1h");
|
|
52984
53214
|
* const result1 = instance.run("BTCUSDT", 14); // Computed
|
|
52985
53215
|
* const result2 = instance.run("BTCUSDT", 14); // Cached
|
|
52986
53216
|
*
|
|
@@ -52990,7 +53220,7 @@ class CacheInstance {
|
|
|
52990
53220
|
* ```
|
|
52991
53221
|
*/
|
|
52992
53222
|
this.clear = () => {
|
|
52993
|
-
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53223
|
+
const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
52994
53224
|
const prefix = `${contextKey}:`;
|
|
52995
53225
|
for (const key of this._cacheMap.keys()) {
|
|
52996
53226
|
if (key.startsWith(prefix)) {
|
|
@@ -53010,7 +53240,7 @@ class CacheInstance {
|
|
|
53010
53240
|
*
|
|
53011
53241
|
* @example
|
|
53012
53242
|
* ```typescript
|
|
53013
|
-
* const instance = new
|
|
53243
|
+
* const instance = new CacheFnInstance(calculateIndicator, "1h");
|
|
53014
53244
|
* instance.run("BTCUSDT", 14); // Cached at 10:00
|
|
53015
53245
|
* instance.run("ETHUSDT", 14); // Cached at 10:00
|
|
53016
53246
|
* // Time passes to 11:00
|
|
@@ -53019,10 +53249,10 @@ class CacheInstance {
|
|
|
53019
53249
|
*/
|
|
53020
53250
|
this.gc = () => {
|
|
53021
53251
|
const currentWhen = backtest.executionContextService.context.when;
|
|
53022
|
-
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
53252
|
+
const currentAligned = align$1(currentWhen.getTime(), this.interval);
|
|
53023
53253
|
let removed = 0;
|
|
53024
53254
|
for (const [key, cached] of this._cacheMap.entries()) {
|
|
53025
|
-
const cachedAligned = align(cached.when.getTime(), this.interval);
|
|
53255
|
+
const cachedAligned = align$1(cached.when.getTime(), this.interval);
|
|
53026
53256
|
if (currentAligned !== cachedAligned) {
|
|
53027
53257
|
this._cacheMap.delete(key);
|
|
53028
53258
|
removed++;
|
|
@@ -53097,7 +53327,7 @@ class CacheFileInstance {
|
|
|
53097
53327
|
*/
|
|
53098
53328
|
this.run = async (...args) => {
|
|
53099
53329
|
backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
53100
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
53330
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
53101
53331
|
{
|
|
53102
53332
|
if (!MethodContextService.hasContext()) {
|
|
53103
53333
|
throw new Error("CacheFileInstance run requires method context");
|
|
@@ -53111,7 +53341,7 @@ class CacheFileInstance {
|
|
|
53111
53341
|
}
|
|
53112
53342
|
const [symbol, ...rest] = args;
|
|
53113
53343
|
const { when } = backtest.executionContextService.context;
|
|
53114
|
-
const alignedTs = align(when.getTime(), this.interval);
|
|
53344
|
+
const alignedTs = align$1(when.getTime(), this.interval);
|
|
53115
53345
|
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53116
53346
|
const entityKey = this.key([symbol, alignedTs, ...rest]);
|
|
53117
53347
|
const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
|
|
@@ -53119,9 +53349,19 @@ class CacheFileInstance {
|
|
|
53119
53349
|
return cached.data;
|
|
53120
53350
|
}
|
|
53121
53351
|
const result = await this.fn.call(null, ...args);
|
|
53122
|
-
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result }, bucket, entityKey);
|
|
53352
|
+
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
53123
53353
|
return result;
|
|
53124
53354
|
};
|
|
53355
|
+
/**
|
|
53356
|
+
* Soft-delete all persisted records for this instance's bucket.
|
|
53357
|
+
* After this call the next `run()` will recompute and re-cache the value.
|
|
53358
|
+
*/
|
|
53359
|
+
this.clear = async () => {
|
|
53360
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53361
|
+
for await (const key of PersistMeasureAdapter.listMeasureData(bucket)) {
|
|
53362
|
+
await PersistMeasureAdapter.removeMeasureData(bucket, key);
|
|
53363
|
+
}
|
|
53364
|
+
};
|
|
53125
53365
|
this.index = CacheFileInstance.createIndex();
|
|
53126
53366
|
}
|
|
53127
53367
|
}
|
|
@@ -53145,10 +53385,10 @@ CacheFileInstance._indexCounter = 0;
|
|
|
53145
53385
|
class CacheUtils {
|
|
53146
53386
|
constructor() {
|
|
53147
53387
|
/**
|
|
53148
|
-
* Memoized function to get or create
|
|
53388
|
+
* Memoized function to get or create CacheFnInstance for a function.
|
|
53149
53389
|
* Each function gets its own isolated cache instance.
|
|
53150
53390
|
*/
|
|
53151
|
-
this._getFnInstance = functoolsKit.memoize(([run]) => run, (run, interval, key) => new
|
|
53391
|
+
this._getFnInstance = functoolsKit.memoize(([run]) => run, (run, interval, key) => new CacheFnInstance(run, interval, key));
|
|
53152
53392
|
/**
|
|
53153
53393
|
* Memoized function to get or create CacheFileInstance for an async function.
|
|
53154
53394
|
* Each function gets its own isolated file-cache instance.
|
|
@@ -53257,31 +53497,34 @@ class CacheUtils {
|
|
|
53257
53497
|
*/
|
|
53258
53498
|
this.file = (run, context) => {
|
|
53259
53499
|
backtest.loggerService.info(CACHE_METHOD_NAME_FILE, { context });
|
|
53500
|
+
{
|
|
53501
|
+
this._getFileInstance(run, context.interval, context.name, context.key);
|
|
53502
|
+
}
|
|
53260
53503
|
const wrappedFn = (...args) => {
|
|
53261
53504
|
const instance = this._getFileInstance(run, context.interval, context.name, context.key);
|
|
53262
53505
|
return instance.run(...args);
|
|
53263
53506
|
};
|
|
53264
|
-
wrappedFn.clear = () => {
|
|
53507
|
+
wrappedFn.clear = async () => {
|
|
53265
53508
|
backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
|
|
53266
|
-
this._getFileInstance.
|
|
53509
|
+
await this._getFileInstance.get(run)?.clear();
|
|
53267
53510
|
};
|
|
53268
53511
|
return wrappedFn;
|
|
53269
53512
|
};
|
|
53270
53513
|
/**
|
|
53271
|
-
* Dispose (remove) the memoized
|
|
53514
|
+
* Dispose (remove) the memoized CacheFnInstance for a specific function.
|
|
53272
53515
|
*
|
|
53273
|
-
* Removes the
|
|
53516
|
+
* Removes the CacheFnInstance from the internal memoization cache, discarding all cached
|
|
53274
53517
|
* results across all contexts (all strategy/exchange/mode combinations) for that function.
|
|
53275
|
-
* The next call to the wrapped function will create a fresh
|
|
53518
|
+
* The next call to the wrapped function will create a fresh CacheFnInstance.
|
|
53276
53519
|
*
|
|
53277
53520
|
* @template T - Function type
|
|
53278
|
-
* @param run - Function whose
|
|
53521
|
+
* @param run - Function whose CacheFnInstance should be disposed.
|
|
53279
53522
|
*
|
|
53280
53523
|
* @example
|
|
53281
53524
|
* ```typescript
|
|
53282
53525
|
* const cachedFn = Cache.fn(calculateIndicator, { interval: "1h" });
|
|
53283
53526
|
*
|
|
53284
|
-
* // Dispose
|
|
53527
|
+
* // Dispose CacheFnInstance for a specific function
|
|
53285
53528
|
* Cache.dispose(calculateIndicator);
|
|
53286
53529
|
* ```
|
|
53287
53530
|
*/
|
|
@@ -53295,7 +53538,7 @@ class CacheUtils {
|
|
|
53295
53538
|
}
|
|
53296
53539
|
};
|
|
53297
53540
|
/**
|
|
53298
|
-
* Clears all memoized
|
|
53541
|
+
* Clears all memoized CacheFnInstance and CacheFileInstance objects.
|
|
53299
53542
|
* Call this when process.cwd() changes between strategy iterations
|
|
53300
53543
|
* so new instances are created with the updated base path.
|
|
53301
53544
|
*/
|
|
@@ -53325,6 +53568,426 @@ class CacheUtils {
|
|
|
53325
53568
|
*/
|
|
53326
53569
|
const Cache = new CacheUtils();
|
|
53327
53570
|
|
|
53571
|
+
const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
|
|
53572
|
+
const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
|
|
53573
|
+
const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
|
|
53574
|
+
const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
|
|
53575
|
+
const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
|
|
53576
|
+
const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
|
|
53577
|
+
const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
|
|
53578
|
+
const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
|
|
53579
|
+
const MS_PER_MINUTE = 60000;
|
|
53580
|
+
const INTERVAL_MINUTES = {
|
|
53581
|
+
"1m": 1,
|
|
53582
|
+
"3m": 3,
|
|
53583
|
+
"5m": 5,
|
|
53584
|
+
"15m": 15,
|
|
53585
|
+
"30m": 30,
|
|
53586
|
+
"1h": 60,
|
|
53587
|
+
"2h": 120,
|
|
53588
|
+
"4h": 240,
|
|
53589
|
+
"6h": 360,
|
|
53590
|
+
"8h": 480,
|
|
53591
|
+
"1d": 1440,
|
|
53592
|
+
"1w": 10080,
|
|
53593
|
+
};
|
|
53594
|
+
/**
|
|
53595
|
+
* Aligns timestamp down to the nearest interval boundary.
|
|
53596
|
+
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
53597
|
+
*
|
|
53598
|
+
* @param timestamp - Timestamp in milliseconds
|
|
53599
|
+
* @param interval - Candle interval
|
|
53600
|
+
* @returns Aligned timestamp rounded down to interval boundary
|
|
53601
|
+
* @throws Error if interval is unknown
|
|
53602
|
+
*
|
|
53603
|
+
* @example
|
|
53604
|
+
* ```typescript
|
|
53605
|
+
* // Align to 15-minute boundary
|
|
53606
|
+
* const aligned = align(new Date("2025-10-01T00:35:00Z").getTime(), "15m");
|
|
53607
|
+
* // Returns timestamp for 2025-10-01T00:30:00Z
|
|
53608
|
+
* ```
|
|
53609
|
+
*/
|
|
53610
|
+
const align = (timestamp, interval) => {
|
|
53611
|
+
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
53612
|
+
if (!intervalMinutes) {
|
|
53613
|
+
throw new Error(`align: unknown interval=${interval}`);
|
|
53614
|
+
}
|
|
53615
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
53616
|
+
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
53617
|
+
};
|
|
53618
|
+
/**
|
|
53619
|
+
* Build a context key string from strategy name, exchange name, frame name, and execution mode.
|
|
53620
|
+
*
|
|
53621
|
+
* @param strategyName - Name of the strategy
|
|
53622
|
+
* @param exchangeName - Name of the exchange
|
|
53623
|
+
* @param frameName - Name of the backtest frame (omitted in live mode)
|
|
53624
|
+
* @param isBacktest - Whether running in backtest mode
|
|
53625
|
+
* @returns Context key string
|
|
53626
|
+
*/
|
|
53627
|
+
const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
|
|
53628
|
+
const parts = [strategyName, exchangeName];
|
|
53629
|
+
if (frameName)
|
|
53630
|
+
parts.push(frameName);
|
|
53631
|
+
parts.push(isBacktest ? "backtest" : "live");
|
|
53632
|
+
return parts.join(":");
|
|
53633
|
+
};
|
|
53634
|
+
/**
|
|
53635
|
+
* Instance class for firing a function exactly once per interval boundary.
|
|
53636
|
+
*
|
|
53637
|
+
* On the first call within a new interval the wrapped function is invoked and
|
|
53638
|
+
* its result is returned. Every subsequent call within the same interval returns
|
|
53639
|
+
* `null` without invoking the function again.
|
|
53640
|
+
* If the function itself returns `null`, the interval countdown does not start —
|
|
53641
|
+
* the next call will retry the function.
|
|
53642
|
+
*
|
|
53643
|
+
* State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
|
|
53644
|
+
*
|
|
53645
|
+
* @example
|
|
53646
|
+
* ```typescript
|
|
53647
|
+
* const instance = new IntervalFnInstance(mySignalFn, "1h");
|
|
53648
|
+
* await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called)
|
|
53649
|
+
* await instance.run("BTCUSDT"); // → null (skipped, same interval)
|
|
53650
|
+
* // After 1 hour passes:
|
|
53651
|
+
* await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called again)
|
|
53652
|
+
* ```
|
|
53653
|
+
*/
|
|
53654
|
+
class IntervalFnInstance {
|
|
53655
|
+
/**
|
|
53656
|
+
* Creates a new IntervalFnInstance.
|
|
53657
|
+
*
|
|
53658
|
+
* @param fn - Signal function to fire once per interval
|
|
53659
|
+
* @param interval - Candle interval that controls the firing boundary
|
|
53660
|
+
*/
|
|
53661
|
+
constructor(fn, interval) {
|
|
53662
|
+
this.fn = fn;
|
|
53663
|
+
this.interval = interval;
|
|
53664
|
+
/** Stores the last aligned timestamp per context+symbol key. */
|
|
53665
|
+
this._stateMap = new Map();
|
|
53666
|
+
/**
|
|
53667
|
+
* Execute the signal function with once-per-interval enforcement.
|
|
53668
|
+
*
|
|
53669
|
+
* Algorithm:
|
|
53670
|
+
* 1. Align the current execution context `when` to the interval boundary.
|
|
53671
|
+
* 2. If the stored aligned timestamp for this context+symbol equals the current one → return `null`.
|
|
53672
|
+
* 3. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
|
|
53673
|
+
* the signal. If it returns `null`, leave state unchanged so the next call retries.
|
|
53674
|
+
*
|
|
53675
|
+
* Requires active method context and execution context.
|
|
53676
|
+
*
|
|
53677
|
+
* @param symbol - Trading pair symbol (e.g. "BTCUSDT")
|
|
53678
|
+
* @returns The signal returned by `fn` on the first non-null fire, `null` on all subsequent calls
|
|
53679
|
+
* within the same interval or when `fn` itself returned `null`
|
|
53680
|
+
* @throws Error if method context, execution context, or interval is missing
|
|
53681
|
+
*/
|
|
53682
|
+
this.run = async (symbol) => {
|
|
53683
|
+
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { symbol });
|
|
53684
|
+
const step = INTERVAL_MINUTES[this.interval];
|
|
53685
|
+
{
|
|
53686
|
+
if (!MethodContextService.hasContext()) {
|
|
53687
|
+
throw new Error("IntervalFnInstance run requires method context");
|
|
53688
|
+
}
|
|
53689
|
+
if (!ExecutionContextService.hasContext()) {
|
|
53690
|
+
throw new Error("IntervalFnInstance run requires execution context");
|
|
53691
|
+
}
|
|
53692
|
+
if (!step) {
|
|
53693
|
+
throw new Error(`IntervalFnInstance unknown interval=${this.interval}`);
|
|
53694
|
+
}
|
|
53695
|
+
}
|
|
53696
|
+
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53697
|
+
const key = `${contextKey}:${symbol}`;
|
|
53698
|
+
const currentWhen = backtest.executionContextService.context.when;
|
|
53699
|
+
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
53700
|
+
if (this._stateMap.get(key) === currentAligned) {
|
|
53701
|
+
return null;
|
|
53702
|
+
}
|
|
53703
|
+
const result = await this.fn(symbol, currentWhen);
|
|
53704
|
+
if (result !== null) {
|
|
53705
|
+
this._stateMap.set(key, currentAligned);
|
|
53706
|
+
}
|
|
53707
|
+
return result;
|
|
53708
|
+
};
|
|
53709
|
+
/**
|
|
53710
|
+
* Clear fired-interval state for the current execution context.
|
|
53711
|
+
*
|
|
53712
|
+
* Removes all entries for the current strategy/exchange/frame/mode combination
|
|
53713
|
+
* from this instance's state map. The next `run()` call will invoke the function again.
|
|
53714
|
+
*
|
|
53715
|
+
* Requires active method context and execution context.
|
|
53716
|
+
*/
|
|
53717
|
+
this.clear = () => {
|
|
53718
|
+
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53719
|
+
const prefix = `${contextKey}:`;
|
|
53720
|
+
for (const key of this._stateMap.keys()) {
|
|
53721
|
+
if (key.startsWith(prefix)) {
|
|
53722
|
+
this._stateMap.delete(key);
|
|
53723
|
+
}
|
|
53724
|
+
}
|
|
53725
|
+
};
|
|
53726
|
+
}
|
|
53727
|
+
}
|
|
53728
|
+
/**
|
|
53729
|
+
* Instance class for firing an async function exactly once per interval boundary,
|
|
53730
|
+
* with the fired state persisted to disk via `PersistIntervalAdapter`.
|
|
53731
|
+
*
|
|
53732
|
+
* On the first call within a new interval the wrapped function is invoked.
|
|
53733
|
+
* If it returns a non-null signal, that result is written to disk and returned.
|
|
53734
|
+
* Every subsequent call within the same interval returns `null` (record exists on disk).
|
|
53735
|
+
* If the function returns `null`, nothing is written and the next call retries.
|
|
53736
|
+
*
|
|
53737
|
+
* Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
|
|
53738
|
+
*
|
|
53739
|
+
* @template T - Async function type: `(symbol: string, ...args) => Promise<ISignalIntervalDto | null>`
|
|
53740
|
+
*
|
|
53741
|
+
* @example
|
|
53742
|
+
* ```typescript
|
|
53743
|
+
* const instance = new IntervalFileInstance(fetchSignal, "1h", "mySignal");
|
|
53744
|
+
* await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called, result written to disk)
|
|
53745
|
+
* await instance.run("BTCUSDT"); // → null (record exists, already fired)
|
|
53746
|
+
* ```
|
|
53747
|
+
*/
|
|
53748
|
+
class IntervalFileInstance {
|
|
53749
|
+
/**
|
|
53750
|
+
* Allocates a new unique index. Called once in the constructor to give each
|
|
53751
|
+
* IntervalFileInstance its own namespace in the persistent key space.
|
|
53752
|
+
*/
|
|
53753
|
+
static createIndex() {
|
|
53754
|
+
return IntervalFileInstance._indexCounter++;
|
|
53755
|
+
}
|
|
53756
|
+
/**
|
|
53757
|
+
* Resets the index counter to zero.
|
|
53758
|
+
* Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
|
|
53759
|
+
*/
|
|
53760
|
+
static clearCounter() {
|
|
53761
|
+
IntervalFileInstance._indexCounter = 0;
|
|
53762
|
+
}
|
|
53763
|
+
/**
|
|
53764
|
+
* Creates a new IntervalFileInstance.
|
|
53765
|
+
*
|
|
53766
|
+
* @param fn - Async signal function to fire once per interval
|
|
53767
|
+
* @param interval - Candle interval that controls the firing boundary
|
|
53768
|
+
* @param name - Human-readable bucket name used as the directory prefix
|
|
53769
|
+
*/
|
|
53770
|
+
constructor(fn, interval, name) {
|
|
53771
|
+
this.fn = fn;
|
|
53772
|
+
this.interval = interval;
|
|
53773
|
+
this.name = name;
|
|
53774
|
+
/**
|
|
53775
|
+
* Execute the async signal function with persistent once-per-interval enforcement.
|
|
53776
|
+
*
|
|
53777
|
+
* Algorithm:
|
|
53778
|
+
* 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
|
|
53779
|
+
* 2. Align execution context `when` to interval boundary → `alignedTs`.
|
|
53780
|
+
* 3. Build entity key = `${symbol}_${alignedTs}`.
|
|
53781
|
+
* 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
|
|
53782
|
+
* 5. On hit — return `null` (interval already fired).
|
|
53783
|
+
* 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
|
|
53784
|
+
*
|
|
53785
|
+
* Requires active method context and execution context.
|
|
53786
|
+
*
|
|
53787
|
+
* @param args - Arguments forwarded to the wrapped function (first must be `symbol: string`)
|
|
53788
|
+
* @returns The signal on the first non-null fire, `null` if already fired this interval
|
|
53789
|
+
* or if `fn` itself returned `null`
|
|
53790
|
+
* @throws Error if method context, execution context, or interval is missing
|
|
53791
|
+
*/
|
|
53792
|
+
this.run = async (...args) => {
|
|
53793
|
+
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
53794
|
+
const step = INTERVAL_MINUTES[this.interval];
|
|
53795
|
+
{
|
|
53796
|
+
if (!MethodContextService.hasContext()) {
|
|
53797
|
+
throw new Error("IntervalFileInstance run requires method context");
|
|
53798
|
+
}
|
|
53799
|
+
if (!ExecutionContextService.hasContext()) {
|
|
53800
|
+
throw new Error("IntervalFileInstance run requires execution context");
|
|
53801
|
+
}
|
|
53802
|
+
if (!step) {
|
|
53803
|
+
throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
|
|
53804
|
+
}
|
|
53805
|
+
}
|
|
53806
|
+
const [symbol] = args;
|
|
53807
|
+
const { when } = backtest.executionContextService.context;
|
|
53808
|
+
const alignedTs = align(when.getTime(), this.interval);
|
|
53809
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53810
|
+
const entityKey = `${symbol}_${alignedTs}`;
|
|
53811
|
+
const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
|
|
53812
|
+
if (cached !== null) {
|
|
53813
|
+
return null;
|
|
53814
|
+
}
|
|
53815
|
+
const result = await this.fn.call(null, ...args);
|
|
53816
|
+
if (result !== null) {
|
|
53817
|
+
await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
53818
|
+
}
|
|
53819
|
+
return result;
|
|
53820
|
+
};
|
|
53821
|
+
/**
|
|
53822
|
+
* Soft-delete all persisted records for this instance's bucket.
|
|
53823
|
+
* After this call the function will fire again on the next `run()`.
|
|
53824
|
+
*/
|
|
53825
|
+
this.clear = async () => {
|
|
53826
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53827
|
+
for await (const key of PersistIntervalAdapter.listIntervalData(bucket)) {
|
|
53828
|
+
await PersistIntervalAdapter.removeIntervalData(bucket, key);
|
|
53829
|
+
}
|
|
53830
|
+
};
|
|
53831
|
+
this.index = IntervalFileInstance.createIndex();
|
|
53832
|
+
}
|
|
53833
|
+
}
|
|
53834
|
+
/** Global counter — incremented once per IntervalFileInstance construction. */
|
|
53835
|
+
IntervalFileInstance._indexCounter = 0;
|
|
53836
|
+
/**
|
|
53837
|
+
* Utility class for wrapping signal functions with once-per-interval firing.
|
|
53838
|
+
* Provides two modes: in-memory (`fn`) and persistent file-based (`file`).
|
|
53839
|
+
* Exported as singleton instance `Interval` for convenient usage.
|
|
53840
|
+
*
|
|
53841
|
+
* @example
|
|
53842
|
+
* ```typescript
|
|
53843
|
+
* import { Interval } from "./classes/Interval";
|
|
53844
|
+
*
|
|
53845
|
+
* const fireOncePerHour = Interval.fn(mySignalFn, { interval: "1h" });
|
|
53846
|
+
* await fireOncePerHour("BTCUSDT", when); // fn called — returns its result
|
|
53847
|
+
* await fireOncePerHour("BTCUSDT", when); // returns null (same interval)
|
|
53848
|
+
* ```
|
|
53849
|
+
*/
|
|
53850
|
+
class IntervalUtils {
|
|
53851
|
+
constructor() {
|
|
53852
|
+
/**
|
|
53853
|
+
* Memoized factory to get or create an `IntervalFnInstance` for a function.
|
|
53854
|
+
* Each function reference gets its own isolated instance.
|
|
53855
|
+
*/
|
|
53856
|
+
this._getInstance = functoolsKit.memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
|
|
53857
|
+
/**
|
|
53858
|
+
* Memoized factory to get or create an `IntervalFileInstance` for an async function.
|
|
53859
|
+
* Each function reference gets its own isolated persistent instance.
|
|
53860
|
+
*/
|
|
53861
|
+
this._getFileInstance = functoolsKit.memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
|
|
53862
|
+
/**
|
|
53863
|
+
* Wrap a signal function with in-memory once-per-interval firing.
|
|
53864
|
+
*
|
|
53865
|
+
* Returns a wrapped version of the function that fires at most once per interval boundary.
|
|
53866
|
+
* If the function returns `null`, the countdown does not start and the next call retries.
|
|
53867
|
+
*
|
|
53868
|
+
* The `run` function reference is used as the memoization key for the underlying
|
|
53869
|
+
* `IntervalFnInstance`, so each unique function reference gets its own isolated instance.
|
|
53870
|
+
*
|
|
53871
|
+
* @param run - Signal function to wrap
|
|
53872
|
+
* @param context.interval - Candle interval that controls the firing boundary
|
|
53873
|
+
* @returns Wrapped function with the same signature as `TIntervalFn`, plus a `clear()` method
|
|
53874
|
+
*
|
|
53875
|
+
* @example
|
|
53876
|
+
* ```typescript
|
|
53877
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
|
|
53878
|
+
*
|
|
53879
|
+
* await fireOnce("BTCUSDT", when); // → signal or null (fn called)
|
|
53880
|
+
* await fireOnce("BTCUSDT", when); // → null (same interval, skipped)
|
|
53881
|
+
* ```
|
|
53882
|
+
*/
|
|
53883
|
+
this.fn = (run, context) => {
|
|
53884
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
|
|
53885
|
+
const wrappedFn = (symbol, _when) => {
|
|
53886
|
+
const instance = this._getInstance(run, context.interval);
|
|
53887
|
+
return instance.run(symbol);
|
|
53888
|
+
};
|
|
53889
|
+
wrappedFn.clear = () => {
|
|
53890
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
|
|
53891
|
+
if (!MethodContextService.hasContext()) {
|
|
53892
|
+
backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
|
|
53893
|
+
return;
|
|
53894
|
+
}
|
|
53895
|
+
if (!ExecutionContextService.hasContext()) {
|
|
53896
|
+
backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
|
|
53897
|
+
return;
|
|
53898
|
+
}
|
|
53899
|
+
this._getInstance.get(run)?.clear();
|
|
53900
|
+
};
|
|
53901
|
+
return wrappedFn;
|
|
53902
|
+
};
|
|
53903
|
+
/**
|
|
53904
|
+
* Wrap an async signal function with persistent file-based once-per-interval firing.
|
|
53905
|
+
*
|
|
53906
|
+
* Returns a wrapped version of the function that reads from disk on hit (returns `null`)
|
|
53907
|
+
* and writes the fired signal to disk on the first successful fire.
|
|
53908
|
+
* Fired state survives process restarts.
|
|
53909
|
+
*
|
|
53910
|
+
* The `run` function reference is used as the memoization key for the underlying
|
|
53911
|
+
* `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
|
|
53912
|
+
*
|
|
53913
|
+
* @template T - Async function type to wrap
|
|
53914
|
+
* @param run - Async signal function to wrap with persistent once-per-interval firing
|
|
53915
|
+
* @param context.interval - Candle interval that controls the firing boundary
|
|
53916
|
+
* @param context.name - Human-readable bucket name; becomes the directory prefix
|
|
53917
|
+
* @returns Wrapped function with the same signature as `T`, plus an async `clear()` method
|
|
53918
|
+
* that deletes persisted records from disk and disposes the memoized instance
|
|
53919
|
+
*
|
|
53920
|
+
* @example
|
|
53921
|
+
* ```typescript
|
|
53922
|
+
* const fetchSignal = async (symbol: string, period: number) => { ... };
|
|
53923
|
+
* const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
|
|
53924
|
+
* await fireOnce.clear(); // delete disk records so the function fires again next call
|
|
53925
|
+
* ```
|
|
53926
|
+
*/
|
|
53927
|
+
this.file = (run, context) => {
|
|
53928
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
|
|
53929
|
+
{
|
|
53930
|
+
this._getFileInstance(run, context.interval, context.name);
|
|
53931
|
+
}
|
|
53932
|
+
const wrappedFn = (...args) => {
|
|
53933
|
+
const instance = this._getFileInstance(run, context.interval, context.name);
|
|
53934
|
+
return instance.run(...args);
|
|
53935
|
+
};
|
|
53936
|
+
wrappedFn.clear = async () => {
|
|
53937
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
|
|
53938
|
+
await this._getFileInstance.get(run)?.clear();
|
|
53939
|
+
};
|
|
53940
|
+
return wrappedFn;
|
|
53941
|
+
};
|
|
53942
|
+
/**
|
|
53943
|
+
* Dispose (remove) the memoized `IntervalFnInstance` for a specific function.
|
|
53944
|
+
*
|
|
53945
|
+
* Removes the instance from the internal memoization cache, discarding all in-memory
|
|
53946
|
+
* fired-interval state across all contexts for that function.
|
|
53947
|
+
* The next call to the wrapped function will create a fresh `IntervalFnInstance`.
|
|
53948
|
+
*
|
|
53949
|
+
* @param run - Function whose `IntervalFnInstance` should be disposed
|
|
53950
|
+
*
|
|
53951
|
+
* @example
|
|
53952
|
+
* ```typescript
|
|
53953
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
|
|
53954
|
+
* Interval.dispose(mySignalFn);
|
|
53955
|
+
* ```
|
|
53956
|
+
*/
|
|
53957
|
+
this.dispose = (run) => {
|
|
53958
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
|
|
53959
|
+
this._getInstance.clear(run);
|
|
53960
|
+
};
|
|
53961
|
+
/**
|
|
53962
|
+
* Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects and
|
|
53963
|
+
* resets the `IntervalFileInstance` index counter.
|
|
53964
|
+
* Call this when `process.cwd()` changes between strategy iterations
|
|
53965
|
+
* so new instances are created with the updated base path.
|
|
53966
|
+
*/
|
|
53967
|
+
this.clear = () => {
|
|
53968
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
|
|
53969
|
+
this._getInstance.clear();
|
|
53970
|
+
this._getFileInstance.clear();
|
|
53971
|
+
IntervalFileInstance.clearCounter();
|
|
53972
|
+
};
|
|
53973
|
+
}
|
|
53974
|
+
}
|
|
53975
|
+
/**
|
|
53976
|
+
* Singleton instance of `IntervalUtils` for convenient once-per-interval signal firing.
|
|
53977
|
+
*
|
|
53978
|
+
* @example
|
|
53979
|
+
* ```typescript
|
|
53980
|
+
* import { Interval } from "./classes/Interval";
|
|
53981
|
+
*
|
|
53982
|
+
* // In-memory: fires once per hour, resets on process restart
|
|
53983
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
|
|
53984
|
+
*
|
|
53985
|
+
* // Persistent: fired state survives restarts
|
|
53986
|
+
* const fireOncePersist = Interval.file(mySignalFn, { interval: "1h", name: "mySignal" });
|
|
53987
|
+
* ```
|
|
53988
|
+
*/
|
|
53989
|
+
const Interval = new IntervalUtils();
|
|
53990
|
+
|
|
53328
53991
|
const BREAKEVEN_METHOD_NAME_GET_DATA = "BreakevenUtils.getData";
|
|
53329
53992
|
const BREAKEVEN_METHOD_NAME_GET_REPORT = "BreakevenUtils.getReport";
|
|
53330
53993
|
const BREAKEVEN_METHOD_NAME_DUMP = "BreakevenUtils.dump";
|
|
@@ -54451,11 +55114,13 @@ exports.Exchange = Exchange;
|
|
|
54451
55114
|
exports.ExecutionContextService = ExecutionContextService;
|
|
54452
55115
|
exports.Heat = Heat;
|
|
54453
55116
|
exports.HighestProfit = HighestProfit;
|
|
55117
|
+
exports.Interval = Interval;
|
|
54454
55118
|
exports.Live = Live;
|
|
54455
55119
|
exports.Log = Log;
|
|
54456
55120
|
exports.Markdown = Markdown;
|
|
54457
55121
|
exports.MarkdownFileBase = MarkdownFileBase;
|
|
54458
55122
|
exports.MarkdownFolderBase = MarkdownFolderBase;
|
|
55123
|
+
exports.MarkdownWriter = MarkdownWriter;
|
|
54459
55124
|
exports.MaxDrawdown = MaxDrawdown;
|
|
54460
55125
|
exports.Memory = Memory;
|
|
54461
55126
|
exports.MethodContextService = MethodContextService;
|
|
@@ -54467,6 +55132,7 @@ exports.Performance = Performance;
|
|
|
54467
55132
|
exports.PersistBase = PersistBase;
|
|
54468
55133
|
exports.PersistBreakevenAdapter = PersistBreakevenAdapter;
|
|
54469
55134
|
exports.PersistCandleAdapter = PersistCandleAdapter;
|
|
55135
|
+
exports.PersistIntervalAdapter = PersistIntervalAdapter;
|
|
54470
55136
|
exports.PersistLogAdapter = PersistLogAdapter;
|
|
54471
55137
|
exports.PersistMeasureAdapter = PersistMeasureAdapter;
|
|
54472
55138
|
exports.PersistMemoryAdapter = PersistMemoryAdapter;
|
|
@@ -54476,9 +55142,11 @@ exports.PersistRiskAdapter = PersistRiskAdapter;
|
|
|
54476
55142
|
exports.PersistScheduleAdapter = PersistScheduleAdapter;
|
|
54477
55143
|
exports.PersistSignalAdapter = PersistSignalAdapter;
|
|
54478
55144
|
exports.PersistStorageAdapter = PersistStorageAdapter;
|
|
55145
|
+
exports.Position = Position;
|
|
54479
55146
|
exports.PositionSize = PositionSize;
|
|
54480
55147
|
exports.Report = Report;
|
|
54481
55148
|
exports.ReportBase = ReportBase;
|
|
55149
|
+
exports.ReportWriter = ReportWriter;
|
|
54482
55150
|
exports.Risk = Risk;
|
|
54483
55151
|
exports.Schedule = Schedule;
|
|
54484
55152
|
exports.Storage = Storage;
|