backtest-kit 6.8.1 → 6.10.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 +1469 -188
- package/build/index.mjs +1463 -189
- package/package.json +3 -3
- package/types.d.ts +645 -9
package/build/index.mjs
CHANGED
|
@@ -888,7 +888,7 @@ const LOGGER_SERVICE$7 = new LoggerService();
|
|
|
888
888
|
/** Symbol key for the singleshot waitForInit function on PersistBase instances. */
|
|
889
889
|
const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
|
|
890
890
|
// Calculate step in milliseconds for candle close time validation
|
|
891
|
-
const INTERVAL_MINUTES$
|
|
891
|
+
const INTERVAL_MINUTES$9 = {
|
|
892
892
|
"1m": 1,
|
|
893
893
|
"3m": 3,
|
|
894
894
|
"5m": 5,
|
|
@@ -902,7 +902,7 @@ const INTERVAL_MINUTES$8 = {
|
|
|
902
902
|
"1d": 1440,
|
|
903
903
|
"1w": 10080,
|
|
904
904
|
};
|
|
905
|
-
const MS_PER_MINUTE$
|
|
905
|
+
const MS_PER_MINUTE$7 = 60000;
|
|
906
906
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
|
|
907
907
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
|
|
908
908
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistSignalUtils.writeSignalData";
|
|
@@ -961,8 +961,18 @@ const PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA = "PersistMeasureUtils.readMea
|
|
|
961
961
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA = "PersistMeasureUtils.writeMeasureData";
|
|
962
962
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON = "PersistMeasureUtils.useJson";
|
|
963
963
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY = "PersistMeasureUtils.useDummy";
|
|
964
|
+
const PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA = "PersistMeasureUtils.removeMeasureData";
|
|
965
|
+
const PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA = "PersistMeasureUtils.listMeasureData";
|
|
964
966
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_CLEAR = "PersistMeasureUtils.clear";
|
|
965
967
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER = "PersistMeasureUtils.usePersistMeasureAdapter";
|
|
968
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA = "PersistIntervalUtils.readIntervalData";
|
|
969
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistIntervalUtils.writeIntervalData";
|
|
970
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON = "PersistIntervalUtils.useJson";
|
|
971
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY = "PersistIntervalUtils.useDummy";
|
|
972
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA = "PersistIntervalUtils.removeIntervalData";
|
|
973
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA = "PersistIntervalUtils.listIntervalData";
|
|
974
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR = "PersistIntervalUtils.clear";
|
|
975
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER = "PersistIntervalUtils.usePersistIntervalAdapter";
|
|
966
976
|
const PERSIST_CANDLE_UTILS_METHOD_NAME_CLEAR = "PersistCandleUtils.clear";
|
|
967
977
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_USE_PERSIST_MEMORY_ADAPTER = "PersistMemoryUtils.usePersistMemoryAdapter";
|
|
968
978
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA = "PersistMemoryUtils.readMemoryData";
|
|
@@ -1875,7 +1885,7 @@ class PersistCandleUtils {
|
|
|
1875
1885
|
const isInitial = !this.getCandlesStorage.has(key);
|
|
1876
1886
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1877
1887
|
await stateStorage.waitForInit(isInitial);
|
|
1878
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1888
|
+
const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
|
|
1879
1889
|
// Calculate expected timestamps and fetch each candle directly
|
|
1880
1890
|
const cachedCandles = [];
|
|
1881
1891
|
for (let i = 0; i < limit; i++) {
|
|
@@ -1931,7 +1941,7 @@ class PersistCandleUtils {
|
|
|
1931
1941
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1932
1942
|
await stateStorage.waitForInit(isInitial);
|
|
1933
1943
|
// Calculate step in milliseconds to determine candle close time
|
|
1934
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1944
|
+
const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
|
|
1935
1945
|
const now = Date.now();
|
|
1936
1946
|
// Write each candle as a separate file, skipping incomplete candles
|
|
1937
1947
|
for (const candle of candles) {
|
|
@@ -2357,7 +2367,8 @@ class PersistMeasureUtils {
|
|
|
2357
2367
|
const stateStorage = this.getMeasureStorage(bucket);
|
|
2358
2368
|
await stateStorage.waitForInit(isInitial);
|
|
2359
2369
|
if (await stateStorage.hasValue(key)) {
|
|
2360
|
-
|
|
2370
|
+
const data = await stateStorage.readValue(key);
|
|
2371
|
+
return data.removed ? null : data;
|
|
2361
2372
|
}
|
|
2362
2373
|
return null;
|
|
2363
2374
|
};
|
|
@@ -2379,6 +2390,27 @@ class PersistMeasureUtils {
|
|
|
2379
2390
|
await stateStorage.waitForInit(isInitial);
|
|
2380
2391
|
await stateStorage.writeValue(key, data);
|
|
2381
2392
|
};
|
|
2393
|
+
/**
|
|
2394
|
+
* Marks a cached entry as removed (soft delete — file is kept on disk).
|
|
2395
|
+
* After this call `readMeasureData` for the same key returns `null`.
|
|
2396
|
+
*
|
|
2397
|
+
* @param bucket - Storage bucket
|
|
2398
|
+
* @param key - Dynamic cache key within the bucket
|
|
2399
|
+
* @returns Promise that resolves when removal is complete
|
|
2400
|
+
*/
|
|
2401
|
+
this.removeMeasureData = async (bucket, key) => {
|
|
2402
|
+
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, {
|
|
2403
|
+
bucket,
|
|
2404
|
+
key,
|
|
2405
|
+
});
|
|
2406
|
+
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2407
|
+
const stateStorage = this.getMeasureStorage(bucket);
|
|
2408
|
+
await stateStorage.waitForInit(isInitial);
|
|
2409
|
+
const data = await stateStorage.readValue(key);
|
|
2410
|
+
if (data) {
|
|
2411
|
+
await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
2412
|
+
}
|
|
2413
|
+
};
|
|
2382
2414
|
}
|
|
2383
2415
|
/**
|
|
2384
2416
|
* Registers a custom persistence adapter.
|
|
@@ -2389,6 +2421,27 @@ class PersistMeasureUtils {
|
|
|
2389
2421
|
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
|
|
2390
2422
|
this.PersistMeasureFactory = Ctor;
|
|
2391
2423
|
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Async generator yielding all non-removed entity keys for a given bucket.
|
|
2426
|
+
* Used by `CacheFileInstance.clear()` to iterate and soft-delete all entries.
|
|
2427
|
+
*
|
|
2428
|
+
* @param bucket - Storage bucket
|
|
2429
|
+
* @returns AsyncGenerator yielding entity keys
|
|
2430
|
+
*/
|
|
2431
|
+
async *listMeasureData(bucket) {
|
|
2432
|
+
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA, { bucket });
|
|
2433
|
+
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2434
|
+
const stateStorage = this.getMeasureStorage(bucket);
|
|
2435
|
+
await stateStorage.waitForInit(isInitial);
|
|
2436
|
+
for await (const key of stateStorage.keys()) {
|
|
2437
|
+
const data = await stateStorage.readValue(String(key));
|
|
2438
|
+
if (data === null || data.removed) {
|
|
2439
|
+
continue;
|
|
2440
|
+
}
|
|
2441
|
+
yield String(key);
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
;
|
|
2392
2445
|
/**
|
|
2393
2446
|
* Clears the memoized storage cache.
|
|
2394
2447
|
* Call this when process.cwd() changes between strategy iterations
|
|
@@ -2418,6 +2471,140 @@ class PersistMeasureUtils {
|
|
|
2418
2471
|
* Used by Cache.file for persistent caching of external API responses.
|
|
2419
2472
|
*/
|
|
2420
2473
|
const PersistMeasureAdapter = new PersistMeasureUtils();
|
|
2474
|
+
/**
|
|
2475
|
+
* Persistence layer for Interval.file once-per-interval signal firing.
|
|
2476
|
+
*
|
|
2477
|
+
* Stores fired-interval markers under `./dump/data/interval/`.
|
|
2478
|
+
* A record's presence means the interval has already fired for that bucket+key;
|
|
2479
|
+
* absence means the function has not yet fired (or returned null last time).
|
|
2480
|
+
*/
|
|
2481
|
+
class PersistIntervalUtils {
|
|
2482
|
+
constructor() {
|
|
2483
|
+
this.PersistIntervalFactory = PersistBase;
|
|
2484
|
+
this.getIntervalStorage = memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistIntervalFactory, [
|
|
2485
|
+
bucket,
|
|
2486
|
+
`./dump/data/interval/`,
|
|
2487
|
+
]));
|
|
2488
|
+
/**
|
|
2489
|
+
* Reads interval data for a given bucket and key.
|
|
2490
|
+
*
|
|
2491
|
+
* @param bucket - Storage bucket (instance name + interval + index)
|
|
2492
|
+
* @param key - Entity key within the bucket (symbol + aligned timestamp)
|
|
2493
|
+
* @returns Promise resolving to stored value or null if not found
|
|
2494
|
+
*/
|
|
2495
|
+
this.readIntervalData = async (bucket, key) => {
|
|
2496
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, {
|
|
2497
|
+
bucket,
|
|
2498
|
+
key,
|
|
2499
|
+
});
|
|
2500
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2501
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2502
|
+
await stateStorage.waitForInit(isInitial);
|
|
2503
|
+
if (await stateStorage.hasValue(key)) {
|
|
2504
|
+
const data = await stateStorage.readValue(key);
|
|
2505
|
+
return data.removed ? null : data;
|
|
2506
|
+
}
|
|
2507
|
+
return null;
|
|
2508
|
+
};
|
|
2509
|
+
/**
|
|
2510
|
+
* Writes interval data to disk.
|
|
2511
|
+
*
|
|
2512
|
+
* @param data - Data to store
|
|
2513
|
+
* @param bucket - Storage bucket
|
|
2514
|
+
* @param key - Entity key within the bucket
|
|
2515
|
+
* @returns Promise that resolves when write is complete
|
|
2516
|
+
*/
|
|
2517
|
+
this.writeIntervalData = async (data, bucket, key) => {
|
|
2518
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, {
|
|
2519
|
+
bucket,
|
|
2520
|
+
key,
|
|
2521
|
+
});
|
|
2522
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2523
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2524
|
+
await stateStorage.waitForInit(isInitial);
|
|
2525
|
+
await stateStorage.writeValue(key, data);
|
|
2526
|
+
};
|
|
2527
|
+
/**
|
|
2528
|
+
* Marks an interval entry as removed (soft delete — file is kept on disk).
|
|
2529
|
+
* After this call `readIntervalData` for the same key returns `null`,
|
|
2530
|
+
* so the function will fire again on the next `IntervalFileInstance.run` call.
|
|
2531
|
+
*
|
|
2532
|
+
* @param bucket - Storage bucket
|
|
2533
|
+
* @param key - Entity key within the bucket
|
|
2534
|
+
* @returns Promise that resolves when removal is complete
|
|
2535
|
+
*/
|
|
2536
|
+
this.removeIntervalData = async (bucket, key) => {
|
|
2537
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, {
|
|
2538
|
+
bucket,
|
|
2539
|
+
key,
|
|
2540
|
+
});
|
|
2541
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2542
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2543
|
+
await stateStorage.waitForInit(isInitial);
|
|
2544
|
+
const data = await stateStorage.readValue(key);
|
|
2545
|
+
if (data) {
|
|
2546
|
+
await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
2547
|
+
}
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
/**
|
|
2551
|
+
* Registers a custom persistence adapter.
|
|
2552
|
+
*
|
|
2553
|
+
* @param Ctor - Custom PersistBase constructor
|
|
2554
|
+
*/
|
|
2555
|
+
usePersistIntervalAdapter(Ctor) {
|
|
2556
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER);
|
|
2557
|
+
this.PersistIntervalFactory = Ctor;
|
|
2558
|
+
}
|
|
2559
|
+
/**
|
|
2560
|
+
* Async generator yielding all non-removed entity keys for a given bucket.
|
|
2561
|
+
* Used by `IntervalFileInstance.clear()` to iterate and soft-delete all entries.
|
|
2562
|
+
*
|
|
2563
|
+
* @param bucket - Storage bucket
|
|
2564
|
+
* @returns AsyncGenerator yielding entity keys
|
|
2565
|
+
*/
|
|
2566
|
+
async *listIntervalData(bucket) {
|
|
2567
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA, { bucket });
|
|
2568
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2569
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2570
|
+
await stateStorage.waitForInit(isInitial);
|
|
2571
|
+
for await (const key of stateStorage.keys()) {
|
|
2572
|
+
const data = await stateStorage.readValue(String(key));
|
|
2573
|
+
if (data === null || data.removed) {
|
|
2574
|
+
continue;
|
|
2575
|
+
}
|
|
2576
|
+
yield String(key);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
;
|
|
2580
|
+
/**
|
|
2581
|
+
* Clears the memoized storage cache.
|
|
2582
|
+
* Call this when process.cwd() changes between strategy iterations.
|
|
2583
|
+
*/
|
|
2584
|
+
clear() {
|
|
2585
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR);
|
|
2586
|
+
this.getIntervalStorage.clear();
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* Switches to the default JSON persist adapter.
|
|
2590
|
+
*/
|
|
2591
|
+
useJson() {
|
|
2592
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON);
|
|
2593
|
+
this.usePersistIntervalAdapter(PersistBase);
|
|
2594
|
+
}
|
|
2595
|
+
/**
|
|
2596
|
+
* Switches to a dummy persist adapter that discards all writes.
|
|
2597
|
+
*/
|
|
2598
|
+
useDummy() {
|
|
2599
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2600
|
+
this.usePersistIntervalAdapter(PersistDummy);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
/**
|
|
2604
|
+
* Global singleton instance of PersistIntervalUtils.
|
|
2605
|
+
* Used by Interval.file for persistent once-per-interval signal firing.
|
|
2606
|
+
*/
|
|
2607
|
+
const PersistIntervalAdapter = new PersistIntervalUtils();
|
|
2421
2608
|
/**
|
|
2422
2609
|
* Utility class for managing memory entry persistence.
|
|
2423
2610
|
*
|
|
@@ -2765,8 +2952,8 @@ class CandleUtils {
|
|
|
2765
2952
|
}
|
|
2766
2953
|
const Candle = new CandleUtils();
|
|
2767
2954
|
|
|
2768
|
-
const MS_PER_MINUTE$
|
|
2769
|
-
const INTERVAL_MINUTES$
|
|
2955
|
+
const MS_PER_MINUTE$6 = 60000;
|
|
2956
|
+
const INTERVAL_MINUTES$8 = {
|
|
2770
2957
|
"1m": 1,
|
|
2771
2958
|
"3m": 3,
|
|
2772
2959
|
"5m": 5,
|
|
@@ -2798,7 +2985,7 @@ const INTERVAL_MINUTES$7 = {
|
|
|
2798
2985
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
2799
2986
|
*/
|
|
2800
2987
|
const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
|
|
2801
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
2988
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$6;
|
|
2802
2989
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
2803
2990
|
};
|
|
2804
2991
|
/**
|
|
@@ -2952,9 +3139,9 @@ const WRITE_CANDLES_CACHE_FN$1 = trycatch(queued(async (candles, dto, self) => {
|
|
|
2952
3139
|
* @returns Promise resolving to array of candle data
|
|
2953
3140
|
*/
|
|
2954
3141
|
const GET_CANDLES_FN$1 = async (dto, since, self) => {
|
|
2955
|
-
const step = INTERVAL_MINUTES$
|
|
3142
|
+
const step = INTERVAL_MINUTES$8[dto.interval];
|
|
2956
3143
|
const sinceTimestamp = since.getTime();
|
|
2957
|
-
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$
|
|
3144
|
+
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$6;
|
|
2958
3145
|
await Candle.acquireLock(`ClientExchange GET_CANDLES_FN symbol=${dto.symbol} interval=${dto.interval} limit=${dto.limit}`);
|
|
2959
3146
|
try {
|
|
2960
3147
|
// Try to read from cache first
|
|
@@ -3068,11 +3255,11 @@ class ClientExchange {
|
|
|
3068
3255
|
interval,
|
|
3069
3256
|
limit,
|
|
3070
3257
|
});
|
|
3071
|
-
const step = INTERVAL_MINUTES$
|
|
3258
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3072
3259
|
if (!step) {
|
|
3073
3260
|
throw new Error(`ClientExchange unknown interval=${interval}`);
|
|
3074
3261
|
}
|
|
3075
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3262
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3076
3263
|
// Align when down to interval boundary
|
|
3077
3264
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3078
3265
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
@@ -3149,11 +3336,11 @@ class ClientExchange {
|
|
|
3149
3336
|
if (!this.params.execution.context.backtest) {
|
|
3150
3337
|
throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
|
|
3151
3338
|
}
|
|
3152
|
-
const step = INTERVAL_MINUTES$
|
|
3339
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3153
3340
|
if (!step) {
|
|
3154
3341
|
throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
|
|
3155
3342
|
}
|
|
3156
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3343
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3157
3344
|
const now = Date.now();
|
|
3158
3345
|
// Align when down to interval boundary
|
|
3159
3346
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
@@ -3317,11 +3504,11 @@ class ClientExchange {
|
|
|
3317
3504
|
sDate,
|
|
3318
3505
|
eDate,
|
|
3319
3506
|
});
|
|
3320
|
-
const step = INTERVAL_MINUTES$
|
|
3507
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3321
3508
|
if (!step) {
|
|
3322
3509
|
throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
|
|
3323
3510
|
}
|
|
3324
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3511
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3325
3512
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3326
3513
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
3327
3514
|
let sinceTimestamp;
|
|
@@ -3450,7 +3637,7 @@ class ClientExchange {
|
|
|
3450
3637
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
3451
3638
|
const to = new Date(alignedTo);
|
|
3452
3639
|
const from = new Date(alignedTo -
|
|
3453
|
-
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
3640
|
+
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$6);
|
|
3454
3641
|
return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
|
|
3455
3642
|
}
|
|
3456
3643
|
/**
|
|
@@ -3479,7 +3666,7 @@ class ClientExchange {
|
|
|
3479
3666
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3480
3667
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
3481
3668
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, 1);
|
|
3482
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
3669
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$6 - MS_PER_MINUTE$6;
|
|
3483
3670
|
// No limit: fetch a single window and return as-is
|
|
3484
3671
|
if (limit === undefined) {
|
|
3485
3672
|
const to = new Date(alignedTo);
|
|
@@ -4458,7 +4645,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
|
|
|
4458
4645
|
}
|
|
4459
4646
|
};
|
|
4460
4647
|
|
|
4461
|
-
const INTERVAL_MINUTES$
|
|
4648
|
+
const INTERVAL_MINUTES$7 = {
|
|
4462
4649
|
"1m": 1,
|
|
4463
4650
|
"3m": 3,
|
|
4464
4651
|
"5m": 5,
|
|
@@ -4842,7 +5029,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
4842
5029
|
}
|
|
4843
5030
|
const currentTime = self.params.execution.context.when.getTime();
|
|
4844
5031
|
{
|
|
4845
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
5032
|
+
const intervalMinutes = INTERVAL_MINUTES$7[self.params.interval];
|
|
4846
5033
|
const intervalMs = intervalMinutes * 60 * 1000;
|
|
4847
5034
|
const alignedTime = Math.floor(currentTime / intervalMs) * intervalMs;
|
|
4848
5035
|
// Проверяем что наступил новый интервал (по aligned timestamp)
|
|
@@ -7783,6 +7970,90 @@ class ClientStrategy {
|
|
|
7783
7970
|
}
|
|
7784
7971
|
return this._pendingSignal._fall.pnlCost;
|
|
7785
7972
|
}
|
|
7973
|
+
/**
|
|
7974
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
7975
|
+
*
|
|
7976
|
+
* Measures how much PnL% the position has given back from its best point.
|
|
7977
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
7978
|
+
* Zero when called at the exact moment the peak was set, or when current PnL >= peak PnL.
|
|
7979
|
+
*
|
|
7980
|
+
* Returns null if no pending signal exists.
|
|
7981
|
+
*
|
|
7982
|
+
* @param symbol - Trading pair symbol
|
|
7983
|
+
* @param currentPrice - Current market price
|
|
7984
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
7985
|
+
*/
|
|
7986
|
+
async getPositionHighestProfitDistancePnlPercentage(symbol, currentPrice) {
|
|
7987
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitDistancePnlPercentage", { symbol, currentPrice });
|
|
7988
|
+
if (!this._pendingSignal) {
|
|
7989
|
+
return null;
|
|
7990
|
+
}
|
|
7991
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
7992
|
+
return Math.max(0, this._pendingSignal._peak.pnlPercentage - currentPnl.pnlPercentage);
|
|
7993
|
+
}
|
|
7994
|
+
/**
|
|
7995
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
7996
|
+
*
|
|
7997
|
+
* Measures how much PnL cost the position has given back from its best point.
|
|
7998
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
7999
|
+
* Zero when called at the exact moment the peak was set, or when current PnL >= peak PnL.
|
|
8000
|
+
*
|
|
8001
|
+
* Returns null if no pending signal exists.
|
|
8002
|
+
*
|
|
8003
|
+
* @param symbol - Trading pair symbol
|
|
8004
|
+
* @param currentPrice - Current market price
|
|
8005
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
8006
|
+
*/
|
|
8007
|
+
async getPositionHighestProfitDistancePnlCost(symbol, currentPrice) {
|
|
8008
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitDistancePnlCost", { symbol, currentPrice });
|
|
8009
|
+
if (!this._pendingSignal) {
|
|
8010
|
+
return null;
|
|
8011
|
+
}
|
|
8012
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8013
|
+
return Math.max(0, this._pendingSignal._peak.pnlCost - currentPnl.pnlCost);
|
|
8014
|
+
}
|
|
8015
|
+
/**
|
|
8016
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
8017
|
+
*
|
|
8018
|
+
* Measures how much the position has recovered from its deepest loss point.
|
|
8019
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
8020
|
+
* Zero when called at the exact moment the trough was set, or when current PnL <= trough PnL.
|
|
8021
|
+
*
|
|
8022
|
+
* Returns null if no pending signal exists.
|
|
8023
|
+
*
|
|
8024
|
+
* @param symbol - Trading pair symbol
|
|
8025
|
+
* @param currentPrice - Current market price
|
|
8026
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
8027
|
+
*/
|
|
8028
|
+
async getPositionHighestMaxDrawdownPnlPercentage(symbol, currentPrice) {
|
|
8029
|
+
this.params.logger.debug("ClientStrategy getPositionHighestMaxDrawdownPnlPercentage", { symbol, currentPrice });
|
|
8030
|
+
if (!this._pendingSignal) {
|
|
8031
|
+
return null;
|
|
8032
|
+
}
|
|
8033
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8034
|
+
return Math.max(0, currentPnl.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
|
|
8035
|
+
}
|
|
8036
|
+
/**
|
|
8037
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
8038
|
+
*
|
|
8039
|
+
* Measures how much the position has recovered from its deepest loss point.
|
|
8040
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
8041
|
+
* Zero when called at the exact moment the trough was set, or when current PnL <= trough PnL.
|
|
8042
|
+
*
|
|
8043
|
+
* Returns null if no pending signal exists.
|
|
8044
|
+
*
|
|
8045
|
+
* @param symbol - Trading pair symbol
|
|
8046
|
+
* @param currentPrice - Current market price
|
|
8047
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
8048
|
+
*/
|
|
8049
|
+
async getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice) {
|
|
8050
|
+
this.params.logger.debug("ClientStrategy getPositionHighestMaxDrawdownPnlCost", { symbol, currentPrice });
|
|
8051
|
+
if (!this._pendingSignal) {
|
|
8052
|
+
return null;
|
|
8053
|
+
}
|
|
8054
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8055
|
+
return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8056
|
+
}
|
|
7786
8057
|
/**
|
|
7787
8058
|
* Performs a single tick of strategy execution.
|
|
7788
8059
|
*
|
|
@@ -9866,7 +10137,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
9866
10137
|
* @param backtest - Whether running in backtest mode
|
|
9867
10138
|
* @returns Unique string key for memoization
|
|
9868
10139
|
*/
|
|
9869
|
-
const CREATE_KEY_FN$
|
|
10140
|
+
const CREATE_KEY_FN$t = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
9870
10141
|
const parts = [symbol, strategyName, exchangeName];
|
|
9871
10142
|
if (frameName)
|
|
9872
10143
|
parts.push(frameName);
|
|
@@ -10133,7 +10404,7 @@ class StrategyConnectionService {
|
|
|
10133
10404
|
* @param backtest - Whether running in backtest mode
|
|
10134
10405
|
* @returns Configured ClientStrategy instance
|
|
10135
10406
|
*/
|
|
10136
|
-
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
10407
|
+
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10137
10408
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10138
10409
|
return new ClientStrategy({
|
|
10139
10410
|
symbol,
|
|
@@ -10890,6 +11161,90 @@ class StrategyConnectionService {
|
|
|
10890
11161
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10891
11162
|
return await strategy.getPositionMaxDrawdownPnlCost(symbol);
|
|
10892
11163
|
};
|
|
11164
|
+
/**
|
|
11165
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
11166
|
+
*
|
|
11167
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11168
|
+
* ClientStrategy.getPositionHighestProfitDistancePnlPercentage().
|
|
11169
|
+
* Returns null if no pending signal exists.
|
|
11170
|
+
*
|
|
11171
|
+
* @param backtest - Whether running in backtest mode
|
|
11172
|
+
* @param symbol - Trading pair symbol
|
|
11173
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11174
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
11175
|
+
*/
|
|
11176
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
11177
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitDistancePnlPercentage", {
|
|
11178
|
+
symbol,
|
|
11179
|
+
context,
|
|
11180
|
+
});
|
|
11181
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11182
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11183
|
+
return await strategy.getPositionHighestProfitDistancePnlPercentage(symbol, currentPrice);
|
|
11184
|
+
};
|
|
11185
|
+
/**
|
|
11186
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
11187
|
+
*
|
|
11188
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11189
|
+
* ClientStrategy.getPositionHighestProfitDistancePnlCost().
|
|
11190
|
+
* Returns null if no pending signal exists.
|
|
11191
|
+
*
|
|
11192
|
+
* @param backtest - Whether running in backtest mode
|
|
11193
|
+
* @param symbol - Trading pair symbol
|
|
11194
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11195
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
11196
|
+
*/
|
|
11197
|
+
this.getPositionHighestProfitDistancePnlCost = async (backtest, symbol, context) => {
|
|
11198
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitDistancePnlCost", {
|
|
11199
|
+
symbol,
|
|
11200
|
+
context,
|
|
11201
|
+
});
|
|
11202
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11203
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11204
|
+
return await strategy.getPositionHighestProfitDistancePnlCost(symbol, currentPrice);
|
|
11205
|
+
};
|
|
11206
|
+
/**
|
|
11207
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
11208
|
+
*
|
|
11209
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11210
|
+
* ClientStrategy.getPositionHighestMaxDrawdownPnlPercentage().
|
|
11211
|
+
* Returns null if no pending signal exists.
|
|
11212
|
+
*
|
|
11213
|
+
* @param backtest - Whether running in backtest mode
|
|
11214
|
+
* @param symbol - Trading pair symbol
|
|
11215
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11216
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
11217
|
+
*/
|
|
11218
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
|
|
11219
|
+
this.loggerService.log("strategyConnectionService getPositionHighestMaxDrawdownPnlPercentage", {
|
|
11220
|
+
symbol,
|
|
11221
|
+
context,
|
|
11222
|
+
});
|
|
11223
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11224
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11225
|
+
return await strategy.getPositionHighestMaxDrawdownPnlPercentage(symbol, currentPrice);
|
|
11226
|
+
};
|
|
11227
|
+
/**
|
|
11228
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
11229
|
+
*
|
|
11230
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11231
|
+
* ClientStrategy.getPositionHighestMaxDrawdownPnlCost().
|
|
11232
|
+
* Returns null if no pending signal exists.
|
|
11233
|
+
*
|
|
11234
|
+
* @param backtest - Whether running in backtest mode
|
|
11235
|
+
* @param symbol - Trading pair symbol
|
|
11236
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11237
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
11238
|
+
*/
|
|
11239
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (backtest, symbol, context) => {
|
|
11240
|
+
this.loggerService.log("strategyConnectionService getPositionHighestMaxDrawdownPnlCost", {
|
|
11241
|
+
symbol,
|
|
11242
|
+
context,
|
|
11243
|
+
});
|
|
11244
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11245
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11246
|
+
return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
|
|
11247
|
+
};
|
|
10893
11248
|
/**
|
|
10894
11249
|
* Disposes the ClientStrategy instance for the given context.
|
|
10895
11250
|
*
|
|
@@ -10928,7 +11283,7 @@ class StrategyConnectionService {
|
|
|
10928
11283
|
}
|
|
10929
11284
|
return;
|
|
10930
11285
|
}
|
|
10931
|
-
const key = CREATE_KEY_FN$
|
|
11286
|
+
const key = CREATE_KEY_FN$t(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
10932
11287
|
if (!this.getStrategy.has(key)) {
|
|
10933
11288
|
return;
|
|
10934
11289
|
}
|
|
@@ -11330,7 +11685,7 @@ class StrategyConnectionService {
|
|
|
11330
11685
|
* Maps FrameInterval to minutes for timestamp calculation.
|
|
11331
11686
|
* Used to generate timeframe arrays with proper spacing.
|
|
11332
11687
|
*/
|
|
11333
|
-
const INTERVAL_MINUTES$
|
|
11688
|
+
const INTERVAL_MINUTES$6 = {
|
|
11334
11689
|
"1m": 1,
|
|
11335
11690
|
"3m": 3,
|
|
11336
11691
|
"5m": 5,
|
|
@@ -11386,7 +11741,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
11386
11741
|
symbol,
|
|
11387
11742
|
});
|
|
11388
11743
|
const { interval, startDate, endDate } = self.params;
|
|
11389
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
11744
|
+
const intervalMinutes = INTERVAL_MINUTES$6[interval];
|
|
11390
11745
|
if (!intervalMinutes) {
|
|
11391
11746
|
throw new Error(`ClientFrame unknown interval: ${interval}`);
|
|
11392
11747
|
}
|
|
@@ -11751,8 +12106,8 @@ const get = (object, path) => {
|
|
|
11751
12106
|
return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
|
|
11752
12107
|
};
|
|
11753
12108
|
|
|
11754
|
-
const MS_PER_MINUTE$
|
|
11755
|
-
const INTERVAL_MINUTES$
|
|
12109
|
+
const MS_PER_MINUTE$5 = 60000;
|
|
12110
|
+
const INTERVAL_MINUTES$5 = {
|
|
11756
12111
|
"1m": 1,
|
|
11757
12112
|
"3m": 3,
|
|
11758
12113
|
"5m": 5,
|
|
@@ -11784,11 +12139,11 @@ const INTERVAL_MINUTES$4 = {
|
|
|
11784
12139
|
* @returns New Date aligned down to interval boundary
|
|
11785
12140
|
*/
|
|
11786
12141
|
const alignToInterval = (date, interval) => {
|
|
11787
|
-
const minutes = INTERVAL_MINUTES$
|
|
12142
|
+
const minutes = INTERVAL_MINUTES$5[interval];
|
|
11788
12143
|
if (minutes === undefined) {
|
|
11789
12144
|
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
11790
12145
|
}
|
|
11791
|
-
const intervalMs = minutes * MS_PER_MINUTE$
|
|
12146
|
+
const intervalMs = minutes * MS_PER_MINUTE$5;
|
|
11792
12147
|
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
11793
12148
|
};
|
|
11794
12149
|
|
|
@@ -12097,7 +12452,7 @@ class ClientRisk {
|
|
|
12097
12452
|
* @param backtest - Whether running in backtest mode
|
|
12098
12453
|
* @returns Unique string key for memoization
|
|
12099
12454
|
*/
|
|
12100
|
-
const CREATE_KEY_FN$
|
|
12455
|
+
const CREATE_KEY_FN$s = (riskName, exchangeName, frameName, backtest) => {
|
|
12101
12456
|
const parts = [riskName, exchangeName];
|
|
12102
12457
|
if (frameName)
|
|
12103
12458
|
parts.push(frameName);
|
|
@@ -12197,7 +12552,7 @@ class RiskConnectionService {
|
|
|
12197
12552
|
* @param backtest - True if backtest mode, false if live mode
|
|
12198
12553
|
* @returns Configured ClientRisk instance
|
|
12199
12554
|
*/
|
|
12200
|
-
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12555
|
+
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
12201
12556
|
const schema = this.riskSchemaService.get(riskName);
|
|
12202
12557
|
return new ClientRisk({
|
|
12203
12558
|
...schema,
|
|
@@ -12266,7 +12621,7 @@ class RiskConnectionService {
|
|
|
12266
12621
|
payload,
|
|
12267
12622
|
});
|
|
12268
12623
|
if (payload) {
|
|
12269
|
-
const key = CREATE_KEY_FN$
|
|
12624
|
+
const key = CREATE_KEY_FN$s(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
12270
12625
|
this.getRisk.clear(key);
|
|
12271
12626
|
}
|
|
12272
12627
|
else {
|
|
@@ -13310,7 +13665,7 @@ class ClientAction {
|
|
|
13310
13665
|
* @param backtest - Whether running in backtest mode
|
|
13311
13666
|
* @returns Unique string key for memoization
|
|
13312
13667
|
*/
|
|
13313
|
-
const CREATE_KEY_FN$
|
|
13668
|
+
const CREATE_KEY_FN$r = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13314
13669
|
const parts = [actionName, strategyName, exchangeName];
|
|
13315
13670
|
if (frameName)
|
|
13316
13671
|
parts.push(frameName);
|
|
@@ -13362,7 +13717,7 @@ class ActionConnectionService {
|
|
|
13362
13717
|
* @param backtest - True if backtest mode, false if live mode
|
|
13363
13718
|
* @returns Configured ClientAction instance
|
|
13364
13719
|
*/
|
|
13365
|
-
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
13720
|
+
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13366
13721
|
const schema = this.actionSchemaService.get(actionName);
|
|
13367
13722
|
return new ClientAction({
|
|
13368
13723
|
...schema,
|
|
@@ -13573,7 +13928,7 @@ class ActionConnectionService {
|
|
|
13573
13928
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
13574
13929
|
return;
|
|
13575
13930
|
}
|
|
13576
|
-
const key = CREATE_KEY_FN$
|
|
13931
|
+
const key = CREATE_KEY_FN$r(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
13577
13932
|
if (!this.getAction.has(key)) {
|
|
13578
13933
|
return;
|
|
13579
13934
|
}
|
|
@@ -13591,7 +13946,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
13591
13946
|
* @param exchangeName - Exchange name
|
|
13592
13947
|
* @returns Unique string key for memoization
|
|
13593
13948
|
*/
|
|
13594
|
-
const CREATE_KEY_FN$
|
|
13949
|
+
const CREATE_KEY_FN$q = (exchangeName) => {
|
|
13595
13950
|
return exchangeName;
|
|
13596
13951
|
};
|
|
13597
13952
|
/**
|
|
@@ -13615,7 +13970,7 @@ class ExchangeCoreService {
|
|
|
13615
13970
|
* @param exchangeName - Name of the exchange to validate
|
|
13616
13971
|
* @returns Promise that resolves when validation is complete
|
|
13617
13972
|
*/
|
|
13618
|
-
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
13973
|
+
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$q(exchangeName), async (exchangeName) => {
|
|
13619
13974
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
13620
13975
|
exchangeName,
|
|
13621
13976
|
});
|
|
@@ -13867,7 +14222,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
13867
14222
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13868
14223
|
* @returns Unique string key for memoization
|
|
13869
14224
|
*/
|
|
13870
|
-
const CREATE_KEY_FN$
|
|
14225
|
+
const CREATE_KEY_FN$p = (context) => {
|
|
13871
14226
|
const parts = [context.strategyName, context.exchangeName];
|
|
13872
14227
|
if (context.frameName)
|
|
13873
14228
|
parts.push(context.frameName);
|
|
@@ -13899,7 +14254,7 @@ class StrategyCoreService {
|
|
|
13899
14254
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13900
14255
|
* @returns Promise that resolves when validation is complete
|
|
13901
14256
|
*/
|
|
13902
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
14257
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
|
|
13903
14258
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
13904
14259
|
context,
|
|
13905
14260
|
});
|
|
@@ -15051,6 +15406,82 @@ class StrategyCoreService {
|
|
|
15051
15406
|
await this.validate(context);
|
|
15052
15407
|
return await this.strategyConnectionService.getPositionMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15053
15408
|
};
|
|
15409
|
+
/**
|
|
15410
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
15411
|
+
*
|
|
15412
|
+
* Delegates to StrategyConnectionService.getPositionHighestProfitDistancePnlPercentage().
|
|
15413
|
+
* Returns null if no pending signal exists.
|
|
15414
|
+
*
|
|
15415
|
+
* @param backtest - Whether running in backtest mode
|
|
15416
|
+
* @param symbol - Trading pair symbol
|
|
15417
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15418
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
15419
|
+
*/
|
|
15420
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
15421
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitDistancePnlPercentage", {
|
|
15422
|
+
symbol,
|
|
15423
|
+
context,
|
|
15424
|
+
});
|
|
15425
|
+
await this.validate(context);
|
|
15426
|
+
return await this.strategyConnectionService.getPositionHighestProfitDistancePnlPercentage(backtest, symbol, context);
|
|
15427
|
+
};
|
|
15428
|
+
/**
|
|
15429
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
15430
|
+
*
|
|
15431
|
+
* Delegates to StrategyConnectionService.getPositionHighestProfitDistancePnlCost().
|
|
15432
|
+
* Returns null if no pending signal exists.
|
|
15433
|
+
*
|
|
15434
|
+
* @param backtest - Whether running in backtest mode
|
|
15435
|
+
* @param symbol - Trading pair symbol
|
|
15436
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15437
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
15438
|
+
*/
|
|
15439
|
+
this.getPositionHighestProfitDistancePnlCost = async (backtest, symbol, context) => {
|
|
15440
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitDistancePnlCost", {
|
|
15441
|
+
symbol,
|
|
15442
|
+
context,
|
|
15443
|
+
});
|
|
15444
|
+
await this.validate(context);
|
|
15445
|
+
return await this.strategyConnectionService.getPositionHighestProfitDistancePnlCost(backtest, symbol, context);
|
|
15446
|
+
};
|
|
15447
|
+
/**
|
|
15448
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
15449
|
+
*
|
|
15450
|
+
* Delegates to StrategyConnectionService.getPositionHighestMaxDrawdownPnlPercentage().
|
|
15451
|
+
* Returns null if no pending signal exists.
|
|
15452
|
+
*
|
|
15453
|
+
* @param backtest - Whether running in backtest mode
|
|
15454
|
+
* @param symbol - Trading pair symbol
|
|
15455
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15456
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
15457
|
+
*/
|
|
15458
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
|
|
15459
|
+
this.loggerService.log("strategyCoreService getPositionHighestMaxDrawdownPnlPercentage", {
|
|
15460
|
+
symbol,
|
|
15461
|
+
context,
|
|
15462
|
+
});
|
|
15463
|
+
await this.validate(context);
|
|
15464
|
+
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlPercentage(backtest, symbol, context);
|
|
15465
|
+
};
|
|
15466
|
+
/**
|
|
15467
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
15468
|
+
*
|
|
15469
|
+
* Delegates to StrategyConnectionService.getPositionHighestMaxDrawdownPnlCost().
|
|
15470
|
+
* Returns null if no pending signal exists.
|
|
15471
|
+
*
|
|
15472
|
+
* @param backtest - Whether running in backtest mode
|
|
15473
|
+
* @param symbol - Trading pair symbol
|
|
15474
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15475
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
15476
|
+
*/
|
|
15477
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (backtest, symbol, context) => {
|
|
15478
|
+
this.loggerService.log("strategyCoreService getPositionHighestMaxDrawdownPnlCost", {
|
|
15479
|
+
symbol,
|
|
15480
|
+
context,
|
|
15481
|
+
});
|
|
15482
|
+
await this.validate(context);
|
|
15483
|
+
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15484
|
+
};
|
|
15054
15485
|
}
|
|
15055
15486
|
}
|
|
15056
15487
|
|
|
@@ -15123,7 +15554,7 @@ class SizingGlobalService {
|
|
|
15123
15554
|
* @param context - Context with riskName, exchangeName, frameName
|
|
15124
15555
|
* @returns Unique string key for memoization
|
|
15125
15556
|
*/
|
|
15126
|
-
const CREATE_KEY_FN$
|
|
15557
|
+
const CREATE_KEY_FN$o = (context) => {
|
|
15127
15558
|
const parts = [context.riskName, context.exchangeName];
|
|
15128
15559
|
if (context.frameName)
|
|
15129
15560
|
parts.push(context.frameName);
|
|
@@ -15149,7 +15580,7 @@ class RiskGlobalService {
|
|
|
15149
15580
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
15150
15581
|
* @returns Promise that resolves when validation is complete
|
|
15151
15582
|
*/
|
|
15152
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
15583
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
|
|
15153
15584
|
this.loggerService.log("riskGlobalService validate", {
|
|
15154
15585
|
context,
|
|
15155
15586
|
});
|
|
@@ -15227,7 +15658,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
15227
15658
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15228
15659
|
* @returns Unique string key for memoization
|
|
15229
15660
|
*/
|
|
15230
|
-
const CREATE_KEY_FN$
|
|
15661
|
+
const CREATE_KEY_FN$n = (context) => {
|
|
15231
15662
|
const parts = [context.strategyName, context.exchangeName];
|
|
15232
15663
|
if (context.frameName)
|
|
15233
15664
|
parts.push(context.frameName);
|
|
@@ -15271,7 +15702,7 @@ class ActionCoreService {
|
|
|
15271
15702
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
15272
15703
|
* @returns Promise that resolves when all validations complete
|
|
15273
15704
|
*/
|
|
15274
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
15705
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
|
|
15275
15706
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
15276
15707
|
context,
|
|
15277
15708
|
});
|
|
@@ -16866,8 +17297,8 @@ class BacktestLogicPrivateService {
|
|
|
16866
17297
|
}
|
|
16867
17298
|
|
|
16868
17299
|
const EMITTER_CHECK_INTERVAL = 5000;
|
|
16869
|
-
const MS_PER_MINUTE$
|
|
16870
|
-
const INTERVAL_MINUTES$
|
|
17300
|
+
const MS_PER_MINUTE$4 = 60000;
|
|
17301
|
+
const INTERVAL_MINUTES$4 = {
|
|
16871
17302
|
"1m": 1,
|
|
16872
17303
|
"3m": 3,
|
|
16873
17304
|
"5m": 5,
|
|
@@ -16883,7 +17314,7 @@ const INTERVAL_MINUTES$3 = {
|
|
|
16883
17314
|
};
|
|
16884
17315
|
const createEmitter = memoize(([interval]) => `${interval}`, (interval) => {
|
|
16885
17316
|
const tickSubject = new Subject();
|
|
16886
|
-
const intervalMs = INTERVAL_MINUTES$
|
|
17317
|
+
const intervalMs = INTERVAL_MINUTES$4[interval] * MS_PER_MINUTE$4;
|
|
16887
17318
|
{
|
|
16888
17319
|
let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
16889
17320
|
Source.fromInterval(EMITTER_CHECK_INTERVAL)
|
|
@@ -20267,7 +20698,7 @@ const ReportWriter = new ReportWriterAdapter();
|
|
|
20267
20698
|
* @param backtest - Whether running in backtest mode
|
|
20268
20699
|
* @returns Unique string key for memoization
|
|
20269
20700
|
*/
|
|
20270
|
-
const CREATE_KEY_FN$
|
|
20701
|
+
const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20271
20702
|
const parts = [symbol, strategyName, exchangeName];
|
|
20272
20703
|
if (frameName)
|
|
20273
20704
|
parts.push(frameName);
|
|
@@ -20513,7 +20944,7 @@ class BacktestMarkdownService {
|
|
|
20513
20944
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20514
20945
|
* Each combination gets its own isolated storage instance.
|
|
20515
20946
|
*/
|
|
20516
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20947
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
|
|
20517
20948
|
/**
|
|
20518
20949
|
* Processes tick events and accumulates closed signals.
|
|
20519
20950
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -20670,7 +21101,7 @@ class BacktestMarkdownService {
|
|
|
20670
21101
|
payload,
|
|
20671
21102
|
});
|
|
20672
21103
|
if (payload) {
|
|
20673
|
-
const key = CREATE_KEY_FN$
|
|
21104
|
+
const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20674
21105
|
this.getStorage.clear(key);
|
|
20675
21106
|
}
|
|
20676
21107
|
else {
|
|
@@ -20732,7 +21163,7 @@ class BacktestMarkdownService {
|
|
|
20732
21163
|
* @param backtest - Whether running in backtest mode
|
|
20733
21164
|
* @returns Unique string key for memoization
|
|
20734
21165
|
*/
|
|
20735
|
-
const CREATE_KEY_FN$
|
|
21166
|
+
const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20736
21167
|
const parts = [symbol, strategyName, exchangeName];
|
|
20737
21168
|
if (frameName)
|
|
20738
21169
|
parts.push(frameName);
|
|
@@ -21227,7 +21658,7 @@ class LiveMarkdownService {
|
|
|
21227
21658
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21228
21659
|
* Each combination gets its own isolated storage instance.
|
|
21229
21660
|
*/
|
|
21230
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21661
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
|
|
21231
21662
|
/**
|
|
21232
21663
|
* Subscribes to live signal emitter to receive tick events.
|
|
21233
21664
|
* Protected against multiple subscriptions.
|
|
@@ -21445,7 +21876,7 @@ class LiveMarkdownService {
|
|
|
21445
21876
|
payload,
|
|
21446
21877
|
});
|
|
21447
21878
|
if (payload) {
|
|
21448
|
-
const key = CREATE_KEY_FN$
|
|
21879
|
+
const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21449
21880
|
this.getStorage.clear(key);
|
|
21450
21881
|
}
|
|
21451
21882
|
else {
|
|
@@ -21465,7 +21896,7 @@ class LiveMarkdownService {
|
|
|
21465
21896
|
* @param backtest - Whether running in backtest mode
|
|
21466
21897
|
* @returns Unique string key for memoization
|
|
21467
21898
|
*/
|
|
21468
|
-
const CREATE_KEY_FN$
|
|
21899
|
+
const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21469
21900
|
const parts = [symbol, strategyName, exchangeName];
|
|
21470
21901
|
if (frameName)
|
|
21471
21902
|
parts.push(frameName);
|
|
@@ -21754,7 +22185,7 @@ class ScheduleMarkdownService {
|
|
|
21754
22185
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21755
22186
|
* Each combination gets its own isolated storage instance.
|
|
21756
22187
|
*/
|
|
21757
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22188
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
|
|
21758
22189
|
/**
|
|
21759
22190
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
21760
22191
|
* Protected against multiple subscriptions.
|
|
@@ -21957,7 +22388,7 @@ class ScheduleMarkdownService {
|
|
|
21957
22388
|
payload,
|
|
21958
22389
|
});
|
|
21959
22390
|
if (payload) {
|
|
21960
|
-
const key = CREATE_KEY_FN$
|
|
22391
|
+
const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21961
22392
|
this.getStorage.clear(key);
|
|
21962
22393
|
}
|
|
21963
22394
|
else {
|
|
@@ -21977,7 +22408,7 @@ class ScheduleMarkdownService {
|
|
|
21977
22408
|
* @param backtest - Whether running in backtest mode
|
|
21978
22409
|
* @returns Unique string key for memoization
|
|
21979
22410
|
*/
|
|
21980
|
-
const CREATE_KEY_FN$
|
|
22411
|
+
const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21981
22412
|
const parts = [symbol, strategyName, exchangeName];
|
|
21982
22413
|
if (frameName)
|
|
21983
22414
|
parts.push(frameName);
|
|
@@ -22222,7 +22653,7 @@ class PerformanceMarkdownService {
|
|
|
22222
22653
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
22223
22654
|
* Each combination gets its own isolated storage instance.
|
|
22224
22655
|
*/
|
|
22225
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22656
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
|
|
22226
22657
|
/**
|
|
22227
22658
|
* Subscribes to performance emitter to receive performance events.
|
|
22228
22659
|
* Protected against multiple subscriptions.
|
|
@@ -22389,7 +22820,7 @@ class PerformanceMarkdownService {
|
|
|
22389
22820
|
payload,
|
|
22390
22821
|
});
|
|
22391
22822
|
if (payload) {
|
|
22392
|
-
const key = CREATE_KEY_FN$
|
|
22823
|
+
const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
22393
22824
|
this.getStorage.clear(key);
|
|
22394
22825
|
}
|
|
22395
22826
|
else {
|
|
@@ -22868,7 +23299,7 @@ class WalkerMarkdownService {
|
|
|
22868
23299
|
* @param backtest - Whether running in backtest mode
|
|
22869
23300
|
* @returns Unique string key for memoization
|
|
22870
23301
|
*/
|
|
22871
|
-
const CREATE_KEY_FN$
|
|
23302
|
+
const CREATE_KEY_FN$i = (exchangeName, frameName, backtest) => {
|
|
22872
23303
|
const parts = [exchangeName];
|
|
22873
23304
|
if (frameName)
|
|
22874
23305
|
parts.push(frameName);
|
|
@@ -23315,7 +23746,7 @@ class HeatMarkdownService {
|
|
|
23315
23746
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
23316
23747
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
23317
23748
|
*/
|
|
23318
|
-
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
23749
|
+
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
23319
23750
|
/**
|
|
23320
23751
|
* Subscribes to signal emitter to receive tick events.
|
|
23321
23752
|
* Protected against multiple subscriptions.
|
|
@@ -23533,7 +23964,7 @@ class HeatMarkdownService {
|
|
|
23533
23964
|
payload,
|
|
23534
23965
|
});
|
|
23535
23966
|
if (payload) {
|
|
23536
|
-
const key = CREATE_KEY_FN$
|
|
23967
|
+
const key = CREATE_KEY_FN$i(payload.exchangeName, payload.frameName, payload.backtest);
|
|
23537
23968
|
this.getStorage.clear(key);
|
|
23538
23969
|
}
|
|
23539
23970
|
else {
|
|
@@ -24564,7 +24995,7 @@ class ClientPartial {
|
|
|
24564
24995
|
* @param backtest - Whether running in backtest mode
|
|
24565
24996
|
* @returns Unique string key for memoization
|
|
24566
24997
|
*/
|
|
24567
|
-
const CREATE_KEY_FN$
|
|
24998
|
+
const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
24568
24999
|
/**
|
|
24569
25000
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
24570
25001
|
*
|
|
@@ -24686,7 +25117,7 @@ class PartialConnectionService {
|
|
|
24686
25117
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
24687
25118
|
* Value: ClientPartial instance with logger and event emitters
|
|
24688
25119
|
*/
|
|
24689
|
-
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
25120
|
+
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
|
|
24690
25121
|
return new ClientPartial({
|
|
24691
25122
|
signalId,
|
|
24692
25123
|
logger: this.loggerService,
|
|
@@ -24776,7 +25207,7 @@ class PartialConnectionService {
|
|
|
24776
25207
|
const partial = this.getPartial(data.id, backtest);
|
|
24777
25208
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
24778
25209
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
24779
|
-
const key = CREATE_KEY_FN$
|
|
25210
|
+
const key = CREATE_KEY_FN$h(data.id, backtest);
|
|
24780
25211
|
this.getPartial.clear(key);
|
|
24781
25212
|
};
|
|
24782
25213
|
}
|
|
@@ -24792,7 +25223,7 @@ class PartialConnectionService {
|
|
|
24792
25223
|
* @param backtest - Whether running in backtest mode
|
|
24793
25224
|
* @returns Unique string key for memoization
|
|
24794
25225
|
*/
|
|
24795
|
-
const CREATE_KEY_FN$
|
|
25226
|
+
const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24796
25227
|
const parts = [symbol, strategyName, exchangeName];
|
|
24797
25228
|
if (frameName)
|
|
24798
25229
|
parts.push(frameName);
|
|
@@ -25015,7 +25446,7 @@ class PartialMarkdownService {
|
|
|
25015
25446
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
25016
25447
|
* Each combination gets its own isolated storage instance.
|
|
25017
25448
|
*/
|
|
25018
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
25449
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
|
|
25019
25450
|
/**
|
|
25020
25451
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
25021
25452
|
* Protected against multiple subscriptions.
|
|
@@ -25225,7 +25656,7 @@ class PartialMarkdownService {
|
|
|
25225
25656
|
payload,
|
|
25226
25657
|
});
|
|
25227
25658
|
if (payload) {
|
|
25228
|
-
const key = CREATE_KEY_FN$
|
|
25659
|
+
const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
25229
25660
|
this.getStorage.clear(key);
|
|
25230
25661
|
}
|
|
25231
25662
|
else {
|
|
@@ -25241,7 +25672,7 @@ class PartialMarkdownService {
|
|
|
25241
25672
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
25242
25673
|
* @returns Unique string key for memoization
|
|
25243
25674
|
*/
|
|
25244
|
-
const CREATE_KEY_FN$
|
|
25675
|
+
const CREATE_KEY_FN$f = (context) => {
|
|
25245
25676
|
const parts = [context.strategyName, context.exchangeName];
|
|
25246
25677
|
if (context.frameName)
|
|
25247
25678
|
parts.push(context.frameName);
|
|
@@ -25315,7 +25746,7 @@ class PartialGlobalService {
|
|
|
25315
25746
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
25316
25747
|
* @param methodName - Name of the calling method for error tracking
|
|
25317
25748
|
*/
|
|
25318
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
25749
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
|
|
25319
25750
|
this.loggerService.log("partialGlobalService validate", {
|
|
25320
25751
|
context,
|
|
25321
25752
|
methodName,
|
|
@@ -25770,7 +26201,7 @@ class ClientBreakeven {
|
|
|
25770
26201
|
* @param backtest - Whether running in backtest mode
|
|
25771
26202
|
* @returns Unique string key for memoization
|
|
25772
26203
|
*/
|
|
25773
|
-
const CREATE_KEY_FN$
|
|
26204
|
+
const CREATE_KEY_FN$e = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
25774
26205
|
/**
|
|
25775
26206
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
25776
26207
|
*
|
|
@@ -25856,7 +26287,7 @@ class BreakevenConnectionService {
|
|
|
25856
26287
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
25857
26288
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
25858
26289
|
*/
|
|
25859
|
-
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
26290
|
+
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$e(signalId, backtest), (signalId, backtest) => {
|
|
25860
26291
|
return new ClientBreakeven({
|
|
25861
26292
|
signalId,
|
|
25862
26293
|
logger: this.loggerService,
|
|
@@ -25917,7 +26348,7 @@ class BreakevenConnectionService {
|
|
|
25917
26348
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
25918
26349
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
25919
26350
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
25920
|
-
const key = CREATE_KEY_FN$
|
|
26351
|
+
const key = CREATE_KEY_FN$e(data.id, backtest);
|
|
25921
26352
|
this.getBreakeven.clear(key);
|
|
25922
26353
|
};
|
|
25923
26354
|
}
|
|
@@ -25933,7 +26364,7 @@ class BreakevenConnectionService {
|
|
|
25933
26364
|
* @param backtest - Whether running in backtest mode
|
|
25934
26365
|
* @returns Unique string key for memoization
|
|
25935
26366
|
*/
|
|
25936
|
-
const CREATE_KEY_FN$
|
|
26367
|
+
const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
25937
26368
|
const parts = [symbol, strategyName, exchangeName];
|
|
25938
26369
|
if (frameName)
|
|
25939
26370
|
parts.push(frameName);
|
|
@@ -26108,7 +26539,7 @@ class BreakevenMarkdownService {
|
|
|
26108
26539
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26109
26540
|
* Each combination gets its own isolated storage instance.
|
|
26110
26541
|
*/
|
|
26111
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
26542
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
|
|
26112
26543
|
/**
|
|
26113
26544
|
* Subscribes to breakeven signal emitter to receive events.
|
|
26114
26545
|
* Protected against multiple subscriptions.
|
|
@@ -26297,7 +26728,7 @@ class BreakevenMarkdownService {
|
|
|
26297
26728
|
payload,
|
|
26298
26729
|
});
|
|
26299
26730
|
if (payload) {
|
|
26300
|
-
const key = CREATE_KEY_FN$
|
|
26731
|
+
const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26301
26732
|
this.getStorage.clear(key);
|
|
26302
26733
|
}
|
|
26303
26734
|
else {
|
|
@@ -26313,7 +26744,7 @@ class BreakevenMarkdownService {
|
|
|
26313
26744
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
26314
26745
|
* @returns Unique string key for memoization
|
|
26315
26746
|
*/
|
|
26316
|
-
const CREATE_KEY_FN$
|
|
26747
|
+
const CREATE_KEY_FN$c = (context) => {
|
|
26317
26748
|
const parts = [context.strategyName, context.exchangeName];
|
|
26318
26749
|
if (context.frameName)
|
|
26319
26750
|
parts.push(context.frameName);
|
|
@@ -26387,7 +26818,7 @@ class BreakevenGlobalService {
|
|
|
26387
26818
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
26388
26819
|
* @param methodName - Name of the calling method for error tracking
|
|
26389
26820
|
*/
|
|
26390
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
26821
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$c(context), (context, methodName) => {
|
|
26391
26822
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
26392
26823
|
context,
|
|
26393
26824
|
methodName,
|
|
@@ -26608,7 +27039,7 @@ class ConfigValidationService {
|
|
|
26608
27039
|
* @param backtest - Whether running in backtest mode
|
|
26609
27040
|
* @returns Unique string key for memoization
|
|
26610
27041
|
*/
|
|
26611
|
-
const CREATE_KEY_FN$
|
|
27042
|
+
const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
26612
27043
|
const parts = [symbol, strategyName, exchangeName];
|
|
26613
27044
|
if (frameName)
|
|
26614
27045
|
parts.push(frameName);
|
|
@@ -26775,7 +27206,7 @@ class RiskMarkdownService {
|
|
|
26775
27206
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26776
27207
|
* Each combination gets its own isolated storage instance.
|
|
26777
27208
|
*/
|
|
26778
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
27209
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
|
|
26779
27210
|
/**
|
|
26780
27211
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
26781
27212
|
* Protected against multiple subscriptions.
|
|
@@ -26964,7 +27395,7 @@ class RiskMarkdownService {
|
|
|
26964
27395
|
payload,
|
|
26965
27396
|
});
|
|
26966
27397
|
if (payload) {
|
|
26967
|
-
const key = CREATE_KEY_FN$
|
|
27398
|
+
const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26968
27399
|
this.getStorage.clear(key);
|
|
26969
27400
|
}
|
|
26970
27401
|
else {
|
|
@@ -29343,7 +29774,7 @@ class HighestProfitReportService {
|
|
|
29343
29774
|
* @returns Colon-separated key string for memoization
|
|
29344
29775
|
* @internal
|
|
29345
29776
|
*/
|
|
29346
|
-
const CREATE_KEY_FN$
|
|
29777
|
+
const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29347
29778
|
const parts = [symbol, strategyName, exchangeName];
|
|
29348
29779
|
if (frameName)
|
|
29349
29780
|
parts.push(frameName);
|
|
@@ -29585,7 +30016,7 @@ class StrategyMarkdownService {
|
|
|
29585
30016
|
*
|
|
29586
30017
|
* @internal
|
|
29587
30018
|
*/
|
|
29588
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
30019
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
|
|
29589
30020
|
/**
|
|
29590
30021
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
29591
30022
|
*
|
|
@@ -30153,7 +30584,7 @@ class StrategyMarkdownService {
|
|
|
30153
30584
|
this.clear = async (payload) => {
|
|
30154
30585
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
30155
30586
|
if (payload) {
|
|
30156
|
-
const key = CREATE_KEY_FN$
|
|
30587
|
+
const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
30157
30588
|
this.getStorage.clear(key);
|
|
30158
30589
|
}
|
|
30159
30590
|
else {
|
|
@@ -30261,7 +30692,7 @@ class StrategyMarkdownService {
|
|
|
30261
30692
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
30262
30693
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
30263
30694
|
*/
|
|
30264
|
-
const CREATE_KEY_FN$
|
|
30695
|
+
const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
30265
30696
|
const parts = [symbol, strategyName, exchangeName];
|
|
30266
30697
|
if (frameName)
|
|
30267
30698
|
parts.push(frameName);
|
|
@@ -30454,7 +30885,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
30454
30885
|
class SyncMarkdownService {
|
|
30455
30886
|
constructor() {
|
|
30456
30887
|
this.loggerService = inject(TYPES.loggerService);
|
|
30457
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
30888
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
|
|
30458
30889
|
/**
|
|
30459
30890
|
* Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
|
|
30460
30891
|
* Protected against multiple subscriptions via `singleshot` — subsequent calls
|
|
@@ -30650,7 +31081,7 @@ class SyncMarkdownService {
|
|
|
30650
31081
|
this.clear = async (payload) => {
|
|
30651
31082
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
30652
31083
|
if (payload) {
|
|
30653
|
-
const key = CREATE_KEY_FN$
|
|
31084
|
+
const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
30654
31085
|
this.getStorage.clear(key);
|
|
30655
31086
|
}
|
|
30656
31087
|
else {
|
|
@@ -30663,7 +31094,7 @@ class SyncMarkdownService {
|
|
|
30663
31094
|
/**
|
|
30664
31095
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
30665
31096
|
*/
|
|
30666
|
-
const CREATE_KEY_FN$
|
|
31097
|
+
const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
30667
31098
|
const parts = [symbol, strategyName, exchangeName];
|
|
30668
31099
|
if (frameName)
|
|
30669
31100
|
parts.push(frameName);
|
|
@@ -30839,7 +31270,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
30839
31270
|
class HighestProfitMarkdownService {
|
|
30840
31271
|
constructor() {
|
|
30841
31272
|
this.loggerService = inject(TYPES.loggerService);
|
|
30842
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31273
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
|
|
30843
31274
|
/**
|
|
30844
31275
|
* Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
|
|
30845
31276
|
* events. Protected against multiple subscriptions via `singleshot` — subsequent
|
|
@@ -31005,7 +31436,7 @@ class HighestProfitMarkdownService {
|
|
|
31005
31436
|
this.clear = async (payload) => {
|
|
31006
31437
|
this.loggerService.log("highestProfitMarkdownService clear", { payload });
|
|
31007
31438
|
if (payload) {
|
|
31008
|
-
const key = CREATE_KEY_FN$
|
|
31439
|
+
const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31009
31440
|
this.getStorage.clear(key);
|
|
31010
31441
|
}
|
|
31011
31442
|
else {
|
|
@@ -31027,7 +31458,7 @@ const LISTEN_TIMEOUT$1 = 120000;
|
|
|
31027
31458
|
* @param backtest - Whether running in backtest mode
|
|
31028
31459
|
* @returns Unique string key for memoization
|
|
31029
31460
|
*/
|
|
31030
|
-
const CREATE_KEY_FN$
|
|
31461
|
+
const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31031
31462
|
const parts = [symbol, strategyName, exchangeName];
|
|
31032
31463
|
if (frameName)
|
|
31033
31464
|
parts.push(frameName);
|
|
@@ -31070,7 +31501,7 @@ class PriceMetaService {
|
|
|
31070
31501
|
* Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
|
|
31071
31502
|
* Instances are cached until clear() is called.
|
|
31072
31503
|
*/
|
|
31073
|
-
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31504
|
+
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
|
|
31074
31505
|
/**
|
|
31075
31506
|
* Returns the current market price for the given symbol and context.
|
|
31076
31507
|
*
|
|
@@ -31099,10 +31530,10 @@ class PriceMetaService {
|
|
|
31099
31530
|
if (source.data) {
|
|
31100
31531
|
return source.data;
|
|
31101
31532
|
}
|
|
31102
|
-
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$
|
|
31533
|
+
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
31103
31534
|
const currentPrice = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
|
|
31104
31535
|
if (typeof currentPrice === "symbol") {
|
|
31105
|
-
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$
|
|
31536
|
+
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31106
31537
|
}
|
|
31107
31538
|
return currentPrice;
|
|
31108
31539
|
};
|
|
@@ -31144,7 +31575,7 @@ class PriceMetaService {
|
|
|
31144
31575
|
this.getSource.clear();
|
|
31145
31576
|
return;
|
|
31146
31577
|
}
|
|
31147
|
-
const key = CREATE_KEY_FN$
|
|
31578
|
+
const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31148
31579
|
this.getSource.clear(key);
|
|
31149
31580
|
};
|
|
31150
31581
|
}
|
|
@@ -31162,7 +31593,7 @@ const LISTEN_TIMEOUT = 120000;
|
|
|
31162
31593
|
* @param backtest - Whether running in backtest mode
|
|
31163
31594
|
* @returns Unique string key for memoization
|
|
31164
31595
|
*/
|
|
31165
|
-
const CREATE_KEY_FN$
|
|
31596
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31166
31597
|
const parts = [symbol, strategyName, exchangeName];
|
|
31167
31598
|
if (frameName)
|
|
31168
31599
|
parts.push(frameName);
|
|
@@ -31205,7 +31636,7 @@ class TimeMetaService {
|
|
|
31205
31636
|
* Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
|
|
31206
31637
|
* Instances are cached until clear() is called.
|
|
31207
31638
|
*/
|
|
31208
|
-
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31639
|
+
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
|
|
31209
31640
|
/**
|
|
31210
31641
|
* Returns the current candle timestamp (in milliseconds) for the given symbol and context.
|
|
31211
31642
|
*
|
|
@@ -31233,10 +31664,10 @@ class TimeMetaService {
|
|
|
31233
31664
|
if (source.data) {
|
|
31234
31665
|
return source.data;
|
|
31235
31666
|
}
|
|
31236
|
-
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$
|
|
31667
|
+
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
31237
31668
|
const timestamp = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
|
|
31238
31669
|
if (typeof timestamp === "symbol") {
|
|
31239
|
-
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$
|
|
31670
|
+
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31240
31671
|
}
|
|
31241
31672
|
return timestamp;
|
|
31242
31673
|
};
|
|
@@ -31278,7 +31709,7 @@ class TimeMetaService {
|
|
|
31278
31709
|
this.getSource.clear();
|
|
31279
31710
|
return;
|
|
31280
31711
|
}
|
|
31281
|
-
const key = CREATE_KEY_FN$
|
|
31712
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31282
31713
|
this.getSource.clear(key);
|
|
31283
31714
|
};
|
|
31284
31715
|
}
|
|
@@ -31374,7 +31805,7 @@ class MaxDrawdownReportService {
|
|
|
31374
31805
|
/**
|
|
31375
31806
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
31376
31807
|
*/
|
|
31377
|
-
const CREATE_KEY_FN$
|
|
31808
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31378
31809
|
const parts = [symbol, strategyName, exchangeName];
|
|
31379
31810
|
if (frameName)
|
|
31380
31811
|
parts.push(frameName);
|
|
@@ -31498,7 +31929,7 @@ class ReportStorage {
|
|
|
31498
31929
|
class MaxDrawdownMarkdownService {
|
|
31499
31930
|
constructor() {
|
|
31500
31931
|
this.loggerService = inject(TYPES.loggerService);
|
|
31501
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31932
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
|
|
31502
31933
|
/**
|
|
31503
31934
|
* Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
|
|
31504
31935
|
* events. Protected against multiple subscriptions via `singleshot`.
|
|
@@ -31577,7 +32008,7 @@ class MaxDrawdownMarkdownService {
|
|
|
31577
32008
|
this.clear = async (payload) => {
|
|
31578
32009
|
this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
|
|
31579
32010
|
if (payload) {
|
|
31580
|
-
const key = CREATE_KEY_FN$
|
|
32011
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31581
32012
|
this.getStorage.clear(key);
|
|
31582
32013
|
}
|
|
31583
32014
|
else {
|
|
@@ -31831,7 +32262,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
|
31831
32262
|
const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
|
|
31832
32263
|
const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
|
|
31833
32264
|
const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
|
|
31834
|
-
const MS_PER_MINUTE$
|
|
32265
|
+
const MS_PER_MINUTE$3 = 60000;
|
|
31835
32266
|
/**
|
|
31836
32267
|
* Gets current timestamp from execution context if available.
|
|
31837
32268
|
* Returns current Date() if no execution context exists (non-trading GUI).
|
|
@@ -31898,7 +32329,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
|
|
|
31898
32329
|
const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
|
|
31899
32330
|
throw new Error(`getAggregatedTrades is not implemented for this exchange`);
|
|
31900
32331
|
};
|
|
31901
|
-
const INTERVAL_MINUTES$
|
|
32332
|
+
const INTERVAL_MINUTES$3 = {
|
|
31902
32333
|
"1m": 1,
|
|
31903
32334
|
"3m": 3,
|
|
31904
32335
|
"5m": 5,
|
|
@@ -31930,7 +32361,7 @@ const INTERVAL_MINUTES$2 = {
|
|
|
31930
32361
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
31931
32362
|
*/
|
|
31932
32363
|
const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
|
|
31933
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
32364
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
|
|
31934
32365
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
31935
32366
|
};
|
|
31936
32367
|
/**
|
|
@@ -32070,11 +32501,11 @@ class ExchangeInstance {
|
|
|
32070
32501
|
limit,
|
|
32071
32502
|
});
|
|
32072
32503
|
const getCandles = this._methods.getCandles;
|
|
32073
|
-
const step = INTERVAL_MINUTES$
|
|
32504
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
32074
32505
|
if (!step) {
|
|
32075
32506
|
throw new Error(`ExchangeInstance unknown interval=${interval}`);
|
|
32076
32507
|
}
|
|
32077
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32508
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
32078
32509
|
// Align when down to interval boundary
|
|
32079
32510
|
const when = await GET_TIMESTAMP_FN();
|
|
32080
32511
|
const whenTimestamp = when.getTime();
|
|
@@ -32259,7 +32690,7 @@ class ExchangeInstance {
|
|
|
32259
32690
|
const when = await GET_TIMESTAMP_FN();
|
|
32260
32691
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
32261
32692
|
const to = new Date(alignedTo);
|
|
32262
|
-
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
32693
|
+
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
|
|
32263
32694
|
const isBacktest = await GET_BACKTEST_FN();
|
|
32264
32695
|
return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
|
|
32265
32696
|
};
|
|
@@ -32291,7 +32722,7 @@ class ExchangeInstance {
|
|
|
32291
32722
|
const when = await GET_TIMESTAMP_FN();
|
|
32292
32723
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
32293
32724
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
|
|
32294
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
32725
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$3 - MS_PER_MINUTE$3;
|
|
32295
32726
|
const isBacktest = await GET_BACKTEST_FN();
|
|
32296
32727
|
// No limit: fetch a single window and return as-is
|
|
32297
32728
|
if (limit === undefined) {
|
|
@@ -32354,11 +32785,11 @@ class ExchangeInstance {
|
|
|
32354
32785
|
sDate,
|
|
32355
32786
|
eDate,
|
|
32356
32787
|
});
|
|
32357
|
-
const step = INTERVAL_MINUTES$
|
|
32788
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
32358
32789
|
if (!step) {
|
|
32359
32790
|
throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
|
|
32360
32791
|
}
|
|
32361
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32792
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
32362
32793
|
const when = await GET_TIMESTAMP_FN();
|
|
32363
32794
|
const nowTimestamp = when.getTime();
|
|
32364
32795
|
const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
|
|
@@ -32648,8 +33079,8 @@ const Exchange = new ExchangeUtils();
|
|
|
32648
33079
|
|
|
32649
33080
|
const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
|
|
32650
33081
|
const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
|
|
32651
|
-
const MS_PER_MINUTE$
|
|
32652
|
-
const INTERVAL_MINUTES$
|
|
33082
|
+
const MS_PER_MINUTE$2 = 60000;
|
|
33083
|
+
const INTERVAL_MINUTES$2 = {
|
|
32653
33084
|
"1m": 1,
|
|
32654
33085
|
"3m": 3,
|
|
32655
33086
|
"5m": 5,
|
|
@@ -32664,7 +33095,7 @@ const INTERVAL_MINUTES$1 = {
|
|
|
32664
33095
|
"1w": 10080,
|
|
32665
33096
|
};
|
|
32666
33097
|
const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
|
|
32667
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
33098
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
|
|
32668
33099
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
32669
33100
|
};
|
|
32670
33101
|
const BAR_LENGTH = 30;
|
|
@@ -32689,11 +33120,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
|
|
|
32689
33120
|
async function checkCandles(params) {
|
|
32690
33121
|
const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
|
|
32691
33122
|
backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
|
|
32692
|
-
const step = INTERVAL_MINUTES$
|
|
33123
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
32693
33124
|
if (!step) {
|
|
32694
33125
|
throw new Error(`checkCandles: unsupported interval=${interval}`);
|
|
32695
33126
|
}
|
|
32696
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
33127
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
32697
33128
|
const dir = join(baseDir, exchangeName, symbol, interval);
|
|
32698
33129
|
const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
32699
33130
|
const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -32763,11 +33194,11 @@ async function warmCandles(params) {
|
|
|
32763
33194
|
from,
|
|
32764
33195
|
to,
|
|
32765
33196
|
});
|
|
32766
|
-
const step = INTERVAL_MINUTES$
|
|
33197
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
32767
33198
|
if (!step) {
|
|
32768
33199
|
throw new Error(`warmCandles: unsupported interval=${interval}`);
|
|
32769
33200
|
}
|
|
32770
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
33201
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
32771
33202
|
const instance = new ExchangeInstance(exchangeName);
|
|
32772
33203
|
const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
32773
33204
|
const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -34706,6 +35137,10 @@ const GET_POSITION_MAX_DRAWDOWN_PRICE_METHOD_NAME = "strategy.getPositionMaxDraw
|
|
|
34706
35137
|
const GET_POSITION_MAX_DRAWDOWN_TIMESTAMP_METHOD_NAME = "strategy.getPositionMaxDrawdownTimestamp";
|
|
34707
35138
|
const GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlPercentage";
|
|
34708
35139
|
const GET_POSITION_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlCost";
|
|
35140
|
+
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlPercentage";
|
|
35141
|
+
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
|
|
35142
|
+
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
|
|
35143
|
+
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
|
|
34709
35144
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
34710
35145
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
34711
35146
|
const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
|
|
@@ -36340,6 +36775,122 @@ async function getPositionMaxDrawdownPnlCost(symbol) {
|
|
|
36340
36775
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36341
36776
|
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36342
36777
|
}
|
|
36778
|
+
/**
|
|
36779
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
36780
|
+
*
|
|
36781
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
36782
|
+
* Returns null if no pending signal exists.
|
|
36783
|
+
*
|
|
36784
|
+
* @param symbol - Trading pair symbol
|
|
36785
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
36786
|
+
*
|
|
36787
|
+
* @example
|
|
36788
|
+
* ```typescript
|
|
36789
|
+
* import { getPositionHighestProfitDistancePnlPercentage } from "backtest-kit";
|
|
36790
|
+
*
|
|
36791
|
+
* const dist = await getPositionHighestProfitDistancePnlPercentage("BTCUSDT");
|
|
36792
|
+
* // e.g. 1.5 (gave back 1.5% from peak)
|
|
36793
|
+
* ```
|
|
36794
|
+
*/
|
|
36795
|
+
async function getPositionHighestProfitDistancePnlPercentage(symbol) {
|
|
36796
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
36797
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36798
|
+
throw new Error("getPositionHighestProfitDistancePnlPercentage requires an execution context");
|
|
36799
|
+
}
|
|
36800
|
+
if (!MethodContextService.hasContext()) {
|
|
36801
|
+
throw new Error("getPositionHighestProfitDistancePnlPercentage requires a method context");
|
|
36802
|
+
}
|
|
36803
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36804
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36805
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36806
|
+
}
|
|
36807
|
+
/**
|
|
36808
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
36809
|
+
*
|
|
36810
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
36811
|
+
* Returns null if no pending signal exists.
|
|
36812
|
+
*
|
|
36813
|
+
* @param symbol - Trading pair symbol
|
|
36814
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
36815
|
+
*
|
|
36816
|
+
* @example
|
|
36817
|
+
* ```typescript
|
|
36818
|
+
* import { getPositionHighestProfitDistancePnlCost } from "backtest-kit";
|
|
36819
|
+
*
|
|
36820
|
+
* const dist = await getPositionHighestProfitDistancePnlCost("BTCUSDT");
|
|
36821
|
+
* // e.g. 3.2 (gave back $3.2 from peak)
|
|
36822
|
+
* ```
|
|
36823
|
+
*/
|
|
36824
|
+
async function getPositionHighestProfitDistancePnlCost(symbol) {
|
|
36825
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
|
|
36826
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36827
|
+
throw new Error("getPositionHighestProfitDistancePnlCost requires an execution context");
|
|
36828
|
+
}
|
|
36829
|
+
if (!MethodContextService.hasContext()) {
|
|
36830
|
+
throw new Error("getPositionHighestProfitDistancePnlCost requires a method context");
|
|
36831
|
+
}
|
|
36832
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36833
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36834
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36835
|
+
}
|
|
36836
|
+
/**
|
|
36837
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
36838
|
+
*
|
|
36839
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
36840
|
+
* Returns null if no pending signal exists.
|
|
36841
|
+
*
|
|
36842
|
+
* @param symbol - Trading pair symbol
|
|
36843
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
36844
|
+
*
|
|
36845
|
+
* @example
|
|
36846
|
+
* ```typescript
|
|
36847
|
+
* import { getPositionHighestMaxDrawdownPnlPercentage } from "backtest-kit";
|
|
36848
|
+
*
|
|
36849
|
+
* const dist = await getPositionHighestMaxDrawdownPnlPercentage("BTCUSDT");
|
|
36850
|
+
* // e.g. 2.1 (recovered 2.1% from trough)
|
|
36851
|
+
* ```
|
|
36852
|
+
*/
|
|
36853
|
+
async function getPositionHighestMaxDrawdownPnlPercentage(symbol) {
|
|
36854
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
36855
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36856
|
+
throw new Error("getPositionHighestMaxDrawdownPnlPercentage requires an execution context");
|
|
36857
|
+
}
|
|
36858
|
+
if (!MethodContextService.hasContext()) {
|
|
36859
|
+
throw new Error("getPositionHighestMaxDrawdownPnlPercentage requires a method context");
|
|
36860
|
+
}
|
|
36861
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36862
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36863
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36864
|
+
}
|
|
36865
|
+
/**
|
|
36866
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
36867
|
+
*
|
|
36868
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
36869
|
+
* Returns null if no pending signal exists.
|
|
36870
|
+
*
|
|
36871
|
+
* @param symbol - Trading pair symbol
|
|
36872
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
36873
|
+
*
|
|
36874
|
+
* @example
|
|
36875
|
+
* ```typescript
|
|
36876
|
+
* import { getPositionHighestMaxDrawdownPnlCost } from "backtest-kit";
|
|
36877
|
+
*
|
|
36878
|
+
* const dist = await getPositionHighestMaxDrawdownPnlCost("BTCUSDT");
|
|
36879
|
+
* // e.g. 4.8 (recovered $4.8 from trough)
|
|
36880
|
+
* ```
|
|
36881
|
+
*/
|
|
36882
|
+
async function getPositionHighestMaxDrawdownPnlCost(symbol) {
|
|
36883
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME, { symbol });
|
|
36884
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36885
|
+
throw new Error("getPositionHighestMaxDrawdownPnlCost requires an execution context");
|
|
36886
|
+
}
|
|
36887
|
+
if (!MethodContextService.hasContext()) {
|
|
36888
|
+
throw new Error("getPositionHighestMaxDrawdownPnlCost requires a method context");
|
|
36889
|
+
}
|
|
36890
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36891
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36892
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36893
|
+
}
|
|
36343
36894
|
/**
|
|
36344
36895
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
36345
36896
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -38009,6 +38560,10 @@ const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "BacktestUtils.getP
|
|
|
38009
38560
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "BacktestUtils.getPositionMaxDrawdownTimestamp";
|
|
38010
38561
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionMaxDrawdownPnlPercentage";
|
|
38011
38562
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionMaxDrawdownPnlCost";
|
|
38563
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
38564
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
|
|
38565
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
38566
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
38012
38567
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
38013
38568
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
38014
38569
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -39278,6 +39833,118 @@ class BacktestUtils {
|
|
|
39278
39833
|
}
|
|
39279
39834
|
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(true, symbol, context);
|
|
39280
39835
|
};
|
|
39836
|
+
/**
|
|
39837
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
39838
|
+
*
|
|
39839
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
39840
|
+
* Returns null if no pending signal exists.
|
|
39841
|
+
*
|
|
39842
|
+
* @param symbol - Trading pair symbol
|
|
39843
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39844
|
+
* @returns drawdown distance in PnL% (≥ 0) or null if no active position
|
|
39845
|
+
*/
|
|
39846
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context) => {
|
|
39847
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, {
|
|
39848
|
+
symbol,
|
|
39849
|
+
context,
|
|
39850
|
+
});
|
|
39851
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39852
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39853
|
+
{
|
|
39854
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39855
|
+
riskName &&
|
|
39856
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39857
|
+
riskList &&
|
|
39858
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
39859
|
+
actions &&
|
|
39860
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
39861
|
+
}
|
|
39862
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(true, symbol, context);
|
|
39863
|
+
};
|
|
39864
|
+
/**
|
|
39865
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
39866
|
+
*
|
|
39867
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
39868
|
+
* Returns null if no pending signal exists.
|
|
39869
|
+
*
|
|
39870
|
+
* @param symbol - Trading pair symbol
|
|
39871
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39872
|
+
* @returns drawdown distance in PnL cost (≥ 0) or null if no active position
|
|
39873
|
+
*/
|
|
39874
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context) => {
|
|
39875
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, {
|
|
39876
|
+
symbol,
|
|
39877
|
+
context,
|
|
39878
|
+
});
|
|
39879
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39880
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39881
|
+
{
|
|
39882
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39883
|
+
riskName &&
|
|
39884
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39885
|
+
riskList &&
|
|
39886
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
39887
|
+
actions &&
|
|
39888
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
39889
|
+
}
|
|
39890
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(true, symbol, context);
|
|
39891
|
+
};
|
|
39892
|
+
/**
|
|
39893
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
39894
|
+
*
|
|
39895
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
39896
|
+
* Returns null if no pending signal exists.
|
|
39897
|
+
*
|
|
39898
|
+
* @param symbol - Trading pair symbol
|
|
39899
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39900
|
+
* @returns recovery distance in PnL% (≥ 0) or null if no active position
|
|
39901
|
+
*/
|
|
39902
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context) => {
|
|
39903
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, {
|
|
39904
|
+
symbol,
|
|
39905
|
+
context,
|
|
39906
|
+
});
|
|
39907
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39908
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39909
|
+
{
|
|
39910
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39911
|
+
riskName &&
|
|
39912
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39913
|
+
riskList &&
|
|
39914
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
39915
|
+
actions &&
|
|
39916
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
39917
|
+
}
|
|
39918
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(true, symbol, context);
|
|
39919
|
+
};
|
|
39920
|
+
/**
|
|
39921
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
39922
|
+
*
|
|
39923
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
39924
|
+
* Returns null if no pending signal exists.
|
|
39925
|
+
*
|
|
39926
|
+
* @param symbol - Trading pair symbol
|
|
39927
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39928
|
+
* @returns recovery distance in PnL cost (≥ 0) or null if no active position
|
|
39929
|
+
*/
|
|
39930
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context) => {
|
|
39931
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, {
|
|
39932
|
+
symbol,
|
|
39933
|
+
context,
|
|
39934
|
+
});
|
|
39935
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39936
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39937
|
+
{
|
|
39938
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39939
|
+
riskName &&
|
|
39940
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39941
|
+
riskList &&
|
|
39942
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
39943
|
+
actions &&
|
|
39944
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
39945
|
+
}
|
|
39946
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
|
|
39947
|
+
};
|
|
39281
39948
|
/**
|
|
39282
39949
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
39283
39950
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -40403,6 +41070,10 @@ const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "LiveUtils.getPositionM
|
|
|
40403
41070
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "LiveUtils.getPositionMaxDrawdownTimestamp";
|
|
40404
41071
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionMaxDrawdownPnlPercentage";
|
|
40405
41072
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionMaxDrawdownPnlCost";
|
|
41073
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
41074
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
|
|
41075
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
41076
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
40406
41077
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
40407
41078
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
40408
41079
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -41799,6 +42470,134 @@ class LiveUtils {
|
|
|
41799
42470
|
frameName: "",
|
|
41800
42471
|
});
|
|
41801
42472
|
};
|
|
42473
|
+
/**
|
|
42474
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
42475
|
+
*
|
|
42476
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
42477
|
+
* Returns null if no pending signal exists.
|
|
42478
|
+
*
|
|
42479
|
+
* @param symbol - Trading pair symbol
|
|
42480
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42481
|
+
* @returns drawdown distance in PnL% (≥ 0) or null if no active position
|
|
42482
|
+
*/
|
|
42483
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context) => {
|
|
42484
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, {
|
|
42485
|
+
symbol,
|
|
42486
|
+
context,
|
|
42487
|
+
});
|
|
42488
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42489
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42490
|
+
{
|
|
42491
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42492
|
+
riskName &&
|
|
42493
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42494
|
+
riskList &&
|
|
42495
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
42496
|
+
actions &&
|
|
42497
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
42498
|
+
}
|
|
42499
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(false, symbol, {
|
|
42500
|
+
strategyName: context.strategyName,
|
|
42501
|
+
exchangeName: context.exchangeName,
|
|
42502
|
+
frameName: "",
|
|
42503
|
+
});
|
|
42504
|
+
};
|
|
42505
|
+
/**
|
|
42506
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
42507
|
+
*
|
|
42508
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
42509
|
+
* Returns null if no pending signal exists.
|
|
42510
|
+
*
|
|
42511
|
+
* @param symbol - Trading pair symbol
|
|
42512
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42513
|
+
* @returns drawdown distance in PnL cost (≥ 0) or null if no active position
|
|
42514
|
+
*/
|
|
42515
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context) => {
|
|
42516
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, {
|
|
42517
|
+
symbol,
|
|
42518
|
+
context,
|
|
42519
|
+
});
|
|
42520
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42521
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42522
|
+
{
|
|
42523
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42524
|
+
riskName &&
|
|
42525
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42526
|
+
riskList &&
|
|
42527
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
42528
|
+
actions &&
|
|
42529
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
42530
|
+
}
|
|
42531
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(false, symbol, {
|
|
42532
|
+
strategyName: context.strategyName,
|
|
42533
|
+
exchangeName: context.exchangeName,
|
|
42534
|
+
frameName: "",
|
|
42535
|
+
});
|
|
42536
|
+
};
|
|
42537
|
+
/**
|
|
42538
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
42539
|
+
*
|
|
42540
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
42541
|
+
* Returns null if no pending signal exists.
|
|
42542
|
+
*
|
|
42543
|
+
* @param symbol - Trading pair symbol
|
|
42544
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42545
|
+
* @returns recovery distance in PnL% (≥ 0) or null if no active position
|
|
42546
|
+
*/
|
|
42547
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context) => {
|
|
42548
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, {
|
|
42549
|
+
symbol,
|
|
42550
|
+
context,
|
|
42551
|
+
});
|
|
42552
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42553
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42554
|
+
{
|
|
42555
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42556
|
+
riskName &&
|
|
42557
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42558
|
+
riskList &&
|
|
42559
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
42560
|
+
actions &&
|
|
42561
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
42562
|
+
}
|
|
42563
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(false, symbol, {
|
|
42564
|
+
strategyName: context.strategyName,
|
|
42565
|
+
exchangeName: context.exchangeName,
|
|
42566
|
+
frameName: "",
|
|
42567
|
+
});
|
|
42568
|
+
};
|
|
42569
|
+
/**
|
|
42570
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
42571
|
+
*
|
|
42572
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
42573
|
+
* Returns null if no pending signal exists.
|
|
42574
|
+
*
|
|
42575
|
+
* @param symbol - Trading pair symbol
|
|
42576
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42577
|
+
* @returns recovery distance in PnL cost (≥ 0) or null if no active position
|
|
42578
|
+
*/
|
|
42579
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context) => {
|
|
42580
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, {
|
|
42581
|
+
symbol,
|
|
42582
|
+
context,
|
|
42583
|
+
});
|
|
42584
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42585
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42586
|
+
{
|
|
42587
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42588
|
+
riskName &&
|
|
42589
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42590
|
+
riskList &&
|
|
42591
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
42592
|
+
actions &&
|
|
42593
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
42594
|
+
}
|
|
42595
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(false, symbol, {
|
|
42596
|
+
strategyName: context.strategyName,
|
|
42597
|
+
exchangeName: context.exchangeName,
|
|
42598
|
+
frameName: "",
|
|
42599
|
+
});
|
|
42600
|
+
};
|
|
41802
42601
|
/**
|
|
41803
42602
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
41804
42603
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -44176,7 +44975,7 @@ const createSearchIndex = () => {
|
|
|
44176
44975
|
return { upsert, remove, list, search, read };
|
|
44177
44976
|
};
|
|
44178
44977
|
|
|
44179
|
-
const CREATE_KEY_FN$
|
|
44978
|
+
const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}-${bucketName}`;
|
|
44180
44979
|
const LIST_MEMORY_FN = ({ id, content }) => ({
|
|
44181
44980
|
memoryId: id,
|
|
44182
44981
|
content: content,
|
|
@@ -44498,7 +45297,7 @@ class MemoryDummyInstance {
|
|
|
44498
45297
|
class MemoryAdapter {
|
|
44499
45298
|
constructor() {
|
|
44500
45299
|
this.MemoryFactory = MemoryPersistInstance;
|
|
44501
|
-
this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$
|
|
45300
|
+
this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
|
|
44502
45301
|
/**
|
|
44503
45302
|
* Activates the adapter by subscribing to signal lifecycle events.
|
|
44504
45303
|
* Clears memoized instances for a signalId when it is cancelled or closed,
|
|
@@ -44509,7 +45308,7 @@ class MemoryAdapter {
|
|
|
44509
45308
|
this.enable = singleshot(() => {
|
|
44510
45309
|
backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_ENABLE);
|
|
44511
45310
|
const handleDispose = (signalId) => {
|
|
44512
|
-
const prefix = CREATE_KEY_FN$
|
|
45311
|
+
const prefix = CREATE_KEY_FN$4(signalId, "");
|
|
44513
45312
|
for (const key of this.getInstance.keys()) {
|
|
44514
45313
|
if (key.startsWith(prefix)) {
|
|
44515
45314
|
const instance = this.getInstance.get(key);
|
|
@@ -44554,7 +45353,7 @@ class MemoryAdapter {
|
|
|
44554
45353
|
bucketName: dto.bucketName,
|
|
44555
45354
|
memoryId: dto.memoryId,
|
|
44556
45355
|
});
|
|
44557
|
-
const key = CREATE_KEY_FN$
|
|
45356
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44558
45357
|
const isInitial = !this.getInstance.has(key);
|
|
44559
45358
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44560
45359
|
await instance.waitForInit(isInitial);
|
|
@@ -44576,7 +45375,7 @@ class MemoryAdapter {
|
|
|
44576
45375
|
bucketName: dto.bucketName,
|
|
44577
45376
|
query: dto.query,
|
|
44578
45377
|
});
|
|
44579
|
-
const key = CREATE_KEY_FN$
|
|
45378
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44580
45379
|
const isInitial = !this.getInstance.has(key);
|
|
44581
45380
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44582
45381
|
await instance.waitForInit(isInitial);
|
|
@@ -44596,7 +45395,7 @@ class MemoryAdapter {
|
|
|
44596
45395
|
signalId: dto.signalId,
|
|
44597
45396
|
bucketName: dto.bucketName,
|
|
44598
45397
|
});
|
|
44599
|
-
const key = CREATE_KEY_FN$
|
|
45398
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44600
45399
|
const isInitial = !this.getInstance.has(key);
|
|
44601
45400
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44602
45401
|
await instance.waitForInit(isInitial);
|
|
@@ -44617,7 +45416,7 @@ class MemoryAdapter {
|
|
|
44617
45416
|
bucketName: dto.bucketName,
|
|
44618
45417
|
memoryId: dto.memoryId,
|
|
44619
45418
|
});
|
|
44620
|
-
const key = CREATE_KEY_FN$
|
|
45419
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44621
45420
|
const isInitial = !this.getInstance.has(key);
|
|
44622
45421
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44623
45422
|
await instance.waitForInit(isInitial);
|
|
@@ -44640,7 +45439,7 @@ class MemoryAdapter {
|
|
|
44640
45439
|
bucketName: dto.bucketName,
|
|
44641
45440
|
memoryId: dto.memoryId,
|
|
44642
45441
|
});
|
|
44643
|
-
const key = CREATE_KEY_FN$
|
|
45442
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44644
45443
|
const isInitial = !this.getInstance.has(key);
|
|
44645
45444
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44646
45445
|
await instance.waitForInit(isInitial);
|
|
@@ -44932,7 +45731,7 @@ async function removeMemory(dto) {
|
|
|
44932
45731
|
});
|
|
44933
45732
|
}
|
|
44934
45733
|
|
|
44935
|
-
const CREATE_KEY_FN$
|
|
45734
|
+
const CREATE_KEY_FN$3 = (signalId, bucketName) => `${signalId}-${bucketName}`;
|
|
44936
45735
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_AGENT = "DumpMemoryInstance.dumpAgentAnswer";
|
|
44937
45736
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_RECORD = "DumpMemoryInstance.dumpRecord";
|
|
44938
45737
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_TABLE = "DumpMemoryInstance.dumpTable";
|
|
@@ -45552,7 +46351,7 @@ class DumpDummyInstance {
|
|
|
45552
46351
|
class DumpAdapter {
|
|
45553
46352
|
constructor() {
|
|
45554
46353
|
this.DumpFactory = DumpMarkdownInstance;
|
|
45555
|
-
this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$
|
|
46354
|
+
this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.DumpFactory, [signalId, bucketName]));
|
|
45556
46355
|
/**
|
|
45557
46356
|
* Activates the adapter by subscribing to signal lifecycle events.
|
|
45558
46357
|
* Clears memoized instances for a signalId when it is cancelled or closed,
|
|
@@ -45563,7 +46362,7 @@ class DumpAdapter {
|
|
|
45563
46362
|
this.enable = singleshot(() => {
|
|
45564
46363
|
backtest.loggerService.info(DUMP_ADAPTER_METHOD_NAME_ENABLE);
|
|
45565
46364
|
const handleDispose = (signalId) => {
|
|
45566
|
-
const prefix = CREATE_KEY_FN$
|
|
46365
|
+
const prefix = CREATE_KEY_FN$3(signalId, "");
|
|
45567
46366
|
for (const key of this.getInstance.keys()) {
|
|
45568
46367
|
if (key.startsWith(prefix)) {
|
|
45569
46368
|
const instance = this.getInstance.get(key);
|
|
@@ -48497,6 +49296,49 @@ PositionSizeUtils.atrBased = async (symbol, accountBalance, priceOpen, atr, cont
|
|
|
48497
49296
|
};
|
|
48498
49297
|
const PositionSize = PositionSizeUtils;
|
|
48499
49298
|
|
|
49299
|
+
const METHOD_NAME_MOONBAG = "Position.moonbag";
|
|
49300
|
+
const METHOD_NAME_BRACKET = "Position.bracket";
|
|
49301
|
+
/**
|
|
49302
|
+
* Utilities for calculating take profit and stop loss price levels.
|
|
49303
|
+
* Automatically inverts direction based on position type (long/short).
|
|
49304
|
+
*/
|
|
49305
|
+
class Position {
|
|
49306
|
+
}
|
|
49307
|
+
/**
|
|
49308
|
+
* Calculates levels for the "moonbag" strategy — fixed TP at 50% from the current price.
|
|
49309
|
+
* @param dto.position - position type: "long" or "short"
|
|
49310
|
+
* @param dto.currentPrice - current asset price
|
|
49311
|
+
* @param dto.percentStopLoss - stop loss percentage from 0 to 100
|
|
49312
|
+
* @returns priceTakeProfit and priceStopLoss in fiat
|
|
49313
|
+
*/
|
|
49314
|
+
Position.moonbag = (dto) => {
|
|
49315
|
+
backtest.loggerService.log(METHOD_NAME_MOONBAG, { dto });
|
|
49316
|
+
const percentTakeProfit = 50;
|
|
49317
|
+
const sign = dto.position === "long" ? 1 : -1;
|
|
49318
|
+
return {
|
|
49319
|
+
position: dto.position,
|
|
49320
|
+
priceTakeProfit: dto.currentPrice * (1 + sign * percentTakeProfit / 100),
|
|
49321
|
+
priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
|
|
49322
|
+
};
|
|
49323
|
+
};
|
|
49324
|
+
/**
|
|
49325
|
+
* Calculates levels for a bracket order with custom TP and SL.
|
|
49326
|
+
* @param dto.position - position type: "long" or "short"
|
|
49327
|
+
* @param dto.currentPrice - current asset price
|
|
49328
|
+
* @param dto.percentStopLoss - stop loss percentage from 0 to 100
|
|
49329
|
+
* @param dto.percentTakeProfit - take profit percentage from 0 to 100
|
|
49330
|
+
* @returns priceTakeProfit and priceStopLoss in fiat
|
|
49331
|
+
*/
|
|
49332
|
+
Position.bracket = (dto) => {
|
|
49333
|
+
backtest.loggerService.log(METHOD_NAME_BRACKET, { dto });
|
|
49334
|
+
const sign = dto.position === "long" ? 1 : -1;
|
|
49335
|
+
return {
|
|
49336
|
+
position: dto.position,
|
|
49337
|
+
priceTakeProfit: dto.currentPrice * (1 + sign * dto.percentTakeProfit / 100),
|
|
49338
|
+
priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
|
|
49339
|
+
};
|
|
49340
|
+
};
|
|
49341
|
+
|
|
48500
49342
|
const PARTIAL_METHOD_NAME_GET_DATA = "PartialUtils.getData";
|
|
48501
49343
|
const PARTIAL_METHOD_NAME_GET_REPORT = "PartialUtils.getReport";
|
|
48502
49344
|
const PARTIAL_METHOD_NAME_DUMP = "PartialUtils.dump";
|
|
@@ -50699,7 +51541,7 @@ const StorageBacktest = new StorageBacktestAdapter();
|
|
|
50699
51541
|
* Generates a unique key for notification identification.
|
|
50700
51542
|
* @returns Random string identifier
|
|
50701
51543
|
*/
|
|
50702
|
-
const CREATE_KEY_FN$
|
|
51544
|
+
const CREATE_KEY_FN$2 = () => randomString();
|
|
50703
51545
|
/**
|
|
50704
51546
|
* Creates a notification model from signal tick result.
|
|
50705
51547
|
* Handles opened, closed, scheduled, and cancelled signal actions.
|
|
@@ -50710,7 +51552,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50710
51552
|
if (data.action === "opened") {
|
|
50711
51553
|
return {
|
|
50712
51554
|
type: "signal.opened",
|
|
50713
|
-
id: CREATE_KEY_FN$
|
|
51555
|
+
id: CREATE_KEY_FN$2(),
|
|
50714
51556
|
timestamp: data.signal.pendingAt,
|
|
50715
51557
|
backtest: data.backtest,
|
|
50716
51558
|
symbol: data.symbol,
|
|
@@ -50744,7 +51586,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50744
51586
|
const durationMin = Math.round(durationMs / 60000);
|
|
50745
51587
|
return {
|
|
50746
51588
|
type: "signal.closed",
|
|
50747
|
-
id: CREATE_KEY_FN$
|
|
51589
|
+
id: CREATE_KEY_FN$2(),
|
|
50748
51590
|
timestamp: data.closeTimestamp,
|
|
50749
51591
|
backtest: data.backtest,
|
|
50750
51592
|
symbol: data.symbol,
|
|
@@ -50778,7 +51620,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50778
51620
|
if (data.action === "scheduled") {
|
|
50779
51621
|
return {
|
|
50780
51622
|
type: "signal.scheduled",
|
|
50781
|
-
id: CREATE_KEY_FN$
|
|
51623
|
+
id: CREATE_KEY_FN$2(),
|
|
50782
51624
|
timestamp: data.signal.scheduledAt,
|
|
50783
51625
|
backtest: data.backtest,
|
|
50784
51626
|
symbol: data.symbol,
|
|
@@ -50811,7 +51653,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50811
51653
|
const durationMin = Math.round(durationMs / 60000);
|
|
50812
51654
|
return {
|
|
50813
51655
|
type: "signal.cancelled",
|
|
50814
|
-
id: CREATE_KEY_FN$
|
|
51656
|
+
id: CREATE_KEY_FN$2(),
|
|
50815
51657
|
timestamp: data.closeTimestamp,
|
|
50816
51658
|
backtest: data.backtest,
|
|
50817
51659
|
symbol: data.symbol,
|
|
@@ -50844,7 +51686,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50844
51686
|
*/
|
|
50845
51687
|
const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
50846
51688
|
type: "partial_profit.available",
|
|
50847
|
-
id: CREATE_KEY_FN$
|
|
51689
|
+
id: CREATE_KEY_FN$2(),
|
|
50848
51690
|
timestamp: data.timestamp,
|
|
50849
51691
|
backtest: data.backtest,
|
|
50850
51692
|
symbol: data.symbol,
|
|
@@ -50879,7 +51721,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
|
50879
51721
|
*/
|
|
50880
51722
|
const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
50881
51723
|
type: "partial_loss.available",
|
|
50882
|
-
id: CREATE_KEY_FN$
|
|
51724
|
+
id: CREATE_KEY_FN$2(),
|
|
50883
51725
|
timestamp: data.timestamp,
|
|
50884
51726
|
backtest: data.backtest,
|
|
50885
51727
|
symbol: data.symbol,
|
|
@@ -50914,7 +51756,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
|
50914
51756
|
*/
|
|
50915
51757
|
const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
|
|
50916
51758
|
type: "breakeven.available",
|
|
50917
|
-
id: CREATE_KEY_FN$
|
|
51759
|
+
id: CREATE_KEY_FN$2(),
|
|
50918
51760
|
timestamp: data.timestamp,
|
|
50919
51761
|
backtest: data.backtest,
|
|
50920
51762
|
symbol: data.symbol,
|
|
@@ -50952,7 +51794,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
50952
51794
|
if (data.action === "partial-profit") {
|
|
50953
51795
|
return {
|
|
50954
51796
|
type: "partial_profit.commit",
|
|
50955
|
-
id: CREATE_KEY_FN$
|
|
51797
|
+
id: CREATE_KEY_FN$2(),
|
|
50956
51798
|
timestamp: data.timestamp,
|
|
50957
51799
|
backtest: data.backtest,
|
|
50958
51800
|
symbol: data.symbol,
|
|
@@ -50984,7 +51826,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
50984
51826
|
if (data.action === "partial-loss") {
|
|
50985
51827
|
return {
|
|
50986
51828
|
type: "partial_loss.commit",
|
|
50987
|
-
id: CREATE_KEY_FN$
|
|
51829
|
+
id: CREATE_KEY_FN$2(),
|
|
50988
51830
|
timestamp: data.timestamp,
|
|
50989
51831
|
backtest: data.backtest,
|
|
50990
51832
|
symbol: data.symbol,
|
|
@@ -51016,7 +51858,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51016
51858
|
if (data.action === "breakeven") {
|
|
51017
51859
|
return {
|
|
51018
51860
|
type: "breakeven.commit",
|
|
51019
|
-
id: CREATE_KEY_FN$
|
|
51861
|
+
id: CREATE_KEY_FN$2(),
|
|
51020
51862
|
timestamp: data.timestamp,
|
|
51021
51863
|
backtest: data.backtest,
|
|
51022
51864
|
symbol: data.symbol,
|
|
@@ -51047,7 +51889,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51047
51889
|
if (data.action === "trailing-stop") {
|
|
51048
51890
|
return {
|
|
51049
51891
|
type: "trailing_stop.commit",
|
|
51050
|
-
id: CREATE_KEY_FN$
|
|
51892
|
+
id: CREATE_KEY_FN$2(),
|
|
51051
51893
|
timestamp: data.timestamp,
|
|
51052
51894
|
backtest: data.backtest,
|
|
51053
51895
|
symbol: data.symbol,
|
|
@@ -51079,7 +51921,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51079
51921
|
if (data.action === "trailing-take") {
|
|
51080
51922
|
return {
|
|
51081
51923
|
type: "trailing_take.commit",
|
|
51082
|
-
id: CREATE_KEY_FN$
|
|
51924
|
+
id: CREATE_KEY_FN$2(),
|
|
51083
51925
|
timestamp: data.timestamp,
|
|
51084
51926
|
backtest: data.backtest,
|
|
51085
51927
|
symbol: data.symbol,
|
|
@@ -51111,7 +51953,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51111
51953
|
if (data.action === "activate-scheduled") {
|
|
51112
51954
|
return {
|
|
51113
51955
|
type: "activate_scheduled.commit",
|
|
51114
|
-
id: CREATE_KEY_FN$
|
|
51956
|
+
id: CREATE_KEY_FN$2(),
|
|
51115
51957
|
timestamp: data.timestamp,
|
|
51116
51958
|
backtest: data.backtest,
|
|
51117
51959
|
symbol: data.symbol,
|
|
@@ -51143,7 +51985,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51143
51985
|
if (data.action === "average-buy") {
|
|
51144
51986
|
return {
|
|
51145
51987
|
type: "average_buy.commit",
|
|
51146
|
-
id: CREATE_KEY_FN$
|
|
51988
|
+
id: CREATE_KEY_FN$2(),
|
|
51147
51989
|
timestamp: data.timestamp,
|
|
51148
51990
|
backtest: data.backtest,
|
|
51149
51991
|
symbol: data.symbol,
|
|
@@ -51176,7 +52018,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51176
52018
|
if (data.action === "cancel-scheduled") {
|
|
51177
52019
|
return {
|
|
51178
52020
|
type: "cancel_scheduled.commit",
|
|
51179
|
-
id: CREATE_KEY_FN$
|
|
52021
|
+
id: CREATE_KEY_FN$2(),
|
|
51180
52022
|
timestamp: data.timestamp,
|
|
51181
52023
|
backtest: data.backtest,
|
|
51182
52024
|
symbol: data.symbol,
|
|
@@ -51199,7 +52041,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51199
52041
|
if (data.action === "close-pending") {
|
|
51200
52042
|
return {
|
|
51201
52043
|
type: "close_pending.commit",
|
|
51202
|
-
id: CREATE_KEY_FN$
|
|
52044
|
+
id: CREATE_KEY_FN$2(),
|
|
51203
52045
|
timestamp: data.timestamp,
|
|
51204
52046
|
backtest: data.backtest,
|
|
51205
52047
|
symbol: data.symbol,
|
|
@@ -51231,7 +52073,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51231
52073
|
if (data.action === "signal-open") {
|
|
51232
52074
|
return {
|
|
51233
52075
|
type: "signal_sync.open",
|
|
51234
|
-
id: CREATE_KEY_FN$
|
|
52076
|
+
id: CREATE_KEY_FN$2(),
|
|
51235
52077
|
timestamp: data.timestamp,
|
|
51236
52078
|
backtest: data.backtest,
|
|
51237
52079
|
symbol: data.symbol,
|
|
@@ -51263,7 +52105,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51263
52105
|
if (data.action === "signal-close") {
|
|
51264
52106
|
return {
|
|
51265
52107
|
type: "signal_sync.close",
|
|
51266
|
-
id: CREATE_KEY_FN$
|
|
52108
|
+
id: CREATE_KEY_FN$2(),
|
|
51267
52109
|
timestamp: data.timestamp,
|
|
51268
52110
|
backtest: data.backtest,
|
|
51269
52111
|
symbol: data.symbol,
|
|
@@ -51301,7 +52143,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51301
52143
|
*/
|
|
51302
52144
|
const CREATE_RISK_NOTIFICATION_FN = (data) => ({
|
|
51303
52145
|
type: "risk.rejection",
|
|
51304
|
-
id: CREATE_KEY_FN$
|
|
52146
|
+
id: CREATE_KEY_FN$2(),
|
|
51305
52147
|
timestamp: data.timestamp,
|
|
51306
52148
|
backtest: data.backtest,
|
|
51307
52149
|
symbol: data.symbol,
|
|
@@ -51327,7 +52169,7 @@ const CREATE_RISK_NOTIFICATION_FN = (data) => ({
|
|
|
51327
52169
|
*/
|
|
51328
52170
|
const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51329
52171
|
type: "error.info",
|
|
51330
|
-
id: CREATE_KEY_FN$
|
|
52172
|
+
id: CREATE_KEY_FN$2(),
|
|
51331
52173
|
error: errorData(error),
|
|
51332
52174
|
message: getErrorMessage(error),
|
|
51333
52175
|
backtest: false,
|
|
@@ -51339,7 +52181,7 @@ const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
|
|
|
51339
52181
|
*/
|
|
51340
52182
|
const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51341
52183
|
type: "error.critical",
|
|
51342
|
-
id: CREATE_KEY_FN$
|
|
52184
|
+
id: CREATE_KEY_FN$2(),
|
|
51343
52185
|
error: errorData(error),
|
|
51344
52186
|
message: getErrorMessage(error),
|
|
51345
52187
|
backtest: false,
|
|
@@ -51351,7 +52193,7 @@ const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
|
|
|
51351
52193
|
*/
|
|
51352
52194
|
const CREATE_VALIDATION_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51353
52195
|
type: "error.validation",
|
|
51354
|
-
id: CREATE_KEY_FN$
|
|
52196
|
+
id: CREATE_KEY_FN$2(),
|
|
51355
52197
|
error: errorData(error),
|
|
51356
52198
|
message: getErrorMessage(error),
|
|
51357
52199
|
backtest: false,
|
|
@@ -52780,7 +53622,7 @@ const NotificationLive = new NotificationLiveAdapter();
|
|
|
52780
53622
|
*/
|
|
52781
53623
|
const NotificationBacktest = new NotificationBacktestAdapter();
|
|
52782
53624
|
|
|
52783
|
-
const CACHE_METHOD_NAME_RUN = "
|
|
53625
|
+
const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
|
|
52784
53626
|
const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
|
|
52785
53627
|
const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
|
|
52786
53628
|
const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
|
|
@@ -52789,8 +53631,8 @@ const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
|
|
|
52789
53631
|
const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
|
|
52790
53632
|
const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
|
|
52791
53633
|
const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
|
|
52792
|
-
const MS_PER_MINUTE = 60000;
|
|
52793
|
-
const INTERVAL_MINUTES = {
|
|
53634
|
+
const MS_PER_MINUTE$1 = 60000;
|
|
53635
|
+
const INTERVAL_MINUTES$1 = {
|
|
52794
53636
|
"1m": 1,
|
|
52795
53637
|
"3m": 3,
|
|
52796
53638
|
"5m": 5,
|
|
@@ -52824,12 +53666,12 @@ const INTERVAL_MINUTES = {
|
|
|
52824
53666
|
* // Returns timestamp for 2025-10-01T01:00:00Z
|
|
52825
53667
|
* ```
|
|
52826
53668
|
*/
|
|
52827
|
-
const align = (timestamp, interval) => {
|
|
52828
|
-
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
53669
|
+
const align$1 = (timestamp, interval) => {
|
|
53670
|
+
const intervalMinutes = INTERVAL_MINUTES$1[interval];
|
|
52829
53671
|
if (!intervalMinutes) {
|
|
52830
53672
|
throw new Error(`align: unknown interval=${interval}`);
|
|
52831
53673
|
}
|
|
52832
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
53674
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
|
|
52833
53675
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
52834
53676
|
};
|
|
52835
53677
|
/**
|
|
@@ -52840,7 +53682,7 @@ const align = (timestamp, interval) => {
|
|
|
52840
53682
|
* @param backtest - Whether running in backtest mode
|
|
52841
53683
|
* @returns Cache key string
|
|
52842
53684
|
*/
|
|
52843
|
-
const CREATE_KEY_FN = (strategyName, exchangeName, frameName, backtest) => {
|
|
53685
|
+
const CREATE_KEY_FN$1 = (strategyName, exchangeName, frameName, backtest) => {
|
|
52844
53686
|
const parts = [strategyName, exchangeName];
|
|
52845
53687
|
if (frameName)
|
|
52846
53688
|
parts.push(frameName);
|
|
@@ -52864,16 +53706,16 @@ const NEVER_VALUE = Symbol("never");
|
|
|
52864
53706
|
*
|
|
52865
53707
|
* @example
|
|
52866
53708
|
* ```typescript
|
|
52867
|
-
* const instance = new
|
|
53709
|
+
* const instance = new CacheFnInstance(myExpensiveFunction, "1h");
|
|
52868
53710
|
* const result = instance.run(arg1, arg2); // Computed
|
|
52869
53711
|
* const result2 = instance.run(arg1, arg2); // Cached (within same hour)
|
|
52870
53712
|
* // After 1 hour passes
|
|
52871
53713
|
* const result3 = instance.run(arg1, arg2); // Recomputed
|
|
52872
53714
|
* ```
|
|
52873
53715
|
*/
|
|
52874
|
-
class
|
|
53716
|
+
class CacheFnInstance {
|
|
52875
53717
|
/**
|
|
52876
|
-
* Creates a new
|
|
53718
|
+
* Creates a new CacheFnInstance for a specific function and interval.
|
|
52877
53719
|
*
|
|
52878
53720
|
* @param fn - Function to cache
|
|
52879
53721
|
* @param interval - Candle interval for cache invalidation (e.g., "1m", "1h")
|
|
@@ -52910,7 +53752,7 @@ class CacheInstance {
|
|
|
52910
53752
|
*
|
|
52911
53753
|
* @example
|
|
52912
53754
|
* ```typescript
|
|
52913
|
-
* const instance = new
|
|
53755
|
+
* const instance = new CacheFnInstance(calculateIndicator, "15m");
|
|
52914
53756
|
* const result = instance.run("BTCUSDT", 100);
|
|
52915
53757
|
* console.log(result.value); // Calculated value
|
|
52916
53758
|
* console.log(result.when); // Cache timestamp
|
|
@@ -52918,26 +53760,26 @@ class CacheInstance {
|
|
|
52918
53760
|
*/
|
|
52919
53761
|
this.run = (...args) => {
|
|
52920
53762
|
backtest.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
|
|
52921
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
53763
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
52922
53764
|
{
|
|
52923
53765
|
if (!MethodContextService.hasContext()) {
|
|
52924
|
-
throw new Error("
|
|
53766
|
+
throw new Error("CacheFnInstance run requires method context");
|
|
52925
53767
|
}
|
|
52926
53768
|
if (!ExecutionContextService.hasContext()) {
|
|
52927
|
-
throw new Error("
|
|
53769
|
+
throw new Error("CacheFnInstance run requires execution context");
|
|
52928
53770
|
}
|
|
52929
53771
|
if (!step) {
|
|
52930
|
-
throw new Error(`
|
|
53772
|
+
throw new Error(`CacheFnInstance unknown cache ttl interval=${this.interval}`);
|
|
52931
53773
|
}
|
|
52932
53774
|
}
|
|
52933
|
-
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53775
|
+
const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
52934
53776
|
const argKey = String(this.key(args));
|
|
52935
53777
|
const key = `${contextKey}:${argKey}`;
|
|
52936
53778
|
const currentWhen = backtest.executionContextService.context.when;
|
|
52937
53779
|
const cached = this._cacheMap.get(key);
|
|
52938
53780
|
if (cached) {
|
|
52939
|
-
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
52940
|
-
const cachedAligned = align(cached.when.getTime(), this.interval);
|
|
53781
|
+
const currentAligned = align$1(currentWhen.getTime(), this.interval);
|
|
53782
|
+
const cachedAligned = align$1(cached.when.getTime(), this.interval);
|
|
52941
53783
|
if (currentAligned === cachedAligned) {
|
|
52942
53784
|
return cached;
|
|
52943
53785
|
}
|
|
@@ -52960,7 +53802,7 @@ class CacheInstance {
|
|
|
52960
53802
|
*
|
|
52961
53803
|
* @example
|
|
52962
53804
|
* ```typescript
|
|
52963
|
-
* const instance = new
|
|
53805
|
+
* const instance = new CacheFnInstance(calculateIndicator, "1h");
|
|
52964
53806
|
* const result1 = instance.run("BTCUSDT", 14); // Computed
|
|
52965
53807
|
* const result2 = instance.run("BTCUSDT", 14); // Cached
|
|
52966
53808
|
*
|
|
@@ -52970,7 +53812,7 @@ class CacheInstance {
|
|
|
52970
53812
|
* ```
|
|
52971
53813
|
*/
|
|
52972
53814
|
this.clear = () => {
|
|
52973
|
-
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53815
|
+
const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
52974
53816
|
const prefix = `${contextKey}:`;
|
|
52975
53817
|
for (const key of this._cacheMap.keys()) {
|
|
52976
53818
|
if (key.startsWith(prefix)) {
|
|
@@ -52990,7 +53832,7 @@ class CacheInstance {
|
|
|
52990
53832
|
*
|
|
52991
53833
|
* @example
|
|
52992
53834
|
* ```typescript
|
|
52993
|
-
* const instance = new
|
|
53835
|
+
* const instance = new CacheFnInstance(calculateIndicator, "1h");
|
|
52994
53836
|
* instance.run("BTCUSDT", 14); // Cached at 10:00
|
|
52995
53837
|
* instance.run("ETHUSDT", 14); // Cached at 10:00
|
|
52996
53838
|
* // Time passes to 11:00
|
|
@@ -52999,10 +53841,10 @@ class CacheInstance {
|
|
|
52999
53841
|
*/
|
|
53000
53842
|
this.gc = () => {
|
|
53001
53843
|
const currentWhen = backtest.executionContextService.context.when;
|
|
53002
|
-
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
53844
|
+
const currentAligned = align$1(currentWhen.getTime(), this.interval);
|
|
53003
53845
|
let removed = 0;
|
|
53004
53846
|
for (const [key, cached] of this._cacheMap.entries()) {
|
|
53005
|
-
const cachedAligned = align(cached.when.getTime(), this.interval);
|
|
53847
|
+
const cachedAligned = align$1(cached.when.getTime(), this.interval);
|
|
53006
53848
|
if (currentAligned !== cachedAligned) {
|
|
53007
53849
|
this._cacheMap.delete(key);
|
|
53008
53850
|
removed++;
|
|
@@ -53077,7 +53919,7 @@ class CacheFileInstance {
|
|
|
53077
53919
|
*/
|
|
53078
53920
|
this.run = async (...args) => {
|
|
53079
53921
|
backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
53080
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
53922
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
53081
53923
|
{
|
|
53082
53924
|
if (!MethodContextService.hasContext()) {
|
|
53083
53925
|
throw new Error("CacheFileInstance run requires method context");
|
|
@@ -53091,7 +53933,7 @@ class CacheFileInstance {
|
|
|
53091
53933
|
}
|
|
53092
53934
|
const [symbol, ...rest] = args;
|
|
53093
53935
|
const { when } = backtest.executionContextService.context;
|
|
53094
|
-
const alignedTs = align(when.getTime(), this.interval);
|
|
53936
|
+
const alignedTs = align$1(when.getTime(), this.interval);
|
|
53095
53937
|
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53096
53938
|
const entityKey = this.key([symbol, alignedTs, ...rest]);
|
|
53097
53939
|
const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
|
|
@@ -53099,9 +53941,19 @@ class CacheFileInstance {
|
|
|
53099
53941
|
return cached.data;
|
|
53100
53942
|
}
|
|
53101
53943
|
const result = await this.fn.call(null, ...args);
|
|
53102
|
-
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result }, bucket, entityKey);
|
|
53944
|
+
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
53103
53945
|
return result;
|
|
53104
53946
|
};
|
|
53947
|
+
/**
|
|
53948
|
+
* Soft-delete all persisted records for this instance's bucket.
|
|
53949
|
+
* After this call the next `run()` will recompute and re-cache the value.
|
|
53950
|
+
*/
|
|
53951
|
+
this.clear = async () => {
|
|
53952
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53953
|
+
for await (const key of PersistMeasureAdapter.listMeasureData(bucket)) {
|
|
53954
|
+
await PersistMeasureAdapter.removeMeasureData(bucket, key);
|
|
53955
|
+
}
|
|
53956
|
+
};
|
|
53105
53957
|
this.index = CacheFileInstance.createIndex();
|
|
53106
53958
|
}
|
|
53107
53959
|
}
|
|
@@ -53125,10 +53977,10 @@ CacheFileInstance._indexCounter = 0;
|
|
|
53125
53977
|
class CacheUtils {
|
|
53126
53978
|
constructor() {
|
|
53127
53979
|
/**
|
|
53128
|
-
* Memoized function to get or create
|
|
53980
|
+
* Memoized function to get or create CacheFnInstance for a function.
|
|
53129
53981
|
* Each function gets its own isolated cache instance.
|
|
53130
53982
|
*/
|
|
53131
|
-
this._getFnInstance = memoize(([run]) => run, (run, interval, key) => new
|
|
53983
|
+
this._getFnInstance = memoize(([run]) => run, (run, interval, key) => new CacheFnInstance(run, interval, key));
|
|
53132
53984
|
/**
|
|
53133
53985
|
* Memoized function to get or create CacheFileInstance for an async function.
|
|
53134
53986
|
* Each function gets its own isolated file-cache instance.
|
|
@@ -53237,31 +54089,34 @@ class CacheUtils {
|
|
|
53237
54089
|
*/
|
|
53238
54090
|
this.file = (run, context) => {
|
|
53239
54091
|
backtest.loggerService.info(CACHE_METHOD_NAME_FILE, { context });
|
|
54092
|
+
{
|
|
54093
|
+
this._getFileInstance(run, context.interval, context.name, context.key);
|
|
54094
|
+
}
|
|
53240
54095
|
const wrappedFn = (...args) => {
|
|
53241
54096
|
const instance = this._getFileInstance(run, context.interval, context.name, context.key);
|
|
53242
54097
|
return instance.run(...args);
|
|
53243
54098
|
};
|
|
53244
|
-
wrappedFn.clear = () => {
|
|
54099
|
+
wrappedFn.clear = async () => {
|
|
53245
54100
|
backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
|
|
53246
|
-
this._getFileInstance.
|
|
54101
|
+
await this._getFileInstance.get(run)?.clear();
|
|
53247
54102
|
};
|
|
53248
54103
|
return wrappedFn;
|
|
53249
54104
|
};
|
|
53250
54105
|
/**
|
|
53251
|
-
* Dispose (remove) the memoized
|
|
54106
|
+
* Dispose (remove) the memoized CacheFnInstance for a specific function.
|
|
53252
54107
|
*
|
|
53253
|
-
* Removes the
|
|
54108
|
+
* Removes the CacheFnInstance from the internal memoization cache, discarding all cached
|
|
53254
54109
|
* results across all contexts (all strategy/exchange/mode combinations) for that function.
|
|
53255
|
-
* The next call to the wrapped function will create a fresh
|
|
54110
|
+
* The next call to the wrapped function will create a fresh CacheFnInstance.
|
|
53256
54111
|
*
|
|
53257
54112
|
* @template T - Function type
|
|
53258
|
-
* @param run - Function whose
|
|
54113
|
+
* @param run - Function whose CacheFnInstance should be disposed.
|
|
53259
54114
|
*
|
|
53260
54115
|
* @example
|
|
53261
54116
|
* ```typescript
|
|
53262
54117
|
* const cachedFn = Cache.fn(calculateIndicator, { interval: "1h" });
|
|
53263
54118
|
*
|
|
53264
|
-
* // Dispose
|
|
54119
|
+
* // Dispose CacheFnInstance for a specific function
|
|
53265
54120
|
* Cache.dispose(calculateIndicator);
|
|
53266
54121
|
* ```
|
|
53267
54122
|
*/
|
|
@@ -53275,7 +54130,7 @@ class CacheUtils {
|
|
|
53275
54130
|
}
|
|
53276
54131
|
};
|
|
53277
54132
|
/**
|
|
53278
|
-
* Clears all memoized
|
|
54133
|
+
* Clears all memoized CacheFnInstance and CacheFileInstance objects.
|
|
53279
54134
|
* Call this when process.cwd() changes between strategy iterations
|
|
53280
54135
|
* so new instances are created with the updated base path.
|
|
53281
54136
|
*/
|
|
@@ -53305,6 +54160,425 @@ class CacheUtils {
|
|
|
53305
54160
|
*/
|
|
53306
54161
|
const Cache = new CacheUtils();
|
|
53307
54162
|
|
|
54163
|
+
const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
|
|
54164
|
+
const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
|
|
54165
|
+
const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
|
|
54166
|
+
const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
|
|
54167
|
+
const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
|
|
54168
|
+
const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
|
|
54169
|
+
const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
|
|
54170
|
+
const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
|
|
54171
|
+
const MS_PER_MINUTE = 60000;
|
|
54172
|
+
const INTERVAL_MINUTES = {
|
|
54173
|
+
"1m": 1,
|
|
54174
|
+
"3m": 3,
|
|
54175
|
+
"5m": 5,
|
|
54176
|
+
"15m": 15,
|
|
54177
|
+
"30m": 30,
|
|
54178
|
+
"1h": 60,
|
|
54179
|
+
"2h": 120,
|
|
54180
|
+
"4h": 240,
|
|
54181
|
+
"6h": 360,
|
|
54182
|
+
"8h": 480,
|
|
54183
|
+
"1d": 1440,
|
|
54184
|
+
"1w": 10080,
|
|
54185
|
+
};
|
|
54186
|
+
/**
|
|
54187
|
+
* Aligns timestamp down to the nearest interval boundary.
|
|
54188
|
+
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
54189
|
+
*
|
|
54190
|
+
* @param timestamp - Timestamp in milliseconds
|
|
54191
|
+
* @param interval - Candle interval
|
|
54192
|
+
* @returns Aligned timestamp rounded down to interval boundary
|
|
54193
|
+
* @throws Error if interval is unknown
|
|
54194
|
+
*
|
|
54195
|
+
* @example
|
|
54196
|
+
* ```typescript
|
|
54197
|
+
* // Align to 15-minute boundary
|
|
54198
|
+
* const aligned = align(new Date("2025-10-01T00:35:00Z").getTime(), "15m");
|
|
54199
|
+
* // Returns timestamp for 2025-10-01T00:30:00Z
|
|
54200
|
+
* ```
|
|
54201
|
+
*/
|
|
54202
|
+
const align = (timestamp, interval) => {
|
|
54203
|
+
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
54204
|
+
if (!intervalMinutes) {
|
|
54205
|
+
throw new Error(`align: unknown interval=${interval}`);
|
|
54206
|
+
}
|
|
54207
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
54208
|
+
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
54209
|
+
};
|
|
54210
|
+
/**
|
|
54211
|
+
* Build a context key string from strategy name, exchange name, frame name, and execution mode.
|
|
54212
|
+
*
|
|
54213
|
+
* @param strategyName - Name of the strategy
|
|
54214
|
+
* @param exchangeName - Name of the exchange
|
|
54215
|
+
* @param frameName - Name of the backtest frame (omitted in live mode)
|
|
54216
|
+
* @param isBacktest - Whether running in backtest mode
|
|
54217
|
+
* @returns Context key string
|
|
54218
|
+
*/
|
|
54219
|
+
const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
|
|
54220
|
+
const parts = [strategyName, exchangeName];
|
|
54221
|
+
if (frameName)
|
|
54222
|
+
parts.push(frameName);
|
|
54223
|
+
parts.push(isBacktest ? "backtest" : "live");
|
|
54224
|
+
return parts.join(":");
|
|
54225
|
+
};
|
|
54226
|
+
/**
|
|
54227
|
+
* Instance class for firing a function exactly once per interval boundary.
|
|
54228
|
+
*
|
|
54229
|
+
* On the first call within a new interval the wrapped function is invoked and
|
|
54230
|
+
* its result is returned. Every subsequent call within the same interval returns
|
|
54231
|
+
* `null` without invoking the function again.
|
|
54232
|
+
* If the function itself returns `null`, the interval countdown does not start —
|
|
54233
|
+
* the next call will retry the function.
|
|
54234
|
+
*
|
|
54235
|
+
* State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
|
|
54236
|
+
*
|
|
54237
|
+
* @example
|
|
54238
|
+
* ```typescript
|
|
54239
|
+
* const instance = new IntervalFnInstance(mySignalFn, "1h");
|
|
54240
|
+
* await instance.run("BTCUSDT"); // → T | null (fn called)
|
|
54241
|
+
* await instance.run("BTCUSDT"); // → null (skipped, same interval)
|
|
54242
|
+
* // After 1 hour passes:
|
|
54243
|
+
* await instance.run("BTCUSDT"); // → T | null (fn called again)
|
|
54244
|
+
* ```
|
|
54245
|
+
*/
|
|
54246
|
+
class IntervalFnInstance {
|
|
54247
|
+
/**
|
|
54248
|
+
* Creates a new IntervalFnInstance.
|
|
54249
|
+
*
|
|
54250
|
+
* @param fn - Function to fire once per interval
|
|
54251
|
+
* @param interval - Candle interval that controls the firing boundary
|
|
54252
|
+
*/
|
|
54253
|
+
constructor(fn, interval) {
|
|
54254
|
+
this.fn = fn;
|
|
54255
|
+
this.interval = interval;
|
|
54256
|
+
/** Stores the last aligned timestamp per context+symbol key. */
|
|
54257
|
+
this._stateMap = new Map();
|
|
54258
|
+
/**
|
|
54259
|
+
* Execute the signal function with once-per-interval enforcement.
|
|
54260
|
+
*
|
|
54261
|
+
* Algorithm:
|
|
54262
|
+
* 1. Align the current execution context `when` to the interval boundary.
|
|
54263
|
+
* 2. If the stored aligned timestamp for this context+symbol equals the current one → return `null`.
|
|
54264
|
+
* 3. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
|
|
54265
|
+
* the signal. If it returns `null`, leave state unchanged so the next call retries.
|
|
54266
|
+
*
|
|
54267
|
+
* Requires active method context and execution context.
|
|
54268
|
+
*
|
|
54269
|
+
* @param symbol - Trading pair symbol (e.g. "BTCUSDT")
|
|
54270
|
+
* @returns The value returned by `fn` on the first non-null fire, `null` on all subsequent calls
|
|
54271
|
+
* within the same interval or when `fn` itself returned `null`
|
|
54272
|
+
* @throws Error if method context, execution context, or interval is missing
|
|
54273
|
+
*/
|
|
54274
|
+
this.run = async (symbol) => {
|
|
54275
|
+
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { symbol });
|
|
54276
|
+
const step = INTERVAL_MINUTES[this.interval];
|
|
54277
|
+
{
|
|
54278
|
+
if (!MethodContextService.hasContext()) {
|
|
54279
|
+
throw new Error("IntervalFnInstance run requires method context");
|
|
54280
|
+
}
|
|
54281
|
+
if (!ExecutionContextService.hasContext()) {
|
|
54282
|
+
throw new Error("IntervalFnInstance run requires execution context");
|
|
54283
|
+
}
|
|
54284
|
+
if (!step) {
|
|
54285
|
+
throw new Error(`IntervalFnInstance unknown interval=${this.interval}`);
|
|
54286
|
+
}
|
|
54287
|
+
}
|
|
54288
|
+
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
54289
|
+
const key = `${contextKey}:${symbol}`;
|
|
54290
|
+
const currentWhen = backtest.executionContextService.context.when;
|
|
54291
|
+
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
54292
|
+
if (this._stateMap.get(key) === currentAligned) {
|
|
54293
|
+
return null;
|
|
54294
|
+
}
|
|
54295
|
+
const result = await this.fn(symbol, currentWhen);
|
|
54296
|
+
if (result !== null) {
|
|
54297
|
+
this._stateMap.set(key, currentAligned);
|
|
54298
|
+
}
|
|
54299
|
+
return result;
|
|
54300
|
+
};
|
|
54301
|
+
/**
|
|
54302
|
+
* Clear fired-interval state for the current execution context.
|
|
54303
|
+
*
|
|
54304
|
+
* Removes all entries for the current strategy/exchange/frame/mode combination
|
|
54305
|
+
* from this instance's state map. The next `run()` call will invoke the function again.
|
|
54306
|
+
*
|
|
54307
|
+
* Requires active method context and execution context.
|
|
54308
|
+
*/
|
|
54309
|
+
this.clear = () => {
|
|
54310
|
+
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
54311
|
+
const prefix = `${contextKey}:`;
|
|
54312
|
+
for (const key of this._stateMap.keys()) {
|
|
54313
|
+
if (key.startsWith(prefix)) {
|
|
54314
|
+
this._stateMap.delete(key);
|
|
54315
|
+
}
|
|
54316
|
+
}
|
|
54317
|
+
};
|
|
54318
|
+
}
|
|
54319
|
+
}
|
|
54320
|
+
/**
|
|
54321
|
+
* Instance class for firing an async function exactly once per interval boundary,
|
|
54322
|
+
* with the fired state persisted to disk via `PersistIntervalAdapter`.
|
|
54323
|
+
*
|
|
54324
|
+
* On the first call within a new interval the wrapped function is invoked.
|
|
54325
|
+
* If it returns a non-null signal, that result is written to disk and returned.
|
|
54326
|
+
* Every subsequent call within the same interval returns `null` (record exists on disk).
|
|
54327
|
+
* If the function returns `null`, nothing is written and the next call retries.
|
|
54328
|
+
*
|
|
54329
|
+
* Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
|
|
54330
|
+
*
|
|
54331
|
+
* @template T - Async function type: `(symbol: string, ...args) => Promise<R | null>`
|
|
54332
|
+
*
|
|
54333
|
+
* @example
|
|
54334
|
+
* ```typescript
|
|
54335
|
+
* const instance = new IntervalFileInstance(fetchSignal, "1h", "mySignal");
|
|
54336
|
+
* await instance.run("BTCUSDT"); // → R | null (fn called, result written to disk)
|
|
54337
|
+
* await instance.run("BTCUSDT"); // → null (record exists, already fired)
|
|
54338
|
+
* ```
|
|
54339
|
+
*/
|
|
54340
|
+
class IntervalFileInstance {
|
|
54341
|
+
/**
|
|
54342
|
+
* Allocates a new unique index. Called once in the constructor to give each
|
|
54343
|
+
* IntervalFileInstance its own namespace in the persistent key space.
|
|
54344
|
+
*/
|
|
54345
|
+
static createIndex() {
|
|
54346
|
+
return IntervalFileInstance._indexCounter++;
|
|
54347
|
+
}
|
|
54348
|
+
/**
|
|
54349
|
+
* Resets the index counter to zero.
|
|
54350
|
+
* Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
|
|
54351
|
+
*/
|
|
54352
|
+
static clearCounter() {
|
|
54353
|
+
IntervalFileInstance._indexCounter = 0;
|
|
54354
|
+
}
|
|
54355
|
+
/**
|
|
54356
|
+
* Creates a new IntervalFileInstance.
|
|
54357
|
+
*
|
|
54358
|
+
* @param fn - Async signal function to fire once per interval
|
|
54359
|
+
* @param interval - Candle interval that controls the firing boundary
|
|
54360
|
+
* @param name - Human-readable bucket name used as the directory prefix
|
|
54361
|
+
*/
|
|
54362
|
+
constructor(fn, interval, name) {
|
|
54363
|
+
this.fn = fn;
|
|
54364
|
+
this.interval = interval;
|
|
54365
|
+
this.name = name;
|
|
54366
|
+
/**
|
|
54367
|
+
* Execute the async function with persistent once-per-interval enforcement.
|
|
54368
|
+
*
|
|
54369
|
+
* Algorithm:
|
|
54370
|
+
* 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
|
|
54371
|
+
* 2. Align execution context `when` to interval boundary → `alignedTs`.
|
|
54372
|
+
* 3. Build entity key = `${symbol}_${alignedTs}`.
|
|
54373
|
+
* 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
|
|
54374
|
+
* 5. On hit — return `null` (interval already fired).
|
|
54375
|
+
* 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
|
|
54376
|
+
*
|
|
54377
|
+
* Requires active method context and execution context.
|
|
54378
|
+
*
|
|
54379
|
+
* @param symbol - Trading pair symbol (e.g. "BTCUSDT")
|
|
54380
|
+
* @returns The value on the first non-null fire, `null` if already fired this interval
|
|
54381
|
+
* or if `fn` itself returned `null`
|
|
54382
|
+
* @throws Error if method context, execution context, or interval is missing
|
|
54383
|
+
*/
|
|
54384
|
+
this.run = async (symbol) => {
|
|
54385
|
+
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { symbol });
|
|
54386
|
+
const step = INTERVAL_MINUTES[this.interval];
|
|
54387
|
+
{
|
|
54388
|
+
if (!MethodContextService.hasContext()) {
|
|
54389
|
+
throw new Error("IntervalFileInstance run requires method context");
|
|
54390
|
+
}
|
|
54391
|
+
if (!ExecutionContextService.hasContext()) {
|
|
54392
|
+
throw new Error("IntervalFileInstance run requires execution context");
|
|
54393
|
+
}
|
|
54394
|
+
if (!step) {
|
|
54395
|
+
throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
|
|
54396
|
+
}
|
|
54397
|
+
}
|
|
54398
|
+
const { when } = backtest.executionContextService.context;
|
|
54399
|
+
const alignedTs = align(when.getTime(), this.interval);
|
|
54400
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
54401
|
+
const entityKey = `${symbol}_${alignedTs}`;
|
|
54402
|
+
const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
|
|
54403
|
+
if (cached !== null) {
|
|
54404
|
+
return null;
|
|
54405
|
+
}
|
|
54406
|
+
const result = await this.fn(symbol, when);
|
|
54407
|
+
if (result !== null) {
|
|
54408
|
+
await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
54409
|
+
}
|
|
54410
|
+
return result;
|
|
54411
|
+
};
|
|
54412
|
+
/**
|
|
54413
|
+
* Soft-delete all persisted records for this instance's bucket.
|
|
54414
|
+
* After this call the function will fire again on the next `run()`.
|
|
54415
|
+
*/
|
|
54416
|
+
this.clear = async () => {
|
|
54417
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
54418
|
+
for await (const key of PersistIntervalAdapter.listIntervalData(bucket)) {
|
|
54419
|
+
await PersistIntervalAdapter.removeIntervalData(bucket, key);
|
|
54420
|
+
}
|
|
54421
|
+
};
|
|
54422
|
+
this.index = IntervalFileInstance.createIndex();
|
|
54423
|
+
}
|
|
54424
|
+
}
|
|
54425
|
+
/** Global counter — incremented once per IntervalFileInstance construction. */
|
|
54426
|
+
IntervalFileInstance._indexCounter = 0;
|
|
54427
|
+
/**
|
|
54428
|
+
* Utility class for wrapping signal functions with once-per-interval firing.
|
|
54429
|
+
* Provides two modes: in-memory (`fn`) and persistent file-based (`file`).
|
|
54430
|
+
* Exported as singleton instance `Interval` for convenient usage.
|
|
54431
|
+
*
|
|
54432
|
+
* @example
|
|
54433
|
+
* ```typescript
|
|
54434
|
+
* import { Interval } from "./classes/Interval";
|
|
54435
|
+
*
|
|
54436
|
+
* const fireOncePerHour = Interval.fn(mySignalFn, { interval: "1h" });
|
|
54437
|
+
* await fireOncePerHour("BTCUSDT"); // fn called — returns its result
|
|
54438
|
+
* await fireOncePerHour("BTCUSDT"); // returns null (same interval)
|
|
54439
|
+
* ```
|
|
54440
|
+
*/
|
|
54441
|
+
class IntervalUtils {
|
|
54442
|
+
constructor() {
|
|
54443
|
+
/**
|
|
54444
|
+
* Memoized factory to get or create an `IntervalFnInstance` for a function.
|
|
54445
|
+
* Each function reference gets its own isolated instance.
|
|
54446
|
+
*/
|
|
54447
|
+
this._getInstance = memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
|
|
54448
|
+
/**
|
|
54449
|
+
* Memoized factory to get or create an `IntervalFileInstance` for an async function.
|
|
54450
|
+
* Each function reference gets its own isolated persistent instance.
|
|
54451
|
+
*/
|
|
54452
|
+
this._getFileInstance = memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
|
|
54453
|
+
/**
|
|
54454
|
+
* Wrap a signal function with in-memory once-per-interval firing.
|
|
54455
|
+
*
|
|
54456
|
+
* Returns a wrapped version of the function that fires at most once per interval boundary.
|
|
54457
|
+
* If the function returns `null`, the countdown does not start and the next call retries.
|
|
54458
|
+
*
|
|
54459
|
+
* The `run` function reference is used as the memoization key for the underlying
|
|
54460
|
+
* `IntervalFnInstance`, so each unique function reference gets its own isolated instance.
|
|
54461
|
+
*
|
|
54462
|
+
* @param run - Signal function to wrap
|
|
54463
|
+
* @param context.interval - Candle interval that controls the firing boundary
|
|
54464
|
+
* @returns Wrapped function with the same signature as `TIntervalFn<T>`, plus a `clear()` method
|
|
54465
|
+
*
|
|
54466
|
+
* @example
|
|
54467
|
+
* ```typescript
|
|
54468
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
|
|
54469
|
+
*
|
|
54470
|
+
* await fireOnce("BTCUSDT"); // → T or null (fn called)
|
|
54471
|
+
* await fireOnce("BTCUSDT"); // → null (same interval, skipped)
|
|
54472
|
+
* ```
|
|
54473
|
+
*/
|
|
54474
|
+
this.fn = (run, context) => {
|
|
54475
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
|
|
54476
|
+
const wrappedFn = (symbol) => {
|
|
54477
|
+
const instance = this._getInstance(run, context.interval);
|
|
54478
|
+
return instance.run(symbol);
|
|
54479
|
+
};
|
|
54480
|
+
wrappedFn.clear = () => {
|
|
54481
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
|
|
54482
|
+
if (!MethodContextService.hasContext()) {
|
|
54483
|
+
backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
|
|
54484
|
+
return;
|
|
54485
|
+
}
|
|
54486
|
+
if (!ExecutionContextService.hasContext()) {
|
|
54487
|
+
backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
|
|
54488
|
+
return;
|
|
54489
|
+
}
|
|
54490
|
+
this._getInstance.get(run)?.clear();
|
|
54491
|
+
};
|
|
54492
|
+
return wrappedFn;
|
|
54493
|
+
};
|
|
54494
|
+
/**
|
|
54495
|
+
* Wrap an async signal function with persistent file-based once-per-interval firing.
|
|
54496
|
+
*
|
|
54497
|
+
* Returns a wrapped version of the function that reads from disk on hit (returns `null`)
|
|
54498
|
+
* and writes the fired signal to disk on the first successful fire.
|
|
54499
|
+
* Fired state survives process restarts.
|
|
54500
|
+
*
|
|
54501
|
+
* The `run` function reference is used as the memoization key for the underlying
|
|
54502
|
+
* `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
|
|
54503
|
+
*
|
|
54504
|
+
* @template T - Async function type to wrap
|
|
54505
|
+
* @param run - Async signal function to wrap with persistent once-per-interval firing
|
|
54506
|
+
* @param context.interval - Candle interval that controls the firing boundary
|
|
54507
|
+
* @param context.name - Human-readable bucket name; becomes the directory prefix
|
|
54508
|
+
* @returns Wrapped function with the same signature as `T`, plus an async `clear()` method
|
|
54509
|
+
* that deletes persisted records from disk and disposes the memoized instance
|
|
54510
|
+
*
|
|
54511
|
+
* @example
|
|
54512
|
+
* ```typescript
|
|
54513
|
+
* const fetchSignal = async (symbol: string, when: Date) => { ... };
|
|
54514
|
+
* const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
|
|
54515
|
+
* await fireOnce.clear(); // delete disk records so the function fires again next call
|
|
54516
|
+
* ```
|
|
54517
|
+
*/
|
|
54518
|
+
this.file = (run, context) => {
|
|
54519
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
|
|
54520
|
+
{
|
|
54521
|
+
this._getFileInstance(run, context.interval, context.name);
|
|
54522
|
+
}
|
|
54523
|
+
const wrappedFn = (symbol) => {
|
|
54524
|
+
const instance = this._getFileInstance(run, context.interval, context.name);
|
|
54525
|
+
return instance.run(symbol);
|
|
54526
|
+
};
|
|
54527
|
+
wrappedFn.clear = async () => {
|
|
54528
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
|
|
54529
|
+
await this._getFileInstance.get(run)?.clear();
|
|
54530
|
+
};
|
|
54531
|
+
return wrappedFn;
|
|
54532
|
+
};
|
|
54533
|
+
/**
|
|
54534
|
+
* Dispose (remove) the memoized `IntervalFnInstance` for a specific function.
|
|
54535
|
+
*
|
|
54536
|
+
* Removes the instance from the internal memoization cache, discarding all in-memory
|
|
54537
|
+
* fired-interval state across all contexts for that function.
|
|
54538
|
+
* The next call to the wrapped function will create a fresh `IntervalFnInstance`.
|
|
54539
|
+
*
|
|
54540
|
+
* @param run - Function whose `IntervalFnInstance` should be disposed
|
|
54541
|
+
*
|
|
54542
|
+
* @example
|
|
54543
|
+
* ```typescript
|
|
54544
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
|
|
54545
|
+
* Interval.dispose(mySignalFn);
|
|
54546
|
+
* ```
|
|
54547
|
+
*/
|
|
54548
|
+
this.dispose = (run) => {
|
|
54549
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
|
|
54550
|
+
this._getInstance.clear(run);
|
|
54551
|
+
};
|
|
54552
|
+
/**
|
|
54553
|
+
* Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects and
|
|
54554
|
+
* resets the `IntervalFileInstance` index counter.
|
|
54555
|
+
* Call this when `process.cwd()` changes between strategy iterations
|
|
54556
|
+
* so new instances are created with the updated base path.
|
|
54557
|
+
*/
|
|
54558
|
+
this.clear = () => {
|
|
54559
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
|
|
54560
|
+
this._getInstance.clear();
|
|
54561
|
+
this._getFileInstance.clear();
|
|
54562
|
+
IntervalFileInstance.clearCounter();
|
|
54563
|
+
};
|
|
54564
|
+
}
|
|
54565
|
+
}
|
|
54566
|
+
/**
|
|
54567
|
+
* Singleton instance of `IntervalUtils` for convenient once-per-interval signal firing.
|
|
54568
|
+
*
|
|
54569
|
+
* @example
|
|
54570
|
+
* ```typescript
|
|
54571
|
+
* import { Interval } from "./classes/Interval";
|
|
54572
|
+
*
|
|
54573
|
+
* // In-memory: fires once per hour, resets on process restart
|
|
54574
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
|
|
54575
|
+
*
|
|
54576
|
+
* // Persistent: fired state survives restarts
|
|
54577
|
+
* const fireOncePersist = Interval.file(mySignalFn, { interval: "1h", name: "mySignal" });
|
|
54578
|
+
* ```
|
|
54579
|
+
*/
|
|
54580
|
+
const Interval = new IntervalUtils();
|
|
54581
|
+
|
|
53308
54582
|
const BREAKEVEN_METHOD_NAME_GET_DATA = "BreakevenUtils.getData";
|
|
53309
54583
|
const BREAKEVEN_METHOD_NAME_GET_REPORT = "BreakevenUtils.getReport";
|
|
53310
54584
|
const BREAKEVEN_METHOD_NAME_DUMP = "BreakevenUtils.dump";
|
|
@@ -54419,4 +55693,4 @@ const validateSignal = (signal, currentPrice) => {
|
|
|
54419
55693
|
return !errors.length;
|
|
54420
55694
|
};
|
|
54421
55695
|
|
|
54422
|
-
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|
|
55696
|
+
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|