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.cjs
CHANGED
|
@@ -908,7 +908,7 @@ const LOGGER_SERVICE$7 = new LoggerService();
|
|
|
908
908
|
/** Symbol key for the singleshot waitForInit function on PersistBase instances. */
|
|
909
909
|
const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
|
|
910
910
|
// Calculate step in milliseconds for candle close time validation
|
|
911
|
-
const INTERVAL_MINUTES$
|
|
911
|
+
const INTERVAL_MINUTES$9 = {
|
|
912
912
|
"1m": 1,
|
|
913
913
|
"3m": 3,
|
|
914
914
|
"5m": 5,
|
|
@@ -922,7 +922,7 @@ const INTERVAL_MINUTES$8 = {
|
|
|
922
922
|
"1d": 1440,
|
|
923
923
|
"1w": 10080,
|
|
924
924
|
};
|
|
925
|
-
const MS_PER_MINUTE$
|
|
925
|
+
const MS_PER_MINUTE$7 = 60000;
|
|
926
926
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
|
|
927
927
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
|
|
928
928
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistSignalUtils.writeSignalData";
|
|
@@ -981,8 +981,18 @@ const PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA = "PersistMeasureUtils.readMea
|
|
|
981
981
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA = "PersistMeasureUtils.writeMeasureData";
|
|
982
982
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON = "PersistMeasureUtils.useJson";
|
|
983
983
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY = "PersistMeasureUtils.useDummy";
|
|
984
|
+
const PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA = "PersistMeasureUtils.removeMeasureData";
|
|
985
|
+
const PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA = "PersistMeasureUtils.listMeasureData";
|
|
984
986
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_CLEAR = "PersistMeasureUtils.clear";
|
|
985
987
|
const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER = "PersistMeasureUtils.usePersistMeasureAdapter";
|
|
988
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA = "PersistIntervalUtils.readIntervalData";
|
|
989
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistIntervalUtils.writeIntervalData";
|
|
990
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON = "PersistIntervalUtils.useJson";
|
|
991
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY = "PersistIntervalUtils.useDummy";
|
|
992
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA = "PersistIntervalUtils.removeIntervalData";
|
|
993
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA = "PersistIntervalUtils.listIntervalData";
|
|
994
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR = "PersistIntervalUtils.clear";
|
|
995
|
+
const PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER = "PersistIntervalUtils.usePersistIntervalAdapter";
|
|
986
996
|
const PERSIST_CANDLE_UTILS_METHOD_NAME_CLEAR = "PersistCandleUtils.clear";
|
|
987
997
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_USE_PERSIST_MEMORY_ADAPTER = "PersistMemoryUtils.usePersistMemoryAdapter";
|
|
988
998
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA = "PersistMemoryUtils.readMemoryData";
|
|
@@ -1895,7 +1905,7 @@ class PersistCandleUtils {
|
|
|
1895
1905
|
const isInitial = !this.getCandlesStorage.has(key);
|
|
1896
1906
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1897
1907
|
await stateStorage.waitForInit(isInitial);
|
|
1898
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1908
|
+
const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
|
|
1899
1909
|
// Calculate expected timestamps and fetch each candle directly
|
|
1900
1910
|
const cachedCandles = [];
|
|
1901
1911
|
for (let i = 0; i < limit; i++) {
|
|
@@ -1951,7 +1961,7 @@ class PersistCandleUtils {
|
|
|
1951
1961
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1952
1962
|
await stateStorage.waitForInit(isInitial);
|
|
1953
1963
|
// Calculate step in milliseconds to determine candle close time
|
|
1954
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1964
|
+
const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
|
|
1955
1965
|
const now = Date.now();
|
|
1956
1966
|
// Write each candle as a separate file, skipping incomplete candles
|
|
1957
1967
|
for (const candle of candles) {
|
|
@@ -2377,7 +2387,8 @@ class PersistMeasureUtils {
|
|
|
2377
2387
|
const stateStorage = this.getMeasureStorage(bucket);
|
|
2378
2388
|
await stateStorage.waitForInit(isInitial);
|
|
2379
2389
|
if (await stateStorage.hasValue(key)) {
|
|
2380
|
-
|
|
2390
|
+
const data = await stateStorage.readValue(key);
|
|
2391
|
+
return data.removed ? null : data;
|
|
2381
2392
|
}
|
|
2382
2393
|
return null;
|
|
2383
2394
|
};
|
|
@@ -2399,6 +2410,27 @@ class PersistMeasureUtils {
|
|
|
2399
2410
|
await stateStorage.waitForInit(isInitial);
|
|
2400
2411
|
await stateStorage.writeValue(key, data);
|
|
2401
2412
|
};
|
|
2413
|
+
/**
|
|
2414
|
+
* Marks a cached entry as removed (soft delete — file is kept on disk).
|
|
2415
|
+
* After this call `readMeasureData` for the same key returns `null`.
|
|
2416
|
+
*
|
|
2417
|
+
* @param bucket - Storage bucket
|
|
2418
|
+
* @param key - Dynamic cache key within the bucket
|
|
2419
|
+
* @returns Promise that resolves when removal is complete
|
|
2420
|
+
*/
|
|
2421
|
+
this.removeMeasureData = async (bucket, key) => {
|
|
2422
|
+
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, {
|
|
2423
|
+
bucket,
|
|
2424
|
+
key,
|
|
2425
|
+
});
|
|
2426
|
+
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2427
|
+
const stateStorage = this.getMeasureStorage(bucket);
|
|
2428
|
+
await stateStorage.waitForInit(isInitial);
|
|
2429
|
+
const data = await stateStorage.readValue(key);
|
|
2430
|
+
if (data) {
|
|
2431
|
+
await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
2432
|
+
}
|
|
2433
|
+
};
|
|
2402
2434
|
}
|
|
2403
2435
|
/**
|
|
2404
2436
|
* Registers a custom persistence adapter.
|
|
@@ -2409,6 +2441,27 @@ class PersistMeasureUtils {
|
|
|
2409
2441
|
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
|
|
2410
2442
|
this.PersistMeasureFactory = Ctor;
|
|
2411
2443
|
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Async generator yielding all non-removed entity keys for a given bucket.
|
|
2446
|
+
* Used by `CacheFileInstance.clear()` to iterate and soft-delete all entries.
|
|
2447
|
+
*
|
|
2448
|
+
* @param bucket - Storage bucket
|
|
2449
|
+
* @returns AsyncGenerator yielding entity keys
|
|
2450
|
+
*/
|
|
2451
|
+
async *listMeasureData(bucket) {
|
|
2452
|
+
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA, { bucket });
|
|
2453
|
+
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2454
|
+
const stateStorage = this.getMeasureStorage(bucket);
|
|
2455
|
+
await stateStorage.waitForInit(isInitial);
|
|
2456
|
+
for await (const key of stateStorage.keys()) {
|
|
2457
|
+
const data = await stateStorage.readValue(String(key));
|
|
2458
|
+
if (data === null || data.removed) {
|
|
2459
|
+
continue;
|
|
2460
|
+
}
|
|
2461
|
+
yield String(key);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
;
|
|
2412
2465
|
/**
|
|
2413
2466
|
* Clears the memoized storage cache.
|
|
2414
2467
|
* Call this when process.cwd() changes between strategy iterations
|
|
@@ -2438,6 +2491,140 @@ class PersistMeasureUtils {
|
|
|
2438
2491
|
* Used by Cache.file for persistent caching of external API responses.
|
|
2439
2492
|
*/
|
|
2440
2493
|
const PersistMeasureAdapter = new PersistMeasureUtils();
|
|
2494
|
+
/**
|
|
2495
|
+
* Persistence layer for Interval.file once-per-interval signal firing.
|
|
2496
|
+
*
|
|
2497
|
+
* Stores fired-interval markers under `./dump/data/interval/`.
|
|
2498
|
+
* A record's presence means the interval has already fired for that bucket+key;
|
|
2499
|
+
* absence means the function has not yet fired (or returned null last time).
|
|
2500
|
+
*/
|
|
2501
|
+
class PersistIntervalUtils {
|
|
2502
|
+
constructor() {
|
|
2503
|
+
this.PersistIntervalFactory = PersistBase;
|
|
2504
|
+
this.getIntervalStorage = functoolsKit.memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistIntervalFactory, [
|
|
2505
|
+
bucket,
|
|
2506
|
+
`./dump/data/interval/`,
|
|
2507
|
+
]));
|
|
2508
|
+
/**
|
|
2509
|
+
* Reads interval data for a given bucket and key.
|
|
2510
|
+
*
|
|
2511
|
+
* @param bucket - Storage bucket (instance name + interval + index)
|
|
2512
|
+
* @param key - Entity key within the bucket (symbol + aligned timestamp)
|
|
2513
|
+
* @returns Promise resolving to stored value or null if not found
|
|
2514
|
+
*/
|
|
2515
|
+
this.readIntervalData = async (bucket, key) => {
|
|
2516
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, {
|
|
2517
|
+
bucket,
|
|
2518
|
+
key,
|
|
2519
|
+
});
|
|
2520
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2521
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2522
|
+
await stateStorage.waitForInit(isInitial);
|
|
2523
|
+
if (await stateStorage.hasValue(key)) {
|
|
2524
|
+
const data = await stateStorage.readValue(key);
|
|
2525
|
+
return data.removed ? null : data;
|
|
2526
|
+
}
|
|
2527
|
+
return null;
|
|
2528
|
+
};
|
|
2529
|
+
/**
|
|
2530
|
+
* Writes interval data to disk.
|
|
2531
|
+
*
|
|
2532
|
+
* @param data - Data to store
|
|
2533
|
+
* @param bucket - Storage bucket
|
|
2534
|
+
* @param key - Entity key within the bucket
|
|
2535
|
+
* @returns Promise that resolves when write is complete
|
|
2536
|
+
*/
|
|
2537
|
+
this.writeIntervalData = async (data, bucket, key) => {
|
|
2538
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, {
|
|
2539
|
+
bucket,
|
|
2540
|
+
key,
|
|
2541
|
+
});
|
|
2542
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2543
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2544
|
+
await stateStorage.waitForInit(isInitial);
|
|
2545
|
+
await stateStorage.writeValue(key, data);
|
|
2546
|
+
};
|
|
2547
|
+
/**
|
|
2548
|
+
* Marks an interval entry as removed (soft delete — file is kept on disk).
|
|
2549
|
+
* After this call `readIntervalData` for the same key returns `null`,
|
|
2550
|
+
* so the function will fire again on the next `IntervalFileInstance.run` call.
|
|
2551
|
+
*
|
|
2552
|
+
* @param bucket - Storage bucket
|
|
2553
|
+
* @param key - Entity key within the bucket
|
|
2554
|
+
* @returns Promise that resolves when removal is complete
|
|
2555
|
+
*/
|
|
2556
|
+
this.removeIntervalData = async (bucket, key) => {
|
|
2557
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, {
|
|
2558
|
+
bucket,
|
|
2559
|
+
key,
|
|
2560
|
+
});
|
|
2561
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2562
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2563
|
+
await stateStorage.waitForInit(isInitial);
|
|
2564
|
+
const data = await stateStorage.readValue(key);
|
|
2565
|
+
if (data) {
|
|
2566
|
+
await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
2567
|
+
}
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Registers a custom persistence adapter.
|
|
2572
|
+
*
|
|
2573
|
+
* @param Ctor - Custom PersistBase constructor
|
|
2574
|
+
*/
|
|
2575
|
+
usePersistIntervalAdapter(Ctor) {
|
|
2576
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER);
|
|
2577
|
+
this.PersistIntervalFactory = Ctor;
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* Async generator yielding all non-removed entity keys for a given bucket.
|
|
2581
|
+
* Used by `IntervalFileInstance.clear()` to iterate and soft-delete all entries.
|
|
2582
|
+
*
|
|
2583
|
+
* @param bucket - Storage bucket
|
|
2584
|
+
* @returns AsyncGenerator yielding entity keys
|
|
2585
|
+
*/
|
|
2586
|
+
async *listIntervalData(bucket) {
|
|
2587
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA, { bucket });
|
|
2588
|
+
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2589
|
+
const stateStorage = this.getIntervalStorage(bucket);
|
|
2590
|
+
await stateStorage.waitForInit(isInitial);
|
|
2591
|
+
for await (const key of stateStorage.keys()) {
|
|
2592
|
+
const data = await stateStorage.readValue(String(key));
|
|
2593
|
+
if (data === null || data.removed) {
|
|
2594
|
+
continue;
|
|
2595
|
+
}
|
|
2596
|
+
yield String(key);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
;
|
|
2600
|
+
/**
|
|
2601
|
+
* Clears the memoized storage cache.
|
|
2602
|
+
* Call this when process.cwd() changes between strategy iterations.
|
|
2603
|
+
*/
|
|
2604
|
+
clear() {
|
|
2605
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR);
|
|
2606
|
+
this.getIntervalStorage.clear();
|
|
2607
|
+
}
|
|
2608
|
+
/**
|
|
2609
|
+
* Switches to the default JSON persist adapter.
|
|
2610
|
+
*/
|
|
2611
|
+
useJson() {
|
|
2612
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON);
|
|
2613
|
+
this.usePersistIntervalAdapter(PersistBase);
|
|
2614
|
+
}
|
|
2615
|
+
/**
|
|
2616
|
+
* Switches to a dummy persist adapter that discards all writes.
|
|
2617
|
+
*/
|
|
2618
|
+
useDummy() {
|
|
2619
|
+
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2620
|
+
this.usePersistIntervalAdapter(PersistDummy);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* Global singleton instance of PersistIntervalUtils.
|
|
2625
|
+
* Used by Interval.file for persistent once-per-interval signal firing.
|
|
2626
|
+
*/
|
|
2627
|
+
const PersistIntervalAdapter = new PersistIntervalUtils();
|
|
2441
2628
|
/**
|
|
2442
2629
|
* Utility class for managing memory entry persistence.
|
|
2443
2630
|
*
|
|
@@ -2785,8 +2972,8 @@ class CandleUtils {
|
|
|
2785
2972
|
}
|
|
2786
2973
|
const Candle = new CandleUtils();
|
|
2787
2974
|
|
|
2788
|
-
const MS_PER_MINUTE$
|
|
2789
|
-
const INTERVAL_MINUTES$
|
|
2975
|
+
const MS_PER_MINUTE$6 = 60000;
|
|
2976
|
+
const INTERVAL_MINUTES$8 = {
|
|
2790
2977
|
"1m": 1,
|
|
2791
2978
|
"3m": 3,
|
|
2792
2979
|
"5m": 5,
|
|
@@ -2818,7 +3005,7 @@ const INTERVAL_MINUTES$7 = {
|
|
|
2818
3005
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
2819
3006
|
*/
|
|
2820
3007
|
const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
|
|
2821
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
3008
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$6;
|
|
2822
3009
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
2823
3010
|
};
|
|
2824
3011
|
/**
|
|
@@ -2972,9 +3159,9 @@ const WRITE_CANDLES_CACHE_FN$1 = functoolsKit.trycatch(functoolsKit.queued(async
|
|
|
2972
3159
|
* @returns Promise resolving to array of candle data
|
|
2973
3160
|
*/
|
|
2974
3161
|
const GET_CANDLES_FN$1 = async (dto, since, self) => {
|
|
2975
|
-
const step = INTERVAL_MINUTES$
|
|
3162
|
+
const step = INTERVAL_MINUTES$8[dto.interval];
|
|
2976
3163
|
const sinceTimestamp = since.getTime();
|
|
2977
|
-
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$
|
|
3164
|
+
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$6;
|
|
2978
3165
|
await Candle.acquireLock(`ClientExchange GET_CANDLES_FN symbol=${dto.symbol} interval=${dto.interval} limit=${dto.limit}`);
|
|
2979
3166
|
try {
|
|
2980
3167
|
// Try to read from cache first
|
|
@@ -3088,11 +3275,11 @@ class ClientExchange {
|
|
|
3088
3275
|
interval,
|
|
3089
3276
|
limit,
|
|
3090
3277
|
});
|
|
3091
|
-
const step = INTERVAL_MINUTES$
|
|
3278
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3092
3279
|
if (!step) {
|
|
3093
3280
|
throw new Error(`ClientExchange unknown interval=${interval}`);
|
|
3094
3281
|
}
|
|
3095
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3282
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3096
3283
|
// Align when down to interval boundary
|
|
3097
3284
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3098
3285
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
@@ -3169,11 +3356,11 @@ class ClientExchange {
|
|
|
3169
3356
|
if (!this.params.execution.context.backtest) {
|
|
3170
3357
|
throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
|
|
3171
3358
|
}
|
|
3172
|
-
const step = INTERVAL_MINUTES$
|
|
3359
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3173
3360
|
if (!step) {
|
|
3174
3361
|
throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
|
|
3175
3362
|
}
|
|
3176
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3363
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3177
3364
|
const now = Date.now();
|
|
3178
3365
|
// Align when down to interval boundary
|
|
3179
3366
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
@@ -3337,11 +3524,11 @@ class ClientExchange {
|
|
|
3337
3524
|
sDate,
|
|
3338
3525
|
eDate,
|
|
3339
3526
|
});
|
|
3340
|
-
const step = INTERVAL_MINUTES$
|
|
3527
|
+
const step = INTERVAL_MINUTES$8[interval];
|
|
3341
3528
|
if (!step) {
|
|
3342
3529
|
throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
|
|
3343
3530
|
}
|
|
3344
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
3531
|
+
const stepMs = step * MS_PER_MINUTE$6;
|
|
3345
3532
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3346
3533
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
3347
3534
|
let sinceTimestamp;
|
|
@@ -3470,7 +3657,7 @@ class ClientExchange {
|
|
|
3470
3657
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
3471
3658
|
const to = new Date(alignedTo);
|
|
3472
3659
|
const from = new Date(alignedTo -
|
|
3473
|
-
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
3660
|
+
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$6);
|
|
3474
3661
|
return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
|
|
3475
3662
|
}
|
|
3476
3663
|
/**
|
|
@@ -3499,7 +3686,7 @@ class ClientExchange {
|
|
|
3499
3686
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
3500
3687
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
3501
3688
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, 1);
|
|
3502
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
3689
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$6 - MS_PER_MINUTE$6;
|
|
3503
3690
|
// No limit: fetch a single window and return as-is
|
|
3504
3691
|
if (limit === undefined) {
|
|
3505
3692
|
const to = new Date(alignedTo);
|
|
@@ -4478,7 +4665,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
|
|
|
4478
4665
|
}
|
|
4479
4666
|
};
|
|
4480
4667
|
|
|
4481
|
-
const INTERVAL_MINUTES$
|
|
4668
|
+
const INTERVAL_MINUTES$7 = {
|
|
4482
4669
|
"1m": 1,
|
|
4483
4670
|
"3m": 3,
|
|
4484
4671
|
"5m": 5,
|
|
@@ -4862,7 +5049,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
4862
5049
|
}
|
|
4863
5050
|
const currentTime = self.params.execution.context.when.getTime();
|
|
4864
5051
|
{
|
|
4865
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
5052
|
+
const intervalMinutes = INTERVAL_MINUTES$7[self.params.interval];
|
|
4866
5053
|
const intervalMs = intervalMinutes * 60 * 1000;
|
|
4867
5054
|
const alignedTime = Math.floor(currentTime / intervalMs) * intervalMs;
|
|
4868
5055
|
// Проверяем что наступил новый интервал (по aligned timestamp)
|
|
@@ -7803,6 +7990,90 @@ class ClientStrategy {
|
|
|
7803
7990
|
}
|
|
7804
7991
|
return this._pendingSignal._fall.pnlCost;
|
|
7805
7992
|
}
|
|
7993
|
+
/**
|
|
7994
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
7995
|
+
*
|
|
7996
|
+
* Measures how much PnL% the position has given back from its best point.
|
|
7997
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
7998
|
+
* Zero when called at the exact moment the peak was set, or when current PnL >= peak PnL.
|
|
7999
|
+
*
|
|
8000
|
+
* Returns null if no pending signal exists.
|
|
8001
|
+
*
|
|
8002
|
+
* @param symbol - Trading pair symbol
|
|
8003
|
+
* @param currentPrice - Current market price
|
|
8004
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
8005
|
+
*/
|
|
8006
|
+
async getPositionHighestProfitDistancePnlPercentage(symbol, currentPrice) {
|
|
8007
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitDistancePnlPercentage", { symbol, currentPrice });
|
|
8008
|
+
if (!this._pendingSignal) {
|
|
8009
|
+
return null;
|
|
8010
|
+
}
|
|
8011
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8012
|
+
return Math.max(0, this._pendingSignal._peak.pnlPercentage - currentPnl.pnlPercentage);
|
|
8013
|
+
}
|
|
8014
|
+
/**
|
|
8015
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
8016
|
+
*
|
|
8017
|
+
* Measures how much PnL cost the position has given back from its best point.
|
|
8018
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
8019
|
+
* Zero when called at the exact moment the peak was set, or when current PnL >= peak PnL.
|
|
8020
|
+
*
|
|
8021
|
+
* Returns null if no pending signal exists.
|
|
8022
|
+
*
|
|
8023
|
+
* @param symbol - Trading pair symbol
|
|
8024
|
+
* @param currentPrice - Current market price
|
|
8025
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
8026
|
+
*/
|
|
8027
|
+
async getPositionHighestProfitDistancePnlCost(symbol, currentPrice) {
|
|
8028
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitDistancePnlCost", { symbol, currentPrice });
|
|
8029
|
+
if (!this._pendingSignal) {
|
|
8030
|
+
return null;
|
|
8031
|
+
}
|
|
8032
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8033
|
+
return Math.max(0, this._pendingSignal._peak.pnlCost - currentPnl.pnlCost);
|
|
8034
|
+
}
|
|
8035
|
+
/**
|
|
8036
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
8037
|
+
*
|
|
8038
|
+
* Measures how much the position has recovered from its deepest loss point.
|
|
8039
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
8040
|
+
* Zero when called at the exact moment the trough was set, or when current PnL <= trough PnL.
|
|
8041
|
+
*
|
|
8042
|
+
* Returns null if no pending signal exists.
|
|
8043
|
+
*
|
|
8044
|
+
* @param symbol - Trading pair symbol
|
|
8045
|
+
* @param currentPrice - Current market price
|
|
8046
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
8047
|
+
*/
|
|
8048
|
+
async getPositionHighestMaxDrawdownPnlPercentage(symbol, currentPrice) {
|
|
8049
|
+
this.params.logger.debug("ClientStrategy getPositionHighestMaxDrawdownPnlPercentage", { symbol, currentPrice });
|
|
8050
|
+
if (!this._pendingSignal) {
|
|
8051
|
+
return null;
|
|
8052
|
+
}
|
|
8053
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8054
|
+
return Math.max(0, currentPnl.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
|
|
8055
|
+
}
|
|
8056
|
+
/**
|
|
8057
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
8058
|
+
*
|
|
8059
|
+
* Measures how much the position has recovered from its deepest loss point.
|
|
8060
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
8061
|
+
* Zero when called at the exact moment the trough was set, or when current PnL <= trough PnL.
|
|
8062
|
+
*
|
|
8063
|
+
* Returns null if no pending signal exists.
|
|
8064
|
+
*
|
|
8065
|
+
* @param symbol - Trading pair symbol
|
|
8066
|
+
* @param currentPrice - Current market price
|
|
8067
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
8068
|
+
*/
|
|
8069
|
+
async getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice) {
|
|
8070
|
+
this.params.logger.debug("ClientStrategy getPositionHighestMaxDrawdownPnlCost", { symbol, currentPrice });
|
|
8071
|
+
if (!this._pendingSignal) {
|
|
8072
|
+
return null;
|
|
8073
|
+
}
|
|
8074
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8075
|
+
return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8076
|
+
}
|
|
7806
8077
|
/**
|
|
7807
8078
|
* Performs a single tick of strategy execution.
|
|
7808
8079
|
*
|
|
@@ -9886,7 +10157,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
9886
10157
|
* @param backtest - Whether running in backtest mode
|
|
9887
10158
|
* @returns Unique string key for memoization
|
|
9888
10159
|
*/
|
|
9889
|
-
const CREATE_KEY_FN$
|
|
10160
|
+
const CREATE_KEY_FN$t = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
9890
10161
|
const parts = [symbol, strategyName, exchangeName];
|
|
9891
10162
|
if (frameName)
|
|
9892
10163
|
parts.push(frameName);
|
|
@@ -10153,7 +10424,7 @@ class StrategyConnectionService {
|
|
|
10153
10424
|
* @param backtest - Whether running in backtest mode
|
|
10154
10425
|
* @returns Configured ClientStrategy instance
|
|
10155
10426
|
*/
|
|
10156
|
-
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
10427
|
+
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10157
10428
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10158
10429
|
return new ClientStrategy({
|
|
10159
10430
|
symbol,
|
|
@@ -10910,6 +11181,90 @@ class StrategyConnectionService {
|
|
|
10910
11181
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10911
11182
|
return await strategy.getPositionMaxDrawdownPnlCost(symbol);
|
|
10912
11183
|
};
|
|
11184
|
+
/**
|
|
11185
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
11186
|
+
*
|
|
11187
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11188
|
+
* ClientStrategy.getPositionHighestProfitDistancePnlPercentage().
|
|
11189
|
+
* Returns null if no pending signal exists.
|
|
11190
|
+
*
|
|
11191
|
+
* @param backtest - Whether running in backtest mode
|
|
11192
|
+
* @param symbol - Trading pair symbol
|
|
11193
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11194
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
11195
|
+
*/
|
|
11196
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
11197
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitDistancePnlPercentage", {
|
|
11198
|
+
symbol,
|
|
11199
|
+
context,
|
|
11200
|
+
});
|
|
11201
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11202
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11203
|
+
return await strategy.getPositionHighestProfitDistancePnlPercentage(symbol, currentPrice);
|
|
11204
|
+
};
|
|
11205
|
+
/**
|
|
11206
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
11207
|
+
*
|
|
11208
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11209
|
+
* ClientStrategy.getPositionHighestProfitDistancePnlCost().
|
|
11210
|
+
* Returns null if no pending signal exists.
|
|
11211
|
+
*
|
|
11212
|
+
* @param backtest - Whether running in backtest mode
|
|
11213
|
+
* @param symbol - Trading pair symbol
|
|
11214
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11215
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
11216
|
+
*/
|
|
11217
|
+
this.getPositionHighestProfitDistancePnlCost = async (backtest, symbol, context) => {
|
|
11218
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitDistancePnlCost", {
|
|
11219
|
+
symbol,
|
|
11220
|
+
context,
|
|
11221
|
+
});
|
|
11222
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11223
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11224
|
+
return await strategy.getPositionHighestProfitDistancePnlCost(symbol, currentPrice);
|
|
11225
|
+
};
|
|
11226
|
+
/**
|
|
11227
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
11228
|
+
*
|
|
11229
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11230
|
+
* ClientStrategy.getPositionHighestMaxDrawdownPnlPercentage().
|
|
11231
|
+
* Returns null if no pending signal exists.
|
|
11232
|
+
*
|
|
11233
|
+
* @param backtest - Whether running in backtest mode
|
|
11234
|
+
* @param symbol - Trading pair symbol
|
|
11235
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11236
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
11237
|
+
*/
|
|
11238
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
|
|
11239
|
+
this.loggerService.log("strategyConnectionService getPositionHighestMaxDrawdownPnlPercentage", {
|
|
11240
|
+
symbol,
|
|
11241
|
+
context,
|
|
11242
|
+
});
|
|
11243
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11244
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11245
|
+
return await strategy.getPositionHighestMaxDrawdownPnlPercentage(symbol, currentPrice);
|
|
11246
|
+
};
|
|
11247
|
+
/**
|
|
11248
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
11249
|
+
*
|
|
11250
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11251
|
+
* ClientStrategy.getPositionHighestMaxDrawdownPnlCost().
|
|
11252
|
+
* Returns null if no pending signal exists.
|
|
11253
|
+
*
|
|
11254
|
+
* @param backtest - Whether running in backtest mode
|
|
11255
|
+
* @param symbol - Trading pair symbol
|
|
11256
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11257
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
11258
|
+
*/
|
|
11259
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (backtest, symbol, context) => {
|
|
11260
|
+
this.loggerService.log("strategyConnectionService getPositionHighestMaxDrawdownPnlCost", {
|
|
11261
|
+
symbol,
|
|
11262
|
+
context,
|
|
11263
|
+
});
|
|
11264
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11265
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11266
|
+
return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
|
|
11267
|
+
};
|
|
10913
11268
|
/**
|
|
10914
11269
|
* Disposes the ClientStrategy instance for the given context.
|
|
10915
11270
|
*
|
|
@@ -10948,7 +11303,7 @@ class StrategyConnectionService {
|
|
|
10948
11303
|
}
|
|
10949
11304
|
return;
|
|
10950
11305
|
}
|
|
10951
|
-
const key = CREATE_KEY_FN$
|
|
11306
|
+
const key = CREATE_KEY_FN$t(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
10952
11307
|
if (!this.getStrategy.has(key)) {
|
|
10953
11308
|
return;
|
|
10954
11309
|
}
|
|
@@ -11350,7 +11705,7 @@ class StrategyConnectionService {
|
|
|
11350
11705
|
* Maps FrameInterval to minutes for timestamp calculation.
|
|
11351
11706
|
* Used to generate timeframe arrays with proper spacing.
|
|
11352
11707
|
*/
|
|
11353
|
-
const INTERVAL_MINUTES$
|
|
11708
|
+
const INTERVAL_MINUTES$6 = {
|
|
11354
11709
|
"1m": 1,
|
|
11355
11710
|
"3m": 3,
|
|
11356
11711
|
"5m": 5,
|
|
@@ -11406,7 +11761,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
11406
11761
|
symbol,
|
|
11407
11762
|
});
|
|
11408
11763
|
const { interval, startDate, endDate } = self.params;
|
|
11409
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
11764
|
+
const intervalMinutes = INTERVAL_MINUTES$6[interval];
|
|
11410
11765
|
if (!intervalMinutes) {
|
|
11411
11766
|
throw new Error(`ClientFrame unknown interval: ${interval}`);
|
|
11412
11767
|
}
|
|
@@ -11771,8 +12126,8 @@ const get = (object, path) => {
|
|
|
11771
12126
|
return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
|
|
11772
12127
|
};
|
|
11773
12128
|
|
|
11774
|
-
const MS_PER_MINUTE$
|
|
11775
|
-
const INTERVAL_MINUTES$
|
|
12129
|
+
const MS_PER_MINUTE$5 = 60000;
|
|
12130
|
+
const INTERVAL_MINUTES$5 = {
|
|
11776
12131
|
"1m": 1,
|
|
11777
12132
|
"3m": 3,
|
|
11778
12133
|
"5m": 5,
|
|
@@ -11804,11 +12159,11 @@ const INTERVAL_MINUTES$4 = {
|
|
|
11804
12159
|
* @returns New Date aligned down to interval boundary
|
|
11805
12160
|
*/
|
|
11806
12161
|
const alignToInterval = (date, interval) => {
|
|
11807
|
-
const minutes = INTERVAL_MINUTES$
|
|
12162
|
+
const minutes = INTERVAL_MINUTES$5[interval];
|
|
11808
12163
|
if (minutes === undefined) {
|
|
11809
12164
|
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
11810
12165
|
}
|
|
11811
|
-
const intervalMs = minutes * MS_PER_MINUTE$
|
|
12166
|
+
const intervalMs = minutes * MS_PER_MINUTE$5;
|
|
11812
12167
|
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
11813
12168
|
};
|
|
11814
12169
|
|
|
@@ -12117,7 +12472,7 @@ class ClientRisk {
|
|
|
12117
12472
|
* @param backtest - Whether running in backtest mode
|
|
12118
12473
|
* @returns Unique string key for memoization
|
|
12119
12474
|
*/
|
|
12120
|
-
const CREATE_KEY_FN$
|
|
12475
|
+
const CREATE_KEY_FN$s = (riskName, exchangeName, frameName, backtest) => {
|
|
12121
12476
|
const parts = [riskName, exchangeName];
|
|
12122
12477
|
if (frameName)
|
|
12123
12478
|
parts.push(frameName);
|
|
@@ -12217,7 +12572,7 @@ class RiskConnectionService {
|
|
|
12217
12572
|
* @param backtest - True if backtest mode, false if live mode
|
|
12218
12573
|
* @returns Configured ClientRisk instance
|
|
12219
12574
|
*/
|
|
12220
|
-
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12575
|
+
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
12221
12576
|
const schema = this.riskSchemaService.get(riskName);
|
|
12222
12577
|
return new ClientRisk({
|
|
12223
12578
|
...schema,
|
|
@@ -12286,7 +12641,7 @@ class RiskConnectionService {
|
|
|
12286
12641
|
payload,
|
|
12287
12642
|
});
|
|
12288
12643
|
if (payload) {
|
|
12289
|
-
const key = CREATE_KEY_FN$
|
|
12644
|
+
const key = CREATE_KEY_FN$s(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
12290
12645
|
this.getRisk.clear(key);
|
|
12291
12646
|
}
|
|
12292
12647
|
else {
|
|
@@ -13330,7 +13685,7 @@ class ClientAction {
|
|
|
13330
13685
|
* @param backtest - Whether running in backtest mode
|
|
13331
13686
|
* @returns Unique string key for memoization
|
|
13332
13687
|
*/
|
|
13333
|
-
const CREATE_KEY_FN$
|
|
13688
|
+
const CREATE_KEY_FN$r = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13334
13689
|
const parts = [actionName, strategyName, exchangeName];
|
|
13335
13690
|
if (frameName)
|
|
13336
13691
|
parts.push(frameName);
|
|
@@ -13382,7 +13737,7 @@ class ActionConnectionService {
|
|
|
13382
13737
|
* @param backtest - True if backtest mode, false if live mode
|
|
13383
13738
|
* @returns Configured ClientAction instance
|
|
13384
13739
|
*/
|
|
13385
|
-
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
13740
|
+
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13386
13741
|
const schema = this.actionSchemaService.get(actionName);
|
|
13387
13742
|
return new ClientAction({
|
|
13388
13743
|
...schema,
|
|
@@ -13593,7 +13948,7 @@ class ActionConnectionService {
|
|
|
13593
13948
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
13594
13949
|
return;
|
|
13595
13950
|
}
|
|
13596
|
-
const key = CREATE_KEY_FN$
|
|
13951
|
+
const key = CREATE_KEY_FN$r(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
13597
13952
|
if (!this.getAction.has(key)) {
|
|
13598
13953
|
return;
|
|
13599
13954
|
}
|
|
@@ -13611,7 +13966,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
13611
13966
|
* @param exchangeName - Exchange name
|
|
13612
13967
|
* @returns Unique string key for memoization
|
|
13613
13968
|
*/
|
|
13614
|
-
const CREATE_KEY_FN$
|
|
13969
|
+
const CREATE_KEY_FN$q = (exchangeName) => {
|
|
13615
13970
|
return exchangeName;
|
|
13616
13971
|
};
|
|
13617
13972
|
/**
|
|
@@ -13635,7 +13990,7 @@ class ExchangeCoreService {
|
|
|
13635
13990
|
* @param exchangeName - Name of the exchange to validate
|
|
13636
13991
|
* @returns Promise that resolves when validation is complete
|
|
13637
13992
|
*/
|
|
13638
|
-
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
13993
|
+
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$q(exchangeName), async (exchangeName) => {
|
|
13639
13994
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
13640
13995
|
exchangeName,
|
|
13641
13996
|
});
|
|
@@ -13887,7 +14242,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
13887
14242
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13888
14243
|
* @returns Unique string key for memoization
|
|
13889
14244
|
*/
|
|
13890
|
-
const CREATE_KEY_FN$
|
|
14245
|
+
const CREATE_KEY_FN$p = (context) => {
|
|
13891
14246
|
const parts = [context.strategyName, context.exchangeName];
|
|
13892
14247
|
if (context.frameName)
|
|
13893
14248
|
parts.push(context.frameName);
|
|
@@ -13919,7 +14274,7 @@ class StrategyCoreService {
|
|
|
13919
14274
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13920
14275
|
* @returns Promise that resolves when validation is complete
|
|
13921
14276
|
*/
|
|
13922
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
14277
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
|
|
13923
14278
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
13924
14279
|
context,
|
|
13925
14280
|
});
|
|
@@ -15071,6 +15426,82 @@ class StrategyCoreService {
|
|
|
15071
15426
|
await this.validate(context);
|
|
15072
15427
|
return await this.strategyConnectionService.getPositionMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15073
15428
|
};
|
|
15429
|
+
/**
|
|
15430
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
15431
|
+
*
|
|
15432
|
+
* Delegates to StrategyConnectionService.getPositionHighestProfitDistancePnlPercentage().
|
|
15433
|
+
* Returns null if no pending signal exists.
|
|
15434
|
+
*
|
|
15435
|
+
* @param backtest - Whether running in backtest mode
|
|
15436
|
+
* @param symbol - Trading pair symbol
|
|
15437
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15438
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
15439
|
+
*/
|
|
15440
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
15441
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitDistancePnlPercentage", {
|
|
15442
|
+
symbol,
|
|
15443
|
+
context,
|
|
15444
|
+
});
|
|
15445
|
+
await this.validate(context);
|
|
15446
|
+
return await this.strategyConnectionService.getPositionHighestProfitDistancePnlPercentage(backtest, symbol, context);
|
|
15447
|
+
};
|
|
15448
|
+
/**
|
|
15449
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
15450
|
+
*
|
|
15451
|
+
* Delegates to StrategyConnectionService.getPositionHighestProfitDistancePnlCost().
|
|
15452
|
+
* Returns null if no pending signal exists.
|
|
15453
|
+
*
|
|
15454
|
+
* @param backtest - Whether running in backtest mode
|
|
15455
|
+
* @param symbol - Trading pair symbol
|
|
15456
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15457
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
15458
|
+
*/
|
|
15459
|
+
this.getPositionHighestProfitDistancePnlCost = async (backtest, symbol, context) => {
|
|
15460
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitDistancePnlCost", {
|
|
15461
|
+
symbol,
|
|
15462
|
+
context,
|
|
15463
|
+
});
|
|
15464
|
+
await this.validate(context);
|
|
15465
|
+
return await this.strategyConnectionService.getPositionHighestProfitDistancePnlCost(backtest, symbol, context);
|
|
15466
|
+
};
|
|
15467
|
+
/**
|
|
15468
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
15469
|
+
*
|
|
15470
|
+
* Delegates to StrategyConnectionService.getPositionHighestMaxDrawdownPnlPercentage().
|
|
15471
|
+
* Returns null if no pending signal exists.
|
|
15472
|
+
*
|
|
15473
|
+
* @param backtest - Whether running in backtest mode
|
|
15474
|
+
* @param symbol - Trading pair symbol
|
|
15475
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15476
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
15477
|
+
*/
|
|
15478
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
|
|
15479
|
+
this.loggerService.log("strategyCoreService getPositionHighestMaxDrawdownPnlPercentage", {
|
|
15480
|
+
symbol,
|
|
15481
|
+
context,
|
|
15482
|
+
});
|
|
15483
|
+
await this.validate(context);
|
|
15484
|
+
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlPercentage(backtest, symbol, context);
|
|
15485
|
+
};
|
|
15486
|
+
/**
|
|
15487
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
15488
|
+
*
|
|
15489
|
+
* Delegates to StrategyConnectionService.getPositionHighestMaxDrawdownPnlCost().
|
|
15490
|
+
* Returns null if no pending signal exists.
|
|
15491
|
+
*
|
|
15492
|
+
* @param backtest - Whether running in backtest mode
|
|
15493
|
+
* @param symbol - Trading pair symbol
|
|
15494
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15495
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
15496
|
+
*/
|
|
15497
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (backtest, symbol, context) => {
|
|
15498
|
+
this.loggerService.log("strategyCoreService getPositionHighestMaxDrawdownPnlCost", {
|
|
15499
|
+
symbol,
|
|
15500
|
+
context,
|
|
15501
|
+
});
|
|
15502
|
+
await this.validate(context);
|
|
15503
|
+
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15504
|
+
};
|
|
15074
15505
|
}
|
|
15075
15506
|
}
|
|
15076
15507
|
|
|
@@ -15143,7 +15574,7 @@ class SizingGlobalService {
|
|
|
15143
15574
|
* @param context - Context with riskName, exchangeName, frameName
|
|
15144
15575
|
* @returns Unique string key for memoization
|
|
15145
15576
|
*/
|
|
15146
|
-
const CREATE_KEY_FN$
|
|
15577
|
+
const CREATE_KEY_FN$o = (context) => {
|
|
15147
15578
|
const parts = [context.riskName, context.exchangeName];
|
|
15148
15579
|
if (context.frameName)
|
|
15149
15580
|
parts.push(context.frameName);
|
|
@@ -15169,7 +15600,7 @@ class RiskGlobalService {
|
|
|
15169
15600
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
15170
15601
|
* @returns Promise that resolves when validation is complete
|
|
15171
15602
|
*/
|
|
15172
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
15603
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
|
|
15173
15604
|
this.loggerService.log("riskGlobalService validate", {
|
|
15174
15605
|
context,
|
|
15175
15606
|
});
|
|
@@ -15247,7 +15678,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
15247
15678
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15248
15679
|
* @returns Unique string key for memoization
|
|
15249
15680
|
*/
|
|
15250
|
-
const CREATE_KEY_FN$
|
|
15681
|
+
const CREATE_KEY_FN$n = (context) => {
|
|
15251
15682
|
const parts = [context.strategyName, context.exchangeName];
|
|
15252
15683
|
if (context.frameName)
|
|
15253
15684
|
parts.push(context.frameName);
|
|
@@ -15291,7 +15722,7 @@ class ActionCoreService {
|
|
|
15291
15722
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
15292
15723
|
* @returns Promise that resolves when all validations complete
|
|
15293
15724
|
*/
|
|
15294
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
15725
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
|
|
15295
15726
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
15296
15727
|
context,
|
|
15297
15728
|
});
|
|
@@ -16886,8 +17317,8 @@ class BacktestLogicPrivateService {
|
|
|
16886
17317
|
}
|
|
16887
17318
|
|
|
16888
17319
|
const EMITTER_CHECK_INTERVAL = 5000;
|
|
16889
|
-
const MS_PER_MINUTE$
|
|
16890
|
-
const INTERVAL_MINUTES$
|
|
17320
|
+
const MS_PER_MINUTE$4 = 60000;
|
|
17321
|
+
const INTERVAL_MINUTES$4 = {
|
|
16891
17322
|
"1m": 1,
|
|
16892
17323
|
"3m": 3,
|
|
16893
17324
|
"5m": 5,
|
|
@@ -16903,7 +17334,7 @@ const INTERVAL_MINUTES$3 = {
|
|
|
16903
17334
|
};
|
|
16904
17335
|
const createEmitter = functoolsKit.memoize(([interval]) => `${interval}`, (interval) => {
|
|
16905
17336
|
const tickSubject = new functoolsKit.Subject();
|
|
16906
|
-
const intervalMs = INTERVAL_MINUTES$
|
|
17337
|
+
const intervalMs = INTERVAL_MINUTES$4[interval] * MS_PER_MINUTE$4;
|
|
16907
17338
|
{
|
|
16908
17339
|
let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
16909
17340
|
functoolsKit.Source.fromInterval(EMITTER_CHECK_INTERVAL)
|
|
@@ -20287,7 +20718,7 @@ const ReportWriter = new ReportWriterAdapter();
|
|
|
20287
20718
|
* @param backtest - Whether running in backtest mode
|
|
20288
20719
|
* @returns Unique string key for memoization
|
|
20289
20720
|
*/
|
|
20290
|
-
const CREATE_KEY_FN$
|
|
20721
|
+
const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20291
20722
|
const parts = [symbol, strategyName, exchangeName];
|
|
20292
20723
|
if (frameName)
|
|
20293
20724
|
parts.push(frameName);
|
|
@@ -20533,7 +20964,7 @@ class BacktestMarkdownService {
|
|
|
20533
20964
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20534
20965
|
* Each combination gets its own isolated storage instance.
|
|
20535
20966
|
*/
|
|
20536
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20967
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
|
|
20537
20968
|
/**
|
|
20538
20969
|
* Processes tick events and accumulates closed signals.
|
|
20539
20970
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -20690,7 +21121,7 @@ class BacktestMarkdownService {
|
|
|
20690
21121
|
payload,
|
|
20691
21122
|
});
|
|
20692
21123
|
if (payload) {
|
|
20693
|
-
const key = CREATE_KEY_FN$
|
|
21124
|
+
const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20694
21125
|
this.getStorage.clear(key);
|
|
20695
21126
|
}
|
|
20696
21127
|
else {
|
|
@@ -20752,7 +21183,7 @@ class BacktestMarkdownService {
|
|
|
20752
21183
|
* @param backtest - Whether running in backtest mode
|
|
20753
21184
|
* @returns Unique string key for memoization
|
|
20754
21185
|
*/
|
|
20755
|
-
const CREATE_KEY_FN$
|
|
21186
|
+
const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20756
21187
|
const parts = [symbol, strategyName, exchangeName];
|
|
20757
21188
|
if (frameName)
|
|
20758
21189
|
parts.push(frameName);
|
|
@@ -21247,7 +21678,7 @@ class LiveMarkdownService {
|
|
|
21247
21678
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21248
21679
|
* Each combination gets its own isolated storage instance.
|
|
21249
21680
|
*/
|
|
21250
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21681
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
|
|
21251
21682
|
/**
|
|
21252
21683
|
* Subscribes to live signal emitter to receive tick events.
|
|
21253
21684
|
* Protected against multiple subscriptions.
|
|
@@ -21465,7 +21896,7 @@ class LiveMarkdownService {
|
|
|
21465
21896
|
payload,
|
|
21466
21897
|
});
|
|
21467
21898
|
if (payload) {
|
|
21468
|
-
const key = CREATE_KEY_FN$
|
|
21899
|
+
const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21469
21900
|
this.getStorage.clear(key);
|
|
21470
21901
|
}
|
|
21471
21902
|
else {
|
|
@@ -21485,7 +21916,7 @@ class LiveMarkdownService {
|
|
|
21485
21916
|
* @param backtest - Whether running in backtest mode
|
|
21486
21917
|
* @returns Unique string key for memoization
|
|
21487
21918
|
*/
|
|
21488
|
-
const CREATE_KEY_FN$
|
|
21919
|
+
const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21489
21920
|
const parts = [symbol, strategyName, exchangeName];
|
|
21490
21921
|
if (frameName)
|
|
21491
21922
|
parts.push(frameName);
|
|
@@ -21774,7 +22205,7 @@ class ScheduleMarkdownService {
|
|
|
21774
22205
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21775
22206
|
* Each combination gets its own isolated storage instance.
|
|
21776
22207
|
*/
|
|
21777
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22208
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
|
|
21778
22209
|
/**
|
|
21779
22210
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
21780
22211
|
* Protected against multiple subscriptions.
|
|
@@ -21977,7 +22408,7 @@ class ScheduleMarkdownService {
|
|
|
21977
22408
|
payload,
|
|
21978
22409
|
});
|
|
21979
22410
|
if (payload) {
|
|
21980
|
-
const key = CREATE_KEY_FN$
|
|
22411
|
+
const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21981
22412
|
this.getStorage.clear(key);
|
|
21982
22413
|
}
|
|
21983
22414
|
else {
|
|
@@ -21997,7 +22428,7 @@ class ScheduleMarkdownService {
|
|
|
21997
22428
|
* @param backtest - Whether running in backtest mode
|
|
21998
22429
|
* @returns Unique string key for memoization
|
|
21999
22430
|
*/
|
|
22000
|
-
const CREATE_KEY_FN$
|
|
22431
|
+
const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
22001
22432
|
const parts = [symbol, strategyName, exchangeName];
|
|
22002
22433
|
if (frameName)
|
|
22003
22434
|
parts.push(frameName);
|
|
@@ -22242,7 +22673,7 @@ class PerformanceMarkdownService {
|
|
|
22242
22673
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
22243
22674
|
* Each combination gets its own isolated storage instance.
|
|
22244
22675
|
*/
|
|
22245
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22676
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
|
|
22246
22677
|
/**
|
|
22247
22678
|
* Subscribes to performance emitter to receive performance events.
|
|
22248
22679
|
* Protected against multiple subscriptions.
|
|
@@ -22409,7 +22840,7 @@ class PerformanceMarkdownService {
|
|
|
22409
22840
|
payload,
|
|
22410
22841
|
});
|
|
22411
22842
|
if (payload) {
|
|
22412
|
-
const key = CREATE_KEY_FN$
|
|
22843
|
+
const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
22413
22844
|
this.getStorage.clear(key);
|
|
22414
22845
|
}
|
|
22415
22846
|
else {
|
|
@@ -22888,7 +23319,7 @@ class WalkerMarkdownService {
|
|
|
22888
23319
|
* @param backtest - Whether running in backtest mode
|
|
22889
23320
|
* @returns Unique string key for memoization
|
|
22890
23321
|
*/
|
|
22891
|
-
const CREATE_KEY_FN$
|
|
23322
|
+
const CREATE_KEY_FN$i = (exchangeName, frameName, backtest) => {
|
|
22892
23323
|
const parts = [exchangeName];
|
|
22893
23324
|
if (frameName)
|
|
22894
23325
|
parts.push(frameName);
|
|
@@ -23335,7 +23766,7 @@ class HeatMarkdownService {
|
|
|
23335
23766
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
23336
23767
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
23337
23768
|
*/
|
|
23338
|
-
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
23769
|
+
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
23339
23770
|
/**
|
|
23340
23771
|
* Subscribes to signal emitter to receive tick events.
|
|
23341
23772
|
* Protected against multiple subscriptions.
|
|
@@ -23553,7 +23984,7 @@ class HeatMarkdownService {
|
|
|
23553
23984
|
payload,
|
|
23554
23985
|
});
|
|
23555
23986
|
if (payload) {
|
|
23556
|
-
const key = CREATE_KEY_FN$
|
|
23987
|
+
const key = CREATE_KEY_FN$i(payload.exchangeName, payload.frameName, payload.backtest);
|
|
23557
23988
|
this.getStorage.clear(key);
|
|
23558
23989
|
}
|
|
23559
23990
|
else {
|
|
@@ -24584,7 +25015,7 @@ class ClientPartial {
|
|
|
24584
25015
|
* @param backtest - Whether running in backtest mode
|
|
24585
25016
|
* @returns Unique string key for memoization
|
|
24586
25017
|
*/
|
|
24587
|
-
const CREATE_KEY_FN$
|
|
25018
|
+
const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
24588
25019
|
/**
|
|
24589
25020
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
24590
25021
|
*
|
|
@@ -24706,7 +25137,7 @@ class PartialConnectionService {
|
|
|
24706
25137
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
24707
25138
|
* Value: ClientPartial instance with logger and event emitters
|
|
24708
25139
|
*/
|
|
24709
|
-
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
25140
|
+
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
|
|
24710
25141
|
return new ClientPartial({
|
|
24711
25142
|
signalId,
|
|
24712
25143
|
logger: this.loggerService,
|
|
@@ -24796,7 +25227,7 @@ class PartialConnectionService {
|
|
|
24796
25227
|
const partial = this.getPartial(data.id, backtest);
|
|
24797
25228
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
24798
25229
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
24799
|
-
const key = CREATE_KEY_FN$
|
|
25230
|
+
const key = CREATE_KEY_FN$h(data.id, backtest);
|
|
24800
25231
|
this.getPartial.clear(key);
|
|
24801
25232
|
};
|
|
24802
25233
|
}
|
|
@@ -24812,7 +25243,7 @@ class PartialConnectionService {
|
|
|
24812
25243
|
* @param backtest - Whether running in backtest mode
|
|
24813
25244
|
* @returns Unique string key for memoization
|
|
24814
25245
|
*/
|
|
24815
|
-
const CREATE_KEY_FN$
|
|
25246
|
+
const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24816
25247
|
const parts = [symbol, strategyName, exchangeName];
|
|
24817
25248
|
if (frameName)
|
|
24818
25249
|
parts.push(frameName);
|
|
@@ -25035,7 +25466,7 @@ class PartialMarkdownService {
|
|
|
25035
25466
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
25036
25467
|
* Each combination gets its own isolated storage instance.
|
|
25037
25468
|
*/
|
|
25038
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
25469
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
|
|
25039
25470
|
/**
|
|
25040
25471
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
25041
25472
|
* Protected against multiple subscriptions.
|
|
@@ -25245,7 +25676,7 @@ class PartialMarkdownService {
|
|
|
25245
25676
|
payload,
|
|
25246
25677
|
});
|
|
25247
25678
|
if (payload) {
|
|
25248
|
-
const key = CREATE_KEY_FN$
|
|
25679
|
+
const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
25249
25680
|
this.getStorage.clear(key);
|
|
25250
25681
|
}
|
|
25251
25682
|
else {
|
|
@@ -25261,7 +25692,7 @@ class PartialMarkdownService {
|
|
|
25261
25692
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
25262
25693
|
* @returns Unique string key for memoization
|
|
25263
25694
|
*/
|
|
25264
|
-
const CREATE_KEY_FN$
|
|
25695
|
+
const CREATE_KEY_FN$f = (context) => {
|
|
25265
25696
|
const parts = [context.strategyName, context.exchangeName];
|
|
25266
25697
|
if (context.frameName)
|
|
25267
25698
|
parts.push(context.frameName);
|
|
@@ -25335,7 +25766,7 @@ class PartialGlobalService {
|
|
|
25335
25766
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
25336
25767
|
* @param methodName - Name of the calling method for error tracking
|
|
25337
25768
|
*/
|
|
25338
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
25769
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
|
|
25339
25770
|
this.loggerService.log("partialGlobalService validate", {
|
|
25340
25771
|
context,
|
|
25341
25772
|
methodName,
|
|
@@ -25790,7 +26221,7 @@ class ClientBreakeven {
|
|
|
25790
26221
|
* @param backtest - Whether running in backtest mode
|
|
25791
26222
|
* @returns Unique string key for memoization
|
|
25792
26223
|
*/
|
|
25793
|
-
const CREATE_KEY_FN$
|
|
26224
|
+
const CREATE_KEY_FN$e = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
25794
26225
|
/**
|
|
25795
26226
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
25796
26227
|
*
|
|
@@ -25876,7 +26307,7 @@ class BreakevenConnectionService {
|
|
|
25876
26307
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
25877
26308
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
25878
26309
|
*/
|
|
25879
|
-
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
26310
|
+
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$e(signalId, backtest), (signalId, backtest) => {
|
|
25880
26311
|
return new ClientBreakeven({
|
|
25881
26312
|
signalId,
|
|
25882
26313
|
logger: this.loggerService,
|
|
@@ -25937,7 +26368,7 @@ class BreakevenConnectionService {
|
|
|
25937
26368
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
25938
26369
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
25939
26370
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
25940
|
-
const key = CREATE_KEY_FN$
|
|
26371
|
+
const key = CREATE_KEY_FN$e(data.id, backtest);
|
|
25941
26372
|
this.getBreakeven.clear(key);
|
|
25942
26373
|
};
|
|
25943
26374
|
}
|
|
@@ -25953,7 +26384,7 @@ class BreakevenConnectionService {
|
|
|
25953
26384
|
* @param backtest - Whether running in backtest mode
|
|
25954
26385
|
* @returns Unique string key for memoization
|
|
25955
26386
|
*/
|
|
25956
|
-
const CREATE_KEY_FN$
|
|
26387
|
+
const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
25957
26388
|
const parts = [symbol, strategyName, exchangeName];
|
|
25958
26389
|
if (frameName)
|
|
25959
26390
|
parts.push(frameName);
|
|
@@ -26128,7 +26559,7 @@ class BreakevenMarkdownService {
|
|
|
26128
26559
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26129
26560
|
* Each combination gets its own isolated storage instance.
|
|
26130
26561
|
*/
|
|
26131
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
26562
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
|
|
26132
26563
|
/**
|
|
26133
26564
|
* Subscribes to breakeven signal emitter to receive events.
|
|
26134
26565
|
* Protected against multiple subscriptions.
|
|
@@ -26317,7 +26748,7 @@ class BreakevenMarkdownService {
|
|
|
26317
26748
|
payload,
|
|
26318
26749
|
});
|
|
26319
26750
|
if (payload) {
|
|
26320
|
-
const key = CREATE_KEY_FN$
|
|
26751
|
+
const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26321
26752
|
this.getStorage.clear(key);
|
|
26322
26753
|
}
|
|
26323
26754
|
else {
|
|
@@ -26333,7 +26764,7 @@ class BreakevenMarkdownService {
|
|
|
26333
26764
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
26334
26765
|
* @returns Unique string key for memoization
|
|
26335
26766
|
*/
|
|
26336
|
-
const CREATE_KEY_FN$
|
|
26767
|
+
const CREATE_KEY_FN$c = (context) => {
|
|
26337
26768
|
const parts = [context.strategyName, context.exchangeName];
|
|
26338
26769
|
if (context.frameName)
|
|
26339
26770
|
parts.push(context.frameName);
|
|
@@ -26407,7 +26838,7 @@ class BreakevenGlobalService {
|
|
|
26407
26838
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
26408
26839
|
* @param methodName - Name of the calling method for error tracking
|
|
26409
26840
|
*/
|
|
26410
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
26841
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$c(context), (context, methodName) => {
|
|
26411
26842
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
26412
26843
|
context,
|
|
26413
26844
|
methodName,
|
|
@@ -26628,7 +27059,7 @@ class ConfigValidationService {
|
|
|
26628
27059
|
* @param backtest - Whether running in backtest mode
|
|
26629
27060
|
* @returns Unique string key for memoization
|
|
26630
27061
|
*/
|
|
26631
|
-
const CREATE_KEY_FN$
|
|
27062
|
+
const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
26632
27063
|
const parts = [symbol, strategyName, exchangeName];
|
|
26633
27064
|
if (frameName)
|
|
26634
27065
|
parts.push(frameName);
|
|
@@ -26795,7 +27226,7 @@ class RiskMarkdownService {
|
|
|
26795
27226
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26796
27227
|
* Each combination gets its own isolated storage instance.
|
|
26797
27228
|
*/
|
|
26798
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
27229
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
|
|
26799
27230
|
/**
|
|
26800
27231
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
26801
27232
|
* Protected against multiple subscriptions.
|
|
@@ -26984,7 +27415,7 @@ class RiskMarkdownService {
|
|
|
26984
27415
|
payload,
|
|
26985
27416
|
});
|
|
26986
27417
|
if (payload) {
|
|
26987
|
-
const key = CREATE_KEY_FN$
|
|
27418
|
+
const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26988
27419
|
this.getStorage.clear(key);
|
|
26989
27420
|
}
|
|
26990
27421
|
else {
|
|
@@ -29363,7 +29794,7 @@ class HighestProfitReportService {
|
|
|
29363
29794
|
* @returns Colon-separated key string for memoization
|
|
29364
29795
|
* @internal
|
|
29365
29796
|
*/
|
|
29366
|
-
const CREATE_KEY_FN$
|
|
29797
|
+
const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29367
29798
|
const parts = [symbol, strategyName, exchangeName];
|
|
29368
29799
|
if (frameName)
|
|
29369
29800
|
parts.push(frameName);
|
|
@@ -29605,7 +30036,7 @@ class StrategyMarkdownService {
|
|
|
29605
30036
|
*
|
|
29606
30037
|
* @internal
|
|
29607
30038
|
*/
|
|
29608
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
30039
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
|
|
29609
30040
|
/**
|
|
29610
30041
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
29611
30042
|
*
|
|
@@ -30173,7 +30604,7 @@ class StrategyMarkdownService {
|
|
|
30173
30604
|
this.clear = async (payload) => {
|
|
30174
30605
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
30175
30606
|
if (payload) {
|
|
30176
|
-
const key = CREATE_KEY_FN$
|
|
30607
|
+
const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
30177
30608
|
this.getStorage.clear(key);
|
|
30178
30609
|
}
|
|
30179
30610
|
else {
|
|
@@ -30281,7 +30712,7 @@ class StrategyMarkdownService {
|
|
|
30281
30712
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
30282
30713
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
30283
30714
|
*/
|
|
30284
|
-
const CREATE_KEY_FN$
|
|
30715
|
+
const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
30285
30716
|
const parts = [symbol, strategyName, exchangeName];
|
|
30286
30717
|
if (frameName)
|
|
30287
30718
|
parts.push(frameName);
|
|
@@ -30474,7 +30905,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
30474
30905
|
class SyncMarkdownService {
|
|
30475
30906
|
constructor() {
|
|
30476
30907
|
this.loggerService = inject(TYPES.loggerService);
|
|
30477
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
30908
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
|
|
30478
30909
|
/**
|
|
30479
30910
|
* Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
|
|
30480
30911
|
* Protected against multiple subscriptions via `singleshot` — subsequent calls
|
|
@@ -30670,7 +31101,7 @@ class SyncMarkdownService {
|
|
|
30670
31101
|
this.clear = async (payload) => {
|
|
30671
31102
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
30672
31103
|
if (payload) {
|
|
30673
|
-
const key = CREATE_KEY_FN$
|
|
31104
|
+
const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
30674
31105
|
this.getStorage.clear(key);
|
|
30675
31106
|
}
|
|
30676
31107
|
else {
|
|
@@ -30683,7 +31114,7 @@ class SyncMarkdownService {
|
|
|
30683
31114
|
/**
|
|
30684
31115
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
30685
31116
|
*/
|
|
30686
|
-
const CREATE_KEY_FN$
|
|
31117
|
+
const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
30687
31118
|
const parts = [symbol, strategyName, exchangeName];
|
|
30688
31119
|
if (frameName)
|
|
30689
31120
|
parts.push(frameName);
|
|
@@ -30859,7 +31290,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
30859
31290
|
class HighestProfitMarkdownService {
|
|
30860
31291
|
constructor() {
|
|
30861
31292
|
this.loggerService = inject(TYPES.loggerService);
|
|
30862
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31293
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
|
|
30863
31294
|
/**
|
|
30864
31295
|
* Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
|
|
30865
31296
|
* events. Protected against multiple subscriptions via `singleshot` — subsequent
|
|
@@ -31025,7 +31456,7 @@ class HighestProfitMarkdownService {
|
|
|
31025
31456
|
this.clear = async (payload) => {
|
|
31026
31457
|
this.loggerService.log("highestProfitMarkdownService clear", { payload });
|
|
31027
31458
|
if (payload) {
|
|
31028
|
-
const key = CREATE_KEY_FN$
|
|
31459
|
+
const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31029
31460
|
this.getStorage.clear(key);
|
|
31030
31461
|
}
|
|
31031
31462
|
else {
|
|
@@ -31047,7 +31478,7 @@ const LISTEN_TIMEOUT$1 = 120000;
|
|
|
31047
31478
|
* @param backtest - Whether running in backtest mode
|
|
31048
31479
|
* @returns Unique string key for memoization
|
|
31049
31480
|
*/
|
|
31050
|
-
const CREATE_KEY_FN$
|
|
31481
|
+
const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31051
31482
|
const parts = [symbol, strategyName, exchangeName];
|
|
31052
31483
|
if (frameName)
|
|
31053
31484
|
parts.push(frameName);
|
|
@@ -31090,7 +31521,7 @@ class PriceMetaService {
|
|
|
31090
31521
|
* Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
|
|
31091
31522
|
* Instances are cached until clear() is called.
|
|
31092
31523
|
*/
|
|
31093
|
-
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31524
|
+
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
|
|
31094
31525
|
/**
|
|
31095
31526
|
* Returns the current market price for the given symbol and context.
|
|
31096
31527
|
*
|
|
@@ -31119,10 +31550,10 @@ class PriceMetaService {
|
|
|
31119
31550
|
if (source.data) {
|
|
31120
31551
|
return source.data;
|
|
31121
31552
|
}
|
|
31122
|
-
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$
|
|
31553
|
+
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
31123
31554
|
const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
|
|
31124
31555
|
if (typeof currentPrice === "symbol") {
|
|
31125
|
-
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$
|
|
31556
|
+
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31126
31557
|
}
|
|
31127
31558
|
return currentPrice;
|
|
31128
31559
|
};
|
|
@@ -31164,7 +31595,7 @@ class PriceMetaService {
|
|
|
31164
31595
|
this.getSource.clear();
|
|
31165
31596
|
return;
|
|
31166
31597
|
}
|
|
31167
|
-
const key = CREATE_KEY_FN$
|
|
31598
|
+
const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31168
31599
|
this.getSource.clear(key);
|
|
31169
31600
|
};
|
|
31170
31601
|
}
|
|
@@ -31182,7 +31613,7 @@ const LISTEN_TIMEOUT = 120000;
|
|
|
31182
31613
|
* @param backtest - Whether running in backtest mode
|
|
31183
31614
|
* @returns Unique string key for memoization
|
|
31184
31615
|
*/
|
|
31185
|
-
const CREATE_KEY_FN$
|
|
31616
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31186
31617
|
const parts = [symbol, strategyName, exchangeName];
|
|
31187
31618
|
if (frameName)
|
|
31188
31619
|
parts.push(frameName);
|
|
@@ -31225,7 +31656,7 @@ class TimeMetaService {
|
|
|
31225
31656
|
* Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
|
|
31226
31657
|
* Instances are cached until clear() is called.
|
|
31227
31658
|
*/
|
|
31228
|
-
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31659
|
+
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
|
|
31229
31660
|
/**
|
|
31230
31661
|
* Returns the current candle timestamp (in milliseconds) for the given symbol and context.
|
|
31231
31662
|
*
|
|
@@ -31253,10 +31684,10 @@ class TimeMetaService {
|
|
|
31253
31684
|
if (source.data) {
|
|
31254
31685
|
return source.data;
|
|
31255
31686
|
}
|
|
31256
|
-
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$
|
|
31687
|
+
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
31257
31688
|
const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
|
|
31258
31689
|
if (typeof timestamp === "symbol") {
|
|
31259
|
-
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$
|
|
31690
|
+
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31260
31691
|
}
|
|
31261
31692
|
return timestamp;
|
|
31262
31693
|
};
|
|
@@ -31298,7 +31729,7 @@ class TimeMetaService {
|
|
|
31298
31729
|
this.getSource.clear();
|
|
31299
31730
|
return;
|
|
31300
31731
|
}
|
|
31301
|
-
const key = CREATE_KEY_FN$
|
|
31732
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31302
31733
|
this.getSource.clear(key);
|
|
31303
31734
|
};
|
|
31304
31735
|
}
|
|
@@ -31394,7 +31825,7 @@ class MaxDrawdownReportService {
|
|
|
31394
31825
|
/**
|
|
31395
31826
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
31396
31827
|
*/
|
|
31397
|
-
const CREATE_KEY_FN$
|
|
31828
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31398
31829
|
const parts = [symbol, strategyName, exchangeName];
|
|
31399
31830
|
if (frameName)
|
|
31400
31831
|
parts.push(frameName);
|
|
@@ -31518,7 +31949,7 @@ class ReportStorage {
|
|
|
31518
31949
|
class MaxDrawdownMarkdownService {
|
|
31519
31950
|
constructor() {
|
|
31520
31951
|
this.loggerService = inject(TYPES.loggerService);
|
|
31521
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31952
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
|
|
31522
31953
|
/**
|
|
31523
31954
|
* Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
|
|
31524
31955
|
* events. Protected against multiple subscriptions via `singleshot`.
|
|
@@ -31597,7 +32028,7 @@ class MaxDrawdownMarkdownService {
|
|
|
31597
32028
|
this.clear = async (payload) => {
|
|
31598
32029
|
this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
|
|
31599
32030
|
if (payload) {
|
|
31600
|
-
const key = CREATE_KEY_FN$
|
|
32031
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31601
32032
|
this.getStorage.clear(key);
|
|
31602
32033
|
}
|
|
31603
32034
|
else {
|
|
@@ -31851,7 +32282,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
|
31851
32282
|
const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
|
|
31852
32283
|
const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
|
|
31853
32284
|
const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
|
|
31854
|
-
const MS_PER_MINUTE$
|
|
32285
|
+
const MS_PER_MINUTE$3 = 60000;
|
|
31855
32286
|
/**
|
|
31856
32287
|
* Gets current timestamp from execution context if available.
|
|
31857
32288
|
* Returns current Date() if no execution context exists (non-trading GUI).
|
|
@@ -31918,7 +32349,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
|
|
|
31918
32349
|
const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
|
|
31919
32350
|
throw new Error(`getAggregatedTrades is not implemented for this exchange`);
|
|
31920
32351
|
};
|
|
31921
|
-
const INTERVAL_MINUTES$
|
|
32352
|
+
const INTERVAL_MINUTES$3 = {
|
|
31922
32353
|
"1m": 1,
|
|
31923
32354
|
"3m": 3,
|
|
31924
32355
|
"5m": 5,
|
|
@@ -31950,7 +32381,7 @@ const INTERVAL_MINUTES$2 = {
|
|
|
31950
32381
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
31951
32382
|
*/
|
|
31952
32383
|
const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
|
|
31953
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
32384
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
|
|
31954
32385
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
31955
32386
|
};
|
|
31956
32387
|
/**
|
|
@@ -32090,11 +32521,11 @@ class ExchangeInstance {
|
|
|
32090
32521
|
limit,
|
|
32091
32522
|
});
|
|
32092
32523
|
const getCandles = this._methods.getCandles;
|
|
32093
|
-
const step = INTERVAL_MINUTES$
|
|
32524
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
32094
32525
|
if (!step) {
|
|
32095
32526
|
throw new Error(`ExchangeInstance unknown interval=${interval}`);
|
|
32096
32527
|
}
|
|
32097
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32528
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
32098
32529
|
// Align when down to interval boundary
|
|
32099
32530
|
const when = await GET_TIMESTAMP_FN();
|
|
32100
32531
|
const whenTimestamp = when.getTime();
|
|
@@ -32279,7 +32710,7 @@ class ExchangeInstance {
|
|
|
32279
32710
|
const when = await GET_TIMESTAMP_FN();
|
|
32280
32711
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
32281
32712
|
const to = new Date(alignedTo);
|
|
32282
|
-
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
32713
|
+
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
|
|
32283
32714
|
const isBacktest = await GET_BACKTEST_FN();
|
|
32284
32715
|
return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
|
|
32285
32716
|
};
|
|
@@ -32311,7 +32742,7 @@ class ExchangeInstance {
|
|
|
32311
32742
|
const when = await GET_TIMESTAMP_FN();
|
|
32312
32743
|
// Align to 1-minute boundary to prevent look-ahead bias
|
|
32313
32744
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
|
|
32314
|
-
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$
|
|
32745
|
+
const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$3 - MS_PER_MINUTE$3;
|
|
32315
32746
|
const isBacktest = await GET_BACKTEST_FN();
|
|
32316
32747
|
// No limit: fetch a single window and return as-is
|
|
32317
32748
|
if (limit === undefined) {
|
|
@@ -32374,11 +32805,11 @@ class ExchangeInstance {
|
|
|
32374
32805
|
sDate,
|
|
32375
32806
|
eDate,
|
|
32376
32807
|
});
|
|
32377
|
-
const step = INTERVAL_MINUTES$
|
|
32808
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
32378
32809
|
if (!step) {
|
|
32379
32810
|
throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
|
|
32380
32811
|
}
|
|
32381
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
32812
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
32382
32813
|
const when = await GET_TIMESTAMP_FN();
|
|
32383
32814
|
const nowTimestamp = when.getTime();
|
|
32384
32815
|
const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
|
|
@@ -32668,8 +33099,8 @@ const Exchange = new ExchangeUtils();
|
|
|
32668
33099
|
|
|
32669
33100
|
const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
|
|
32670
33101
|
const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
|
|
32671
|
-
const MS_PER_MINUTE$
|
|
32672
|
-
const INTERVAL_MINUTES$
|
|
33102
|
+
const MS_PER_MINUTE$2 = 60000;
|
|
33103
|
+
const INTERVAL_MINUTES$2 = {
|
|
32673
33104
|
"1m": 1,
|
|
32674
33105
|
"3m": 3,
|
|
32675
33106
|
"5m": 5,
|
|
@@ -32684,7 +33115,7 @@ const INTERVAL_MINUTES$1 = {
|
|
|
32684
33115
|
"1w": 10080,
|
|
32685
33116
|
};
|
|
32686
33117
|
const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
|
|
32687
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
33118
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
|
|
32688
33119
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
32689
33120
|
};
|
|
32690
33121
|
const BAR_LENGTH = 30;
|
|
@@ -32709,11 +33140,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
|
|
|
32709
33140
|
async function checkCandles(params) {
|
|
32710
33141
|
const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
|
|
32711
33142
|
backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
|
|
32712
|
-
const step = INTERVAL_MINUTES$
|
|
33143
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
32713
33144
|
if (!step) {
|
|
32714
33145
|
throw new Error(`checkCandles: unsupported interval=${interval}`);
|
|
32715
33146
|
}
|
|
32716
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
33147
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
32717
33148
|
const dir = path.join(baseDir, exchangeName, symbol, interval);
|
|
32718
33149
|
const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
32719
33150
|
const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -32783,11 +33214,11 @@ async function warmCandles(params) {
|
|
|
32783
33214
|
from,
|
|
32784
33215
|
to,
|
|
32785
33216
|
});
|
|
32786
|
-
const step = INTERVAL_MINUTES$
|
|
33217
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
32787
33218
|
if (!step) {
|
|
32788
33219
|
throw new Error(`warmCandles: unsupported interval=${interval}`);
|
|
32789
33220
|
}
|
|
32790
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
33221
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
32791
33222
|
const instance = new ExchangeInstance(exchangeName);
|
|
32792
33223
|
const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
32793
33224
|
const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -34726,6 +35157,10 @@ const GET_POSITION_MAX_DRAWDOWN_PRICE_METHOD_NAME = "strategy.getPositionMaxDraw
|
|
|
34726
35157
|
const GET_POSITION_MAX_DRAWDOWN_TIMESTAMP_METHOD_NAME = "strategy.getPositionMaxDrawdownTimestamp";
|
|
34727
35158
|
const GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlPercentage";
|
|
34728
35159
|
const GET_POSITION_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlCost";
|
|
35160
|
+
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlPercentage";
|
|
35161
|
+
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
|
|
35162
|
+
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
|
|
35163
|
+
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
|
|
34729
35164
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
34730
35165
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
34731
35166
|
const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
|
|
@@ -36360,6 +36795,122 @@ async function getPositionMaxDrawdownPnlCost(symbol) {
|
|
|
36360
36795
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36361
36796
|
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36362
36797
|
}
|
|
36798
|
+
/**
|
|
36799
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
36800
|
+
*
|
|
36801
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
36802
|
+
* Returns null if no pending signal exists.
|
|
36803
|
+
*
|
|
36804
|
+
* @param symbol - Trading pair symbol
|
|
36805
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
36806
|
+
*
|
|
36807
|
+
* @example
|
|
36808
|
+
* ```typescript
|
|
36809
|
+
* import { getPositionHighestProfitDistancePnlPercentage } from "backtest-kit";
|
|
36810
|
+
*
|
|
36811
|
+
* const dist = await getPositionHighestProfitDistancePnlPercentage("BTCUSDT");
|
|
36812
|
+
* // e.g. 1.5 (gave back 1.5% from peak)
|
|
36813
|
+
* ```
|
|
36814
|
+
*/
|
|
36815
|
+
async function getPositionHighestProfitDistancePnlPercentage(symbol) {
|
|
36816
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
36817
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36818
|
+
throw new Error("getPositionHighestProfitDistancePnlPercentage requires an execution context");
|
|
36819
|
+
}
|
|
36820
|
+
if (!MethodContextService.hasContext()) {
|
|
36821
|
+
throw new Error("getPositionHighestProfitDistancePnlPercentage requires a method context");
|
|
36822
|
+
}
|
|
36823
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36824
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36825
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36826
|
+
}
|
|
36827
|
+
/**
|
|
36828
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
36829
|
+
*
|
|
36830
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
36831
|
+
* Returns null if no pending signal exists.
|
|
36832
|
+
*
|
|
36833
|
+
* @param symbol - Trading pair symbol
|
|
36834
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
36835
|
+
*
|
|
36836
|
+
* @example
|
|
36837
|
+
* ```typescript
|
|
36838
|
+
* import { getPositionHighestProfitDistancePnlCost } from "backtest-kit";
|
|
36839
|
+
*
|
|
36840
|
+
* const dist = await getPositionHighestProfitDistancePnlCost("BTCUSDT");
|
|
36841
|
+
* // e.g. 3.2 (gave back $3.2 from peak)
|
|
36842
|
+
* ```
|
|
36843
|
+
*/
|
|
36844
|
+
async function getPositionHighestProfitDistancePnlCost(symbol) {
|
|
36845
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
|
|
36846
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36847
|
+
throw new Error("getPositionHighestProfitDistancePnlCost requires an execution context");
|
|
36848
|
+
}
|
|
36849
|
+
if (!MethodContextService.hasContext()) {
|
|
36850
|
+
throw new Error("getPositionHighestProfitDistancePnlCost requires a method context");
|
|
36851
|
+
}
|
|
36852
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36853
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36854
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36855
|
+
}
|
|
36856
|
+
/**
|
|
36857
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
36858
|
+
*
|
|
36859
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
36860
|
+
* Returns null if no pending signal exists.
|
|
36861
|
+
*
|
|
36862
|
+
* @param symbol - Trading pair symbol
|
|
36863
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
36864
|
+
*
|
|
36865
|
+
* @example
|
|
36866
|
+
* ```typescript
|
|
36867
|
+
* import { getPositionHighestMaxDrawdownPnlPercentage } from "backtest-kit";
|
|
36868
|
+
*
|
|
36869
|
+
* const dist = await getPositionHighestMaxDrawdownPnlPercentage("BTCUSDT");
|
|
36870
|
+
* // e.g. 2.1 (recovered 2.1% from trough)
|
|
36871
|
+
* ```
|
|
36872
|
+
*/
|
|
36873
|
+
async function getPositionHighestMaxDrawdownPnlPercentage(symbol) {
|
|
36874
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
36875
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36876
|
+
throw new Error("getPositionHighestMaxDrawdownPnlPercentage requires an execution context");
|
|
36877
|
+
}
|
|
36878
|
+
if (!MethodContextService.hasContext()) {
|
|
36879
|
+
throw new Error("getPositionHighestMaxDrawdownPnlPercentage requires a method context");
|
|
36880
|
+
}
|
|
36881
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36882
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36883
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36884
|
+
}
|
|
36885
|
+
/**
|
|
36886
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
36887
|
+
*
|
|
36888
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
36889
|
+
* Returns null if no pending signal exists.
|
|
36890
|
+
*
|
|
36891
|
+
* @param symbol - Trading pair symbol
|
|
36892
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
36893
|
+
*
|
|
36894
|
+
* @example
|
|
36895
|
+
* ```typescript
|
|
36896
|
+
* import { getPositionHighestMaxDrawdownPnlCost } from "backtest-kit";
|
|
36897
|
+
*
|
|
36898
|
+
* const dist = await getPositionHighestMaxDrawdownPnlCost("BTCUSDT");
|
|
36899
|
+
* // e.g. 4.8 (recovered $4.8 from trough)
|
|
36900
|
+
* ```
|
|
36901
|
+
*/
|
|
36902
|
+
async function getPositionHighestMaxDrawdownPnlCost(symbol) {
|
|
36903
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME, { symbol });
|
|
36904
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36905
|
+
throw new Error("getPositionHighestMaxDrawdownPnlCost requires an execution context");
|
|
36906
|
+
}
|
|
36907
|
+
if (!MethodContextService.hasContext()) {
|
|
36908
|
+
throw new Error("getPositionHighestMaxDrawdownPnlCost requires a method context");
|
|
36909
|
+
}
|
|
36910
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36911
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36912
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36913
|
+
}
|
|
36363
36914
|
/**
|
|
36364
36915
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
36365
36916
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -38029,6 +38580,10 @@ const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "BacktestUtils.getP
|
|
|
38029
38580
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "BacktestUtils.getPositionMaxDrawdownTimestamp";
|
|
38030
38581
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionMaxDrawdownPnlPercentage";
|
|
38031
38582
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionMaxDrawdownPnlCost";
|
|
38583
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
38584
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
|
|
38585
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
38586
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
38032
38587
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
38033
38588
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
38034
38589
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -39298,6 +39853,118 @@ class BacktestUtils {
|
|
|
39298
39853
|
}
|
|
39299
39854
|
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(true, symbol, context);
|
|
39300
39855
|
};
|
|
39856
|
+
/**
|
|
39857
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
39858
|
+
*
|
|
39859
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
39860
|
+
* Returns null if no pending signal exists.
|
|
39861
|
+
*
|
|
39862
|
+
* @param symbol - Trading pair symbol
|
|
39863
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39864
|
+
* @returns drawdown distance in PnL% (≥ 0) or null if no active position
|
|
39865
|
+
*/
|
|
39866
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context) => {
|
|
39867
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, {
|
|
39868
|
+
symbol,
|
|
39869
|
+
context,
|
|
39870
|
+
});
|
|
39871
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39872
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39873
|
+
{
|
|
39874
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39875
|
+
riskName &&
|
|
39876
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39877
|
+
riskList &&
|
|
39878
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
39879
|
+
actions &&
|
|
39880
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
39881
|
+
}
|
|
39882
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(true, symbol, context);
|
|
39883
|
+
};
|
|
39884
|
+
/**
|
|
39885
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
39886
|
+
*
|
|
39887
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
39888
|
+
* Returns null if no pending signal exists.
|
|
39889
|
+
*
|
|
39890
|
+
* @param symbol - Trading pair symbol
|
|
39891
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39892
|
+
* @returns drawdown distance in PnL cost (≥ 0) or null if no active position
|
|
39893
|
+
*/
|
|
39894
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context) => {
|
|
39895
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, {
|
|
39896
|
+
symbol,
|
|
39897
|
+
context,
|
|
39898
|
+
});
|
|
39899
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39900
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39901
|
+
{
|
|
39902
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39903
|
+
riskName &&
|
|
39904
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39905
|
+
riskList &&
|
|
39906
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
39907
|
+
actions &&
|
|
39908
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
39909
|
+
}
|
|
39910
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(true, symbol, context);
|
|
39911
|
+
};
|
|
39912
|
+
/**
|
|
39913
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
39914
|
+
*
|
|
39915
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
39916
|
+
* Returns null if no pending signal exists.
|
|
39917
|
+
*
|
|
39918
|
+
* @param symbol - Trading pair symbol
|
|
39919
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39920
|
+
* @returns recovery distance in PnL% (≥ 0) or null if no active position
|
|
39921
|
+
*/
|
|
39922
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context) => {
|
|
39923
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, {
|
|
39924
|
+
symbol,
|
|
39925
|
+
context,
|
|
39926
|
+
});
|
|
39927
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39928
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39929
|
+
{
|
|
39930
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39931
|
+
riskName &&
|
|
39932
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39933
|
+
riskList &&
|
|
39934
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
39935
|
+
actions &&
|
|
39936
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
39937
|
+
}
|
|
39938
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(true, symbol, context);
|
|
39939
|
+
};
|
|
39940
|
+
/**
|
|
39941
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
39942
|
+
*
|
|
39943
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
39944
|
+
* Returns null if no pending signal exists.
|
|
39945
|
+
*
|
|
39946
|
+
* @param symbol - Trading pair symbol
|
|
39947
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39948
|
+
* @returns recovery distance in PnL cost (≥ 0) or null if no active position
|
|
39949
|
+
*/
|
|
39950
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context) => {
|
|
39951
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, {
|
|
39952
|
+
symbol,
|
|
39953
|
+
context,
|
|
39954
|
+
});
|
|
39955
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39956
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39957
|
+
{
|
|
39958
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39959
|
+
riskName &&
|
|
39960
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39961
|
+
riskList &&
|
|
39962
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
39963
|
+
actions &&
|
|
39964
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
39965
|
+
}
|
|
39966
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
|
|
39967
|
+
};
|
|
39301
39968
|
/**
|
|
39302
39969
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
39303
39970
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -40423,6 +41090,10 @@ const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "LiveUtils.getPositionM
|
|
|
40423
41090
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "LiveUtils.getPositionMaxDrawdownTimestamp";
|
|
40424
41091
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionMaxDrawdownPnlPercentage";
|
|
40425
41092
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionMaxDrawdownPnlCost";
|
|
41093
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
41094
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
|
|
41095
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
41096
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
40426
41097
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
40427
41098
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
40428
41099
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -41819,6 +42490,134 @@ class LiveUtils {
|
|
|
41819
42490
|
frameName: "",
|
|
41820
42491
|
});
|
|
41821
42492
|
};
|
|
42493
|
+
/**
|
|
42494
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
42495
|
+
*
|
|
42496
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
42497
|
+
* Returns null if no pending signal exists.
|
|
42498
|
+
*
|
|
42499
|
+
* @param symbol - Trading pair symbol
|
|
42500
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42501
|
+
* @returns drawdown distance in PnL% (≥ 0) or null if no active position
|
|
42502
|
+
*/
|
|
42503
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context) => {
|
|
42504
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, {
|
|
42505
|
+
symbol,
|
|
42506
|
+
context,
|
|
42507
|
+
});
|
|
42508
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42509
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42510
|
+
{
|
|
42511
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42512
|
+
riskName &&
|
|
42513
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42514
|
+
riskList &&
|
|
42515
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
42516
|
+
actions &&
|
|
42517
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
42518
|
+
}
|
|
42519
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(false, symbol, {
|
|
42520
|
+
strategyName: context.strategyName,
|
|
42521
|
+
exchangeName: context.exchangeName,
|
|
42522
|
+
frameName: "",
|
|
42523
|
+
});
|
|
42524
|
+
};
|
|
42525
|
+
/**
|
|
42526
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
42527
|
+
*
|
|
42528
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
42529
|
+
* Returns null if no pending signal exists.
|
|
42530
|
+
*
|
|
42531
|
+
* @param symbol - Trading pair symbol
|
|
42532
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42533
|
+
* @returns drawdown distance in PnL cost (≥ 0) or null if no active position
|
|
42534
|
+
*/
|
|
42535
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context) => {
|
|
42536
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, {
|
|
42537
|
+
symbol,
|
|
42538
|
+
context,
|
|
42539
|
+
});
|
|
42540
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42541
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42542
|
+
{
|
|
42543
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42544
|
+
riskName &&
|
|
42545
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42546
|
+
riskList &&
|
|
42547
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
42548
|
+
actions &&
|
|
42549
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
42550
|
+
}
|
|
42551
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(false, symbol, {
|
|
42552
|
+
strategyName: context.strategyName,
|
|
42553
|
+
exchangeName: context.exchangeName,
|
|
42554
|
+
frameName: "",
|
|
42555
|
+
});
|
|
42556
|
+
};
|
|
42557
|
+
/**
|
|
42558
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
42559
|
+
*
|
|
42560
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
42561
|
+
* Returns null if no pending signal exists.
|
|
42562
|
+
*
|
|
42563
|
+
* @param symbol - Trading pair symbol
|
|
42564
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42565
|
+
* @returns recovery distance in PnL% (≥ 0) or null if no active position
|
|
42566
|
+
*/
|
|
42567
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context) => {
|
|
42568
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, {
|
|
42569
|
+
symbol,
|
|
42570
|
+
context,
|
|
42571
|
+
});
|
|
42572
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42573
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42574
|
+
{
|
|
42575
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42576
|
+
riskName &&
|
|
42577
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42578
|
+
riskList &&
|
|
42579
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
42580
|
+
actions &&
|
|
42581
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
42582
|
+
}
|
|
42583
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(false, symbol, {
|
|
42584
|
+
strategyName: context.strategyName,
|
|
42585
|
+
exchangeName: context.exchangeName,
|
|
42586
|
+
frameName: "",
|
|
42587
|
+
});
|
|
42588
|
+
};
|
|
42589
|
+
/**
|
|
42590
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
42591
|
+
*
|
|
42592
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
42593
|
+
* Returns null if no pending signal exists.
|
|
42594
|
+
*
|
|
42595
|
+
* @param symbol - Trading pair symbol
|
|
42596
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42597
|
+
* @returns recovery distance in PnL cost (≥ 0) or null if no active position
|
|
42598
|
+
*/
|
|
42599
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context) => {
|
|
42600
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, {
|
|
42601
|
+
symbol,
|
|
42602
|
+
context,
|
|
42603
|
+
});
|
|
42604
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42605
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42606
|
+
{
|
|
42607
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42608
|
+
riskName &&
|
|
42609
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42610
|
+
riskList &&
|
|
42611
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
42612
|
+
actions &&
|
|
42613
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
42614
|
+
}
|
|
42615
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(false, symbol, {
|
|
42616
|
+
strategyName: context.strategyName,
|
|
42617
|
+
exchangeName: context.exchangeName,
|
|
42618
|
+
frameName: "",
|
|
42619
|
+
});
|
|
42620
|
+
};
|
|
41822
42621
|
/**
|
|
41823
42622
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
41824
42623
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -44196,7 +44995,7 @@ const createSearchIndex = () => {
|
|
|
44196
44995
|
return { upsert, remove, list, search, read };
|
|
44197
44996
|
};
|
|
44198
44997
|
|
|
44199
|
-
const CREATE_KEY_FN$
|
|
44998
|
+
const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}-${bucketName}`;
|
|
44200
44999
|
const LIST_MEMORY_FN = ({ id, content }) => ({
|
|
44201
45000
|
memoryId: id,
|
|
44202
45001
|
content: content,
|
|
@@ -44518,7 +45317,7 @@ class MemoryDummyInstance {
|
|
|
44518
45317
|
class MemoryAdapter {
|
|
44519
45318
|
constructor() {
|
|
44520
45319
|
this.MemoryFactory = MemoryPersistInstance;
|
|
44521
|
-
this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$
|
|
45320
|
+
this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
|
|
44522
45321
|
/**
|
|
44523
45322
|
* Activates the adapter by subscribing to signal lifecycle events.
|
|
44524
45323
|
* Clears memoized instances for a signalId when it is cancelled or closed,
|
|
@@ -44529,7 +45328,7 @@ class MemoryAdapter {
|
|
|
44529
45328
|
this.enable = functoolsKit.singleshot(() => {
|
|
44530
45329
|
backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_ENABLE);
|
|
44531
45330
|
const handleDispose = (signalId) => {
|
|
44532
|
-
const prefix = CREATE_KEY_FN$
|
|
45331
|
+
const prefix = CREATE_KEY_FN$4(signalId, "");
|
|
44533
45332
|
for (const key of this.getInstance.keys()) {
|
|
44534
45333
|
if (key.startsWith(prefix)) {
|
|
44535
45334
|
const instance = this.getInstance.get(key);
|
|
@@ -44574,7 +45373,7 @@ class MemoryAdapter {
|
|
|
44574
45373
|
bucketName: dto.bucketName,
|
|
44575
45374
|
memoryId: dto.memoryId,
|
|
44576
45375
|
});
|
|
44577
|
-
const key = CREATE_KEY_FN$
|
|
45376
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44578
45377
|
const isInitial = !this.getInstance.has(key);
|
|
44579
45378
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44580
45379
|
await instance.waitForInit(isInitial);
|
|
@@ -44596,7 +45395,7 @@ class MemoryAdapter {
|
|
|
44596
45395
|
bucketName: dto.bucketName,
|
|
44597
45396
|
query: dto.query,
|
|
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);
|
|
@@ -44616,7 +45415,7 @@ class MemoryAdapter {
|
|
|
44616
45415
|
signalId: dto.signalId,
|
|
44617
45416
|
bucketName: dto.bucketName,
|
|
44618
45417
|
});
|
|
44619
|
-
const key = CREATE_KEY_FN$
|
|
45418
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44620
45419
|
const isInitial = !this.getInstance.has(key);
|
|
44621
45420
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44622
45421
|
await instance.waitForInit(isInitial);
|
|
@@ -44637,7 +45436,7 @@ class MemoryAdapter {
|
|
|
44637
45436
|
bucketName: dto.bucketName,
|
|
44638
45437
|
memoryId: dto.memoryId,
|
|
44639
45438
|
});
|
|
44640
|
-
const key = CREATE_KEY_FN$
|
|
45439
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44641
45440
|
const isInitial = !this.getInstance.has(key);
|
|
44642
45441
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44643
45442
|
await instance.waitForInit(isInitial);
|
|
@@ -44660,7 +45459,7 @@ class MemoryAdapter {
|
|
|
44660
45459
|
bucketName: dto.bucketName,
|
|
44661
45460
|
memoryId: dto.memoryId,
|
|
44662
45461
|
});
|
|
44663
|
-
const key = CREATE_KEY_FN$
|
|
45462
|
+
const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
|
|
44664
45463
|
const isInitial = !this.getInstance.has(key);
|
|
44665
45464
|
const instance = this.getInstance(dto.signalId, dto.bucketName);
|
|
44666
45465
|
await instance.waitForInit(isInitial);
|
|
@@ -44952,7 +45751,7 @@ async function removeMemory(dto) {
|
|
|
44952
45751
|
});
|
|
44953
45752
|
}
|
|
44954
45753
|
|
|
44955
|
-
const CREATE_KEY_FN$
|
|
45754
|
+
const CREATE_KEY_FN$3 = (signalId, bucketName) => `${signalId}-${bucketName}`;
|
|
44956
45755
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_AGENT = "DumpMemoryInstance.dumpAgentAnswer";
|
|
44957
45756
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_RECORD = "DumpMemoryInstance.dumpRecord";
|
|
44958
45757
|
const DUMP_MEMORY_INSTANCE_METHOD_NAME_TABLE = "DumpMemoryInstance.dumpTable";
|
|
@@ -45572,7 +46371,7 @@ class DumpDummyInstance {
|
|
|
45572
46371
|
class DumpAdapter {
|
|
45573
46372
|
constructor() {
|
|
45574
46373
|
this.DumpFactory = DumpMarkdownInstance;
|
|
45575
|
-
this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$
|
|
46374
|
+
this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.DumpFactory, [signalId, bucketName]));
|
|
45576
46375
|
/**
|
|
45577
46376
|
* Activates the adapter by subscribing to signal lifecycle events.
|
|
45578
46377
|
* Clears memoized instances for a signalId when it is cancelled or closed,
|
|
@@ -45583,7 +46382,7 @@ class DumpAdapter {
|
|
|
45583
46382
|
this.enable = functoolsKit.singleshot(() => {
|
|
45584
46383
|
backtest.loggerService.info(DUMP_ADAPTER_METHOD_NAME_ENABLE);
|
|
45585
46384
|
const handleDispose = (signalId) => {
|
|
45586
|
-
const prefix = CREATE_KEY_FN$
|
|
46385
|
+
const prefix = CREATE_KEY_FN$3(signalId, "");
|
|
45587
46386
|
for (const key of this.getInstance.keys()) {
|
|
45588
46387
|
if (key.startsWith(prefix)) {
|
|
45589
46388
|
const instance = this.getInstance.get(key);
|
|
@@ -48517,6 +49316,49 @@ PositionSizeUtils.atrBased = async (symbol, accountBalance, priceOpen, atr, cont
|
|
|
48517
49316
|
};
|
|
48518
49317
|
const PositionSize = PositionSizeUtils;
|
|
48519
49318
|
|
|
49319
|
+
const METHOD_NAME_MOONBAG = "Position.moonbag";
|
|
49320
|
+
const METHOD_NAME_BRACKET = "Position.bracket";
|
|
49321
|
+
/**
|
|
49322
|
+
* Utilities for calculating take profit and stop loss price levels.
|
|
49323
|
+
* Automatically inverts direction based on position type (long/short).
|
|
49324
|
+
*/
|
|
49325
|
+
class Position {
|
|
49326
|
+
}
|
|
49327
|
+
/**
|
|
49328
|
+
* Calculates levels for the "moonbag" strategy — fixed TP at 50% from the current price.
|
|
49329
|
+
* @param dto.position - position type: "long" or "short"
|
|
49330
|
+
* @param dto.currentPrice - current asset price
|
|
49331
|
+
* @param dto.percentStopLoss - stop loss percentage from 0 to 100
|
|
49332
|
+
* @returns priceTakeProfit and priceStopLoss in fiat
|
|
49333
|
+
*/
|
|
49334
|
+
Position.moonbag = (dto) => {
|
|
49335
|
+
backtest.loggerService.log(METHOD_NAME_MOONBAG, { dto });
|
|
49336
|
+
const percentTakeProfit = 50;
|
|
49337
|
+
const sign = dto.position === "long" ? 1 : -1;
|
|
49338
|
+
return {
|
|
49339
|
+
position: dto.position,
|
|
49340
|
+
priceTakeProfit: dto.currentPrice * (1 + sign * percentTakeProfit / 100),
|
|
49341
|
+
priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
|
|
49342
|
+
};
|
|
49343
|
+
};
|
|
49344
|
+
/**
|
|
49345
|
+
* Calculates levels for a bracket order with custom TP and SL.
|
|
49346
|
+
* @param dto.position - position type: "long" or "short"
|
|
49347
|
+
* @param dto.currentPrice - current asset price
|
|
49348
|
+
* @param dto.percentStopLoss - stop loss percentage from 0 to 100
|
|
49349
|
+
* @param dto.percentTakeProfit - take profit percentage from 0 to 100
|
|
49350
|
+
* @returns priceTakeProfit and priceStopLoss in fiat
|
|
49351
|
+
*/
|
|
49352
|
+
Position.bracket = (dto) => {
|
|
49353
|
+
backtest.loggerService.log(METHOD_NAME_BRACKET, { dto });
|
|
49354
|
+
const sign = dto.position === "long" ? 1 : -1;
|
|
49355
|
+
return {
|
|
49356
|
+
position: dto.position,
|
|
49357
|
+
priceTakeProfit: dto.currentPrice * (1 + sign * dto.percentTakeProfit / 100),
|
|
49358
|
+
priceStopLoss: dto.currentPrice * (1 - sign * dto.percentStopLoss / 100),
|
|
49359
|
+
};
|
|
49360
|
+
};
|
|
49361
|
+
|
|
48520
49362
|
const PARTIAL_METHOD_NAME_GET_DATA = "PartialUtils.getData";
|
|
48521
49363
|
const PARTIAL_METHOD_NAME_GET_REPORT = "PartialUtils.getReport";
|
|
48522
49364
|
const PARTIAL_METHOD_NAME_DUMP = "PartialUtils.dump";
|
|
@@ -50719,7 +51561,7 @@ const StorageBacktest = new StorageBacktestAdapter();
|
|
|
50719
51561
|
* Generates a unique key for notification identification.
|
|
50720
51562
|
* @returns Random string identifier
|
|
50721
51563
|
*/
|
|
50722
|
-
const CREATE_KEY_FN$
|
|
51564
|
+
const CREATE_KEY_FN$2 = () => functoolsKit.randomString();
|
|
50723
51565
|
/**
|
|
50724
51566
|
* Creates a notification model from signal tick result.
|
|
50725
51567
|
* Handles opened, closed, scheduled, and cancelled signal actions.
|
|
@@ -50730,7 +51572,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50730
51572
|
if (data.action === "opened") {
|
|
50731
51573
|
return {
|
|
50732
51574
|
type: "signal.opened",
|
|
50733
|
-
id: CREATE_KEY_FN$
|
|
51575
|
+
id: CREATE_KEY_FN$2(),
|
|
50734
51576
|
timestamp: data.signal.pendingAt,
|
|
50735
51577
|
backtest: data.backtest,
|
|
50736
51578
|
symbol: data.symbol,
|
|
@@ -50764,7 +51606,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50764
51606
|
const durationMin = Math.round(durationMs / 60000);
|
|
50765
51607
|
return {
|
|
50766
51608
|
type: "signal.closed",
|
|
50767
|
-
id: CREATE_KEY_FN$
|
|
51609
|
+
id: CREATE_KEY_FN$2(),
|
|
50768
51610
|
timestamp: data.closeTimestamp,
|
|
50769
51611
|
backtest: data.backtest,
|
|
50770
51612
|
symbol: data.symbol,
|
|
@@ -50798,7 +51640,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50798
51640
|
if (data.action === "scheduled") {
|
|
50799
51641
|
return {
|
|
50800
51642
|
type: "signal.scheduled",
|
|
50801
|
-
id: CREATE_KEY_FN$
|
|
51643
|
+
id: CREATE_KEY_FN$2(),
|
|
50802
51644
|
timestamp: data.signal.scheduledAt,
|
|
50803
51645
|
backtest: data.backtest,
|
|
50804
51646
|
symbol: data.symbol,
|
|
@@ -50831,7 +51673,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50831
51673
|
const durationMin = Math.round(durationMs / 60000);
|
|
50832
51674
|
return {
|
|
50833
51675
|
type: "signal.cancelled",
|
|
50834
|
-
id: CREATE_KEY_FN$
|
|
51676
|
+
id: CREATE_KEY_FN$2(),
|
|
50835
51677
|
timestamp: data.closeTimestamp,
|
|
50836
51678
|
backtest: data.backtest,
|
|
50837
51679
|
symbol: data.symbol,
|
|
@@ -50864,7 +51706,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
50864
51706
|
*/
|
|
50865
51707
|
const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
50866
51708
|
type: "partial_profit.available",
|
|
50867
|
-
id: CREATE_KEY_FN$
|
|
51709
|
+
id: CREATE_KEY_FN$2(),
|
|
50868
51710
|
timestamp: data.timestamp,
|
|
50869
51711
|
backtest: data.backtest,
|
|
50870
51712
|
symbol: data.symbol,
|
|
@@ -50899,7 +51741,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
|
50899
51741
|
*/
|
|
50900
51742
|
const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
50901
51743
|
type: "partial_loss.available",
|
|
50902
|
-
id: CREATE_KEY_FN$
|
|
51744
|
+
id: CREATE_KEY_FN$2(),
|
|
50903
51745
|
timestamp: data.timestamp,
|
|
50904
51746
|
backtest: data.backtest,
|
|
50905
51747
|
symbol: data.symbol,
|
|
@@ -50934,7 +51776,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
|
50934
51776
|
*/
|
|
50935
51777
|
const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
|
|
50936
51778
|
type: "breakeven.available",
|
|
50937
|
-
id: CREATE_KEY_FN$
|
|
51779
|
+
id: CREATE_KEY_FN$2(),
|
|
50938
51780
|
timestamp: data.timestamp,
|
|
50939
51781
|
backtest: data.backtest,
|
|
50940
51782
|
symbol: data.symbol,
|
|
@@ -50972,7 +51814,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
50972
51814
|
if (data.action === "partial-profit") {
|
|
50973
51815
|
return {
|
|
50974
51816
|
type: "partial_profit.commit",
|
|
50975
|
-
id: CREATE_KEY_FN$
|
|
51817
|
+
id: CREATE_KEY_FN$2(),
|
|
50976
51818
|
timestamp: data.timestamp,
|
|
50977
51819
|
backtest: data.backtest,
|
|
50978
51820
|
symbol: data.symbol,
|
|
@@ -51004,7 +51846,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51004
51846
|
if (data.action === "partial-loss") {
|
|
51005
51847
|
return {
|
|
51006
51848
|
type: "partial_loss.commit",
|
|
51007
|
-
id: CREATE_KEY_FN$
|
|
51849
|
+
id: CREATE_KEY_FN$2(),
|
|
51008
51850
|
timestamp: data.timestamp,
|
|
51009
51851
|
backtest: data.backtest,
|
|
51010
51852
|
symbol: data.symbol,
|
|
@@ -51036,7 +51878,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51036
51878
|
if (data.action === "breakeven") {
|
|
51037
51879
|
return {
|
|
51038
51880
|
type: "breakeven.commit",
|
|
51039
|
-
id: CREATE_KEY_FN$
|
|
51881
|
+
id: CREATE_KEY_FN$2(),
|
|
51040
51882
|
timestamp: data.timestamp,
|
|
51041
51883
|
backtest: data.backtest,
|
|
51042
51884
|
symbol: data.symbol,
|
|
@@ -51067,7 +51909,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51067
51909
|
if (data.action === "trailing-stop") {
|
|
51068
51910
|
return {
|
|
51069
51911
|
type: "trailing_stop.commit",
|
|
51070
|
-
id: CREATE_KEY_FN$
|
|
51912
|
+
id: CREATE_KEY_FN$2(),
|
|
51071
51913
|
timestamp: data.timestamp,
|
|
51072
51914
|
backtest: data.backtest,
|
|
51073
51915
|
symbol: data.symbol,
|
|
@@ -51099,7 +51941,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51099
51941
|
if (data.action === "trailing-take") {
|
|
51100
51942
|
return {
|
|
51101
51943
|
type: "trailing_take.commit",
|
|
51102
|
-
id: CREATE_KEY_FN$
|
|
51944
|
+
id: CREATE_KEY_FN$2(),
|
|
51103
51945
|
timestamp: data.timestamp,
|
|
51104
51946
|
backtest: data.backtest,
|
|
51105
51947
|
symbol: data.symbol,
|
|
@@ -51131,7 +51973,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51131
51973
|
if (data.action === "activate-scheduled") {
|
|
51132
51974
|
return {
|
|
51133
51975
|
type: "activate_scheduled.commit",
|
|
51134
|
-
id: CREATE_KEY_FN$
|
|
51976
|
+
id: CREATE_KEY_FN$2(),
|
|
51135
51977
|
timestamp: data.timestamp,
|
|
51136
51978
|
backtest: data.backtest,
|
|
51137
51979
|
symbol: data.symbol,
|
|
@@ -51163,7 +52005,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51163
52005
|
if (data.action === "average-buy") {
|
|
51164
52006
|
return {
|
|
51165
52007
|
type: "average_buy.commit",
|
|
51166
|
-
id: CREATE_KEY_FN$
|
|
52008
|
+
id: CREATE_KEY_FN$2(),
|
|
51167
52009
|
timestamp: data.timestamp,
|
|
51168
52010
|
backtest: data.backtest,
|
|
51169
52011
|
symbol: data.symbol,
|
|
@@ -51196,7 +52038,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51196
52038
|
if (data.action === "cancel-scheduled") {
|
|
51197
52039
|
return {
|
|
51198
52040
|
type: "cancel_scheduled.commit",
|
|
51199
|
-
id: CREATE_KEY_FN$
|
|
52041
|
+
id: CREATE_KEY_FN$2(),
|
|
51200
52042
|
timestamp: data.timestamp,
|
|
51201
52043
|
backtest: data.backtest,
|
|
51202
52044
|
symbol: data.symbol,
|
|
@@ -51219,7 +52061,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51219
52061
|
if (data.action === "close-pending") {
|
|
51220
52062
|
return {
|
|
51221
52063
|
type: "close_pending.commit",
|
|
51222
|
-
id: CREATE_KEY_FN$
|
|
52064
|
+
id: CREATE_KEY_FN$2(),
|
|
51223
52065
|
timestamp: data.timestamp,
|
|
51224
52066
|
backtest: data.backtest,
|
|
51225
52067
|
symbol: data.symbol,
|
|
@@ -51251,7 +52093,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51251
52093
|
if (data.action === "signal-open") {
|
|
51252
52094
|
return {
|
|
51253
52095
|
type: "signal_sync.open",
|
|
51254
|
-
id: CREATE_KEY_FN$
|
|
52096
|
+
id: CREATE_KEY_FN$2(),
|
|
51255
52097
|
timestamp: data.timestamp,
|
|
51256
52098
|
backtest: data.backtest,
|
|
51257
52099
|
symbol: data.symbol,
|
|
@@ -51283,7 +52125,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51283
52125
|
if (data.action === "signal-close") {
|
|
51284
52126
|
return {
|
|
51285
52127
|
type: "signal_sync.close",
|
|
51286
|
-
id: CREATE_KEY_FN$
|
|
52128
|
+
id: CREATE_KEY_FN$2(),
|
|
51287
52129
|
timestamp: data.timestamp,
|
|
51288
52130
|
backtest: data.backtest,
|
|
51289
52131
|
symbol: data.symbol,
|
|
@@ -51321,7 +52163,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51321
52163
|
*/
|
|
51322
52164
|
const CREATE_RISK_NOTIFICATION_FN = (data) => ({
|
|
51323
52165
|
type: "risk.rejection",
|
|
51324
|
-
id: CREATE_KEY_FN$
|
|
52166
|
+
id: CREATE_KEY_FN$2(),
|
|
51325
52167
|
timestamp: data.timestamp,
|
|
51326
52168
|
backtest: data.backtest,
|
|
51327
52169
|
symbol: data.symbol,
|
|
@@ -51347,7 +52189,7 @@ const CREATE_RISK_NOTIFICATION_FN = (data) => ({
|
|
|
51347
52189
|
*/
|
|
51348
52190
|
const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51349
52191
|
type: "error.info",
|
|
51350
|
-
id: CREATE_KEY_FN$
|
|
52192
|
+
id: CREATE_KEY_FN$2(),
|
|
51351
52193
|
error: functoolsKit.errorData(error),
|
|
51352
52194
|
message: functoolsKit.getErrorMessage(error),
|
|
51353
52195
|
backtest: false,
|
|
@@ -51359,7 +52201,7 @@ const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
|
|
|
51359
52201
|
*/
|
|
51360
52202
|
const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51361
52203
|
type: "error.critical",
|
|
51362
|
-
id: CREATE_KEY_FN$
|
|
52204
|
+
id: CREATE_KEY_FN$2(),
|
|
51363
52205
|
error: functoolsKit.errorData(error),
|
|
51364
52206
|
message: functoolsKit.getErrorMessage(error),
|
|
51365
52207
|
backtest: false,
|
|
@@ -51371,7 +52213,7 @@ const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
|
|
|
51371
52213
|
*/
|
|
51372
52214
|
const CREATE_VALIDATION_ERROR_NOTIFICATION_FN = (error) => ({
|
|
51373
52215
|
type: "error.validation",
|
|
51374
|
-
id: CREATE_KEY_FN$
|
|
52216
|
+
id: CREATE_KEY_FN$2(),
|
|
51375
52217
|
error: functoolsKit.errorData(error),
|
|
51376
52218
|
message: functoolsKit.getErrorMessage(error),
|
|
51377
52219
|
backtest: false,
|
|
@@ -52800,7 +53642,7 @@ const NotificationLive = new NotificationLiveAdapter();
|
|
|
52800
53642
|
*/
|
|
52801
53643
|
const NotificationBacktest = new NotificationBacktestAdapter();
|
|
52802
53644
|
|
|
52803
|
-
const CACHE_METHOD_NAME_RUN = "
|
|
53645
|
+
const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
|
|
52804
53646
|
const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
|
|
52805
53647
|
const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
|
|
52806
53648
|
const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
|
|
@@ -52809,8 +53651,8 @@ const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
|
|
|
52809
53651
|
const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
|
|
52810
53652
|
const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
|
|
52811
53653
|
const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
|
|
52812
|
-
const MS_PER_MINUTE = 60000;
|
|
52813
|
-
const INTERVAL_MINUTES = {
|
|
53654
|
+
const MS_PER_MINUTE$1 = 60000;
|
|
53655
|
+
const INTERVAL_MINUTES$1 = {
|
|
52814
53656
|
"1m": 1,
|
|
52815
53657
|
"3m": 3,
|
|
52816
53658
|
"5m": 5,
|
|
@@ -52844,12 +53686,12 @@ const INTERVAL_MINUTES = {
|
|
|
52844
53686
|
* // Returns timestamp for 2025-10-01T01:00:00Z
|
|
52845
53687
|
* ```
|
|
52846
53688
|
*/
|
|
52847
|
-
const align = (timestamp, interval) => {
|
|
52848
|
-
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
53689
|
+
const align$1 = (timestamp, interval) => {
|
|
53690
|
+
const intervalMinutes = INTERVAL_MINUTES$1[interval];
|
|
52849
53691
|
if (!intervalMinutes) {
|
|
52850
53692
|
throw new Error(`align: unknown interval=${interval}`);
|
|
52851
53693
|
}
|
|
52852
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
53694
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
|
|
52853
53695
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
52854
53696
|
};
|
|
52855
53697
|
/**
|
|
@@ -52860,7 +53702,7 @@ const align = (timestamp, interval) => {
|
|
|
52860
53702
|
* @param backtest - Whether running in backtest mode
|
|
52861
53703
|
* @returns Cache key string
|
|
52862
53704
|
*/
|
|
52863
|
-
const CREATE_KEY_FN = (strategyName, exchangeName, frameName, backtest) => {
|
|
53705
|
+
const CREATE_KEY_FN$1 = (strategyName, exchangeName, frameName, backtest) => {
|
|
52864
53706
|
const parts = [strategyName, exchangeName];
|
|
52865
53707
|
if (frameName)
|
|
52866
53708
|
parts.push(frameName);
|
|
@@ -52884,16 +53726,16 @@ const NEVER_VALUE = Symbol("never");
|
|
|
52884
53726
|
*
|
|
52885
53727
|
* @example
|
|
52886
53728
|
* ```typescript
|
|
52887
|
-
* const instance = new
|
|
53729
|
+
* const instance = new CacheFnInstance(myExpensiveFunction, "1h");
|
|
52888
53730
|
* const result = instance.run(arg1, arg2); // Computed
|
|
52889
53731
|
* const result2 = instance.run(arg1, arg2); // Cached (within same hour)
|
|
52890
53732
|
* // After 1 hour passes
|
|
52891
53733
|
* const result3 = instance.run(arg1, arg2); // Recomputed
|
|
52892
53734
|
* ```
|
|
52893
53735
|
*/
|
|
52894
|
-
class
|
|
53736
|
+
class CacheFnInstance {
|
|
52895
53737
|
/**
|
|
52896
|
-
* Creates a new
|
|
53738
|
+
* Creates a new CacheFnInstance for a specific function and interval.
|
|
52897
53739
|
*
|
|
52898
53740
|
* @param fn - Function to cache
|
|
52899
53741
|
* @param interval - Candle interval for cache invalidation (e.g., "1m", "1h")
|
|
@@ -52930,7 +53772,7 @@ class CacheInstance {
|
|
|
52930
53772
|
*
|
|
52931
53773
|
* @example
|
|
52932
53774
|
* ```typescript
|
|
52933
|
-
* const instance = new
|
|
53775
|
+
* const instance = new CacheFnInstance(calculateIndicator, "15m");
|
|
52934
53776
|
* const result = instance.run("BTCUSDT", 100);
|
|
52935
53777
|
* console.log(result.value); // Calculated value
|
|
52936
53778
|
* console.log(result.when); // Cache timestamp
|
|
@@ -52938,26 +53780,26 @@ class CacheInstance {
|
|
|
52938
53780
|
*/
|
|
52939
53781
|
this.run = (...args) => {
|
|
52940
53782
|
backtest.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
|
|
52941
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
53783
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
52942
53784
|
{
|
|
52943
53785
|
if (!MethodContextService.hasContext()) {
|
|
52944
|
-
throw new Error("
|
|
53786
|
+
throw new Error("CacheFnInstance run requires method context");
|
|
52945
53787
|
}
|
|
52946
53788
|
if (!ExecutionContextService.hasContext()) {
|
|
52947
|
-
throw new Error("
|
|
53789
|
+
throw new Error("CacheFnInstance run requires execution context");
|
|
52948
53790
|
}
|
|
52949
53791
|
if (!step) {
|
|
52950
|
-
throw new Error(`
|
|
53792
|
+
throw new Error(`CacheFnInstance unknown cache ttl interval=${this.interval}`);
|
|
52951
53793
|
}
|
|
52952
53794
|
}
|
|
52953
|
-
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53795
|
+
const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
52954
53796
|
const argKey = String(this.key(args));
|
|
52955
53797
|
const key = `${contextKey}:${argKey}`;
|
|
52956
53798
|
const currentWhen = backtest.executionContextService.context.when;
|
|
52957
53799
|
const cached = this._cacheMap.get(key);
|
|
52958
53800
|
if (cached) {
|
|
52959
|
-
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
52960
|
-
const cachedAligned = align(cached.when.getTime(), this.interval);
|
|
53801
|
+
const currentAligned = align$1(currentWhen.getTime(), this.interval);
|
|
53802
|
+
const cachedAligned = align$1(cached.when.getTime(), this.interval);
|
|
52961
53803
|
if (currentAligned === cachedAligned) {
|
|
52962
53804
|
return cached;
|
|
52963
53805
|
}
|
|
@@ -52980,7 +53822,7 @@ class CacheInstance {
|
|
|
52980
53822
|
*
|
|
52981
53823
|
* @example
|
|
52982
53824
|
* ```typescript
|
|
52983
|
-
* const instance = new
|
|
53825
|
+
* const instance = new CacheFnInstance(calculateIndicator, "1h");
|
|
52984
53826
|
* const result1 = instance.run("BTCUSDT", 14); // Computed
|
|
52985
53827
|
* const result2 = instance.run("BTCUSDT", 14); // Cached
|
|
52986
53828
|
*
|
|
@@ -52990,7 +53832,7 @@ class CacheInstance {
|
|
|
52990
53832
|
* ```
|
|
52991
53833
|
*/
|
|
52992
53834
|
this.clear = () => {
|
|
52993
|
-
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53835
|
+
const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
52994
53836
|
const prefix = `${contextKey}:`;
|
|
52995
53837
|
for (const key of this._cacheMap.keys()) {
|
|
52996
53838
|
if (key.startsWith(prefix)) {
|
|
@@ -53010,7 +53852,7 @@ class CacheInstance {
|
|
|
53010
53852
|
*
|
|
53011
53853
|
* @example
|
|
53012
53854
|
* ```typescript
|
|
53013
|
-
* const instance = new
|
|
53855
|
+
* const instance = new CacheFnInstance(calculateIndicator, "1h");
|
|
53014
53856
|
* instance.run("BTCUSDT", 14); // Cached at 10:00
|
|
53015
53857
|
* instance.run("ETHUSDT", 14); // Cached at 10:00
|
|
53016
53858
|
* // Time passes to 11:00
|
|
@@ -53019,10 +53861,10 @@ class CacheInstance {
|
|
|
53019
53861
|
*/
|
|
53020
53862
|
this.gc = () => {
|
|
53021
53863
|
const currentWhen = backtest.executionContextService.context.when;
|
|
53022
|
-
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
53864
|
+
const currentAligned = align$1(currentWhen.getTime(), this.interval);
|
|
53023
53865
|
let removed = 0;
|
|
53024
53866
|
for (const [key, cached] of this._cacheMap.entries()) {
|
|
53025
|
-
const cachedAligned = align(cached.when.getTime(), this.interval);
|
|
53867
|
+
const cachedAligned = align$1(cached.when.getTime(), this.interval);
|
|
53026
53868
|
if (currentAligned !== cachedAligned) {
|
|
53027
53869
|
this._cacheMap.delete(key);
|
|
53028
53870
|
removed++;
|
|
@@ -53097,7 +53939,7 @@ class CacheFileInstance {
|
|
|
53097
53939
|
*/
|
|
53098
53940
|
this.run = async (...args) => {
|
|
53099
53941
|
backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
53100
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
53942
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
53101
53943
|
{
|
|
53102
53944
|
if (!MethodContextService.hasContext()) {
|
|
53103
53945
|
throw new Error("CacheFileInstance run requires method context");
|
|
@@ -53111,7 +53953,7 @@ class CacheFileInstance {
|
|
|
53111
53953
|
}
|
|
53112
53954
|
const [symbol, ...rest] = args;
|
|
53113
53955
|
const { when } = backtest.executionContextService.context;
|
|
53114
|
-
const alignedTs = align(when.getTime(), this.interval);
|
|
53956
|
+
const alignedTs = align$1(when.getTime(), this.interval);
|
|
53115
53957
|
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53116
53958
|
const entityKey = this.key([symbol, alignedTs, ...rest]);
|
|
53117
53959
|
const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
|
|
@@ -53119,9 +53961,19 @@ class CacheFileInstance {
|
|
|
53119
53961
|
return cached.data;
|
|
53120
53962
|
}
|
|
53121
53963
|
const result = await this.fn.call(null, ...args);
|
|
53122
|
-
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result }, bucket, entityKey);
|
|
53964
|
+
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
53123
53965
|
return result;
|
|
53124
53966
|
};
|
|
53967
|
+
/**
|
|
53968
|
+
* Soft-delete all persisted records for this instance's bucket.
|
|
53969
|
+
* After this call the next `run()` will recompute and re-cache the value.
|
|
53970
|
+
*/
|
|
53971
|
+
this.clear = async () => {
|
|
53972
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53973
|
+
for await (const key of PersistMeasureAdapter.listMeasureData(bucket)) {
|
|
53974
|
+
await PersistMeasureAdapter.removeMeasureData(bucket, key);
|
|
53975
|
+
}
|
|
53976
|
+
};
|
|
53125
53977
|
this.index = CacheFileInstance.createIndex();
|
|
53126
53978
|
}
|
|
53127
53979
|
}
|
|
@@ -53145,10 +53997,10 @@ CacheFileInstance._indexCounter = 0;
|
|
|
53145
53997
|
class CacheUtils {
|
|
53146
53998
|
constructor() {
|
|
53147
53999
|
/**
|
|
53148
|
-
* Memoized function to get or create
|
|
54000
|
+
* Memoized function to get or create CacheFnInstance for a function.
|
|
53149
54001
|
* Each function gets its own isolated cache instance.
|
|
53150
54002
|
*/
|
|
53151
|
-
this._getFnInstance = functoolsKit.memoize(([run]) => run, (run, interval, key) => new
|
|
54003
|
+
this._getFnInstance = functoolsKit.memoize(([run]) => run, (run, interval, key) => new CacheFnInstance(run, interval, key));
|
|
53152
54004
|
/**
|
|
53153
54005
|
* Memoized function to get or create CacheFileInstance for an async function.
|
|
53154
54006
|
* Each function gets its own isolated file-cache instance.
|
|
@@ -53257,31 +54109,34 @@ class CacheUtils {
|
|
|
53257
54109
|
*/
|
|
53258
54110
|
this.file = (run, context) => {
|
|
53259
54111
|
backtest.loggerService.info(CACHE_METHOD_NAME_FILE, { context });
|
|
54112
|
+
{
|
|
54113
|
+
this._getFileInstance(run, context.interval, context.name, context.key);
|
|
54114
|
+
}
|
|
53260
54115
|
const wrappedFn = (...args) => {
|
|
53261
54116
|
const instance = this._getFileInstance(run, context.interval, context.name, context.key);
|
|
53262
54117
|
return instance.run(...args);
|
|
53263
54118
|
};
|
|
53264
|
-
wrappedFn.clear = () => {
|
|
54119
|
+
wrappedFn.clear = async () => {
|
|
53265
54120
|
backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
|
|
53266
|
-
this._getFileInstance.
|
|
54121
|
+
await this._getFileInstance.get(run)?.clear();
|
|
53267
54122
|
};
|
|
53268
54123
|
return wrappedFn;
|
|
53269
54124
|
};
|
|
53270
54125
|
/**
|
|
53271
|
-
* Dispose (remove) the memoized
|
|
54126
|
+
* Dispose (remove) the memoized CacheFnInstance for a specific function.
|
|
53272
54127
|
*
|
|
53273
|
-
* Removes the
|
|
54128
|
+
* Removes the CacheFnInstance from the internal memoization cache, discarding all cached
|
|
53274
54129
|
* results across all contexts (all strategy/exchange/mode combinations) for that function.
|
|
53275
|
-
* The next call to the wrapped function will create a fresh
|
|
54130
|
+
* The next call to the wrapped function will create a fresh CacheFnInstance.
|
|
53276
54131
|
*
|
|
53277
54132
|
* @template T - Function type
|
|
53278
|
-
* @param run - Function whose
|
|
54133
|
+
* @param run - Function whose CacheFnInstance should be disposed.
|
|
53279
54134
|
*
|
|
53280
54135
|
* @example
|
|
53281
54136
|
* ```typescript
|
|
53282
54137
|
* const cachedFn = Cache.fn(calculateIndicator, { interval: "1h" });
|
|
53283
54138
|
*
|
|
53284
|
-
* // Dispose
|
|
54139
|
+
* // Dispose CacheFnInstance for a specific function
|
|
53285
54140
|
* Cache.dispose(calculateIndicator);
|
|
53286
54141
|
* ```
|
|
53287
54142
|
*/
|
|
@@ -53295,7 +54150,7 @@ class CacheUtils {
|
|
|
53295
54150
|
}
|
|
53296
54151
|
};
|
|
53297
54152
|
/**
|
|
53298
|
-
* Clears all memoized
|
|
54153
|
+
* Clears all memoized CacheFnInstance and CacheFileInstance objects.
|
|
53299
54154
|
* Call this when process.cwd() changes between strategy iterations
|
|
53300
54155
|
* so new instances are created with the updated base path.
|
|
53301
54156
|
*/
|
|
@@ -53325,6 +54180,425 @@ class CacheUtils {
|
|
|
53325
54180
|
*/
|
|
53326
54181
|
const Cache = new CacheUtils();
|
|
53327
54182
|
|
|
54183
|
+
const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
|
|
54184
|
+
const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
|
|
54185
|
+
const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
|
|
54186
|
+
const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
|
|
54187
|
+
const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
|
|
54188
|
+
const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
|
|
54189
|
+
const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
|
|
54190
|
+
const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
|
|
54191
|
+
const MS_PER_MINUTE = 60000;
|
|
54192
|
+
const INTERVAL_MINUTES = {
|
|
54193
|
+
"1m": 1,
|
|
54194
|
+
"3m": 3,
|
|
54195
|
+
"5m": 5,
|
|
54196
|
+
"15m": 15,
|
|
54197
|
+
"30m": 30,
|
|
54198
|
+
"1h": 60,
|
|
54199
|
+
"2h": 120,
|
|
54200
|
+
"4h": 240,
|
|
54201
|
+
"6h": 360,
|
|
54202
|
+
"8h": 480,
|
|
54203
|
+
"1d": 1440,
|
|
54204
|
+
"1w": 10080,
|
|
54205
|
+
};
|
|
54206
|
+
/**
|
|
54207
|
+
* Aligns timestamp down to the nearest interval boundary.
|
|
54208
|
+
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
54209
|
+
*
|
|
54210
|
+
* @param timestamp - Timestamp in milliseconds
|
|
54211
|
+
* @param interval - Candle interval
|
|
54212
|
+
* @returns Aligned timestamp rounded down to interval boundary
|
|
54213
|
+
* @throws Error if interval is unknown
|
|
54214
|
+
*
|
|
54215
|
+
* @example
|
|
54216
|
+
* ```typescript
|
|
54217
|
+
* // Align to 15-minute boundary
|
|
54218
|
+
* const aligned = align(new Date("2025-10-01T00:35:00Z").getTime(), "15m");
|
|
54219
|
+
* // Returns timestamp for 2025-10-01T00:30:00Z
|
|
54220
|
+
* ```
|
|
54221
|
+
*/
|
|
54222
|
+
const align = (timestamp, interval) => {
|
|
54223
|
+
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
54224
|
+
if (!intervalMinutes) {
|
|
54225
|
+
throw new Error(`align: unknown interval=${interval}`);
|
|
54226
|
+
}
|
|
54227
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
54228
|
+
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
54229
|
+
};
|
|
54230
|
+
/**
|
|
54231
|
+
* Build a context key string from strategy name, exchange name, frame name, and execution mode.
|
|
54232
|
+
*
|
|
54233
|
+
* @param strategyName - Name of the strategy
|
|
54234
|
+
* @param exchangeName - Name of the exchange
|
|
54235
|
+
* @param frameName - Name of the backtest frame (omitted in live mode)
|
|
54236
|
+
* @param isBacktest - Whether running in backtest mode
|
|
54237
|
+
* @returns Context key string
|
|
54238
|
+
*/
|
|
54239
|
+
const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
|
|
54240
|
+
const parts = [strategyName, exchangeName];
|
|
54241
|
+
if (frameName)
|
|
54242
|
+
parts.push(frameName);
|
|
54243
|
+
parts.push(isBacktest ? "backtest" : "live");
|
|
54244
|
+
return parts.join(":");
|
|
54245
|
+
};
|
|
54246
|
+
/**
|
|
54247
|
+
* Instance class for firing a function exactly once per interval boundary.
|
|
54248
|
+
*
|
|
54249
|
+
* On the first call within a new interval the wrapped function is invoked and
|
|
54250
|
+
* its result is returned. Every subsequent call within the same interval returns
|
|
54251
|
+
* `null` without invoking the function again.
|
|
54252
|
+
* If the function itself returns `null`, the interval countdown does not start —
|
|
54253
|
+
* the next call will retry the function.
|
|
54254
|
+
*
|
|
54255
|
+
* State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
|
|
54256
|
+
*
|
|
54257
|
+
* @example
|
|
54258
|
+
* ```typescript
|
|
54259
|
+
* const instance = new IntervalFnInstance(mySignalFn, "1h");
|
|
54260
|
+
* await instance.run("BTCUSDT"); // → T | null (fn called)
|
|
54261
|
+
* await instance.run("BTCUSDT"); // → null (skipped, same interval)
|
|
54262
|
+
* // After 1 hour passes:
|
|
54263
|
+
* await instance.run("BTCUSDT"); // → T | null (fn called again)
|
|
54264
|
+
* ```
|
|
54265
|
+
*/
|
|
54266
|
+
class IntervalFnInstance {
|
|
54267
|
+
/**
|
|
54268
|
+
* Creates a new IntervalFnInstance.
|
|
54269
|
+
*
|
|
54270
|
+
* @param fn - Function to fire once per interval
|
|
54271
|
+
* @param interval - Candle interval that controls the firing boundary
|
|
54272
|
+
*/
|
|
54273
|
+
constructor(fn, interval) {
|
|
54274
|
+
this.fn = fn;
|
|
54275
|
+
this.interval = interval;
|
|
54276
|
+
/** Stores the last aligned timestamp per context+symbol key. */
|
|
54277
|
+
this._stateMap = new Map();
|
|
54278
|
+
/**
|
|
54279
|
+
* Execute the signal function with once-per-interval enforcement.
|
|
54280
|
+
*
|
|
54281
|
+
* Algorithm:
|
|
54282
|
+
* 1. Align the current execution context `when` to the interval boundary.
|
|
54283
|
+
* 2. If the stored aligned timestamp for this context+symbol equals the current one → return `null`.
|
|
54284
|
+
* 3. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
|
|
54285
|
+
* the signal. If it returns `null`, leave state unchanged so the next call retries.
|
|
54286
|
+
*
|
|
54287
|
+
* Requires active method context and execution context.
|
|
54288
|
+
*
|
|
54289
|
+
* @param symbol - Trading pair symbol (e.g. "BTCUSDT")
|
|
54290
|
+
* @returns The value returned by `fn` on the first non-null fire, `null` on all subsequent calls
|
|
54291
|
+
* within the same interval or when `fn` itself returned `null`
|
|
54292
|
+
* @throws Error if method context, execution context, or interval is missing
|
|
54293
|
+
*/
|
|
54294
|
+
this.run = async (symbol) => {
|
|
54295
|
+
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { symbol });
|
|
54296
|
+
const step = INTERVAL_MINUTES[this.interval];
|
|
54297
|
+
{
|
|
54298
|
+
if (!MethodContextService.hasContext()) {
|
|
54299
|
+
throw new Error("IntervalFnInstance run requires method context");
|
|
54300
|
+
}
|
|
54301
|
+
if (!ExecutionContextService.hasContext()) {
|
|
54302
|
+
throw new Error("IntervalFnInstance run requires execution context");
|
|
54303
|
+
}
|
|
54304
|
+
if (!step) {
|
|
54305
|
+
throw new Error(`IntervalFnInstance unknown interval=${this.interval}`);
|
|
54306
|
+
}
|
|
54307
|
+
}
|
|
54308
|
+
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
54309
|
+
const key = `${contextKey}:${symbol}`;
|
|
54310
|
+
const currentWhen = backtest.executionContextService.context.when;
|
|
54311
|
+
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
54312
|
+
if (this._stateMap.get(key) === currentAligned) {
|
|
54313
|
+
return null;
|
|
54314
|
+
}
|
|
54315
|
+
const result = await this.fn(symbol, currentWhen);
|
|
54316
|
+
if (result !== null) {
|
|
54317
|
+
this._stateMap.set(key, currentAligned);
|
|
54318
|
+
}
|
|
54319
|
+
return result;
|
|
54320
|
+
};
|
|
54321
|
+
/**
|
|
54322
|
+
* Clear fired-interval state for the current execution context.
|
|
54323
|
+
*
|
|
54324
|
+
* Removes all entries for the current strategy/exchange/frame/mode combination
|
|
54325
|
+
* from this instance's state map. The next `run()` call will invoke the function again.
|
|
54326
|
+
*
|
|
54327
|
+
* Requires active method context and execution context.
|
|
54328
|
+
*/
|
|
54329
|
+
this.clear = () => {
|
|
54330
|
+
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
54331
|
+
const prefix = `${contextKey}:`;
|
|
54332
|
+
for (const key of this._stateMap.keys()) {
|
|
54333
|
+
if (key.startsWith(prefix)) {
|
|
54334
|
+
this._stateMap.delete(key);
|
|
54335
|
+
}
|
|
54336
|
+
}
|
|
54337
|
+
};
|
|
54338
|
+
}
|
|
54339
|
+
}
|
|
54340
|
+
/**
|
|
54341
|
+
* Instance class for firing an async function exactly once per interval boundary,
|
|
54342
|
+
* with the fired state persisted to disk via `PersistIntervalAdapter`.
|
|
54343
|
+
*
|
|
54344
|
+
* On the first call within a new interval the wrapped function is invoked.
|
|
54345
|
+
* If it returns a non-null signal, that result is written to disk and returned.
|
|
54346
|
+
* Every subsequent call within the same interval returns `null` (record exists on disk).
|
|
54347
|
+
* If the function returns `null`, nothing is written and the next call retries.
|
|
54348
|
+
*
|
|
54349
|
+
* Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
|
|
54350
|
+
*
|
|
54351
|
+
* @template T - Async function type: `(symbol: string, ...args) => Promise<R | null>`
|
|
54352
|
+
*
|
|
54353
|
+
* @example
|
|
54354
|
+
* ```typescript
|
|
54355
|
+
* const instance = new IntervalFileInstance(fetchSignal, "1h", "mySignal");
|
|
54356
|
+
* await instance.run("BTCUSDT"); // → R | null (fn called, result written to disk)
|
|
54357
|
+
* await instance.run("BTCUSDT"); // → null (record exists, already fired)
|
|
54358
|
+
* ```
|
|
54359
|
+
*/
|
|
54360
|
+
class IntervalFileInstance {
|
|
54361
|
+
/**
|
|
54362
|
+
* Allocates a new unique index. Called once in the constructor to give each
|
|
54363
|
+
* IntervalFileInstance its own namespace in the persistent key space.
|
|
54364
|
+
*/
|
|
54365
|
+
static createIndex() {
|
|
54366
|
+
return IntervalFileInstance._indexCounter++;
|
|
54367
|
+
}
|
|
54368
|
+
/**
|
|
54369
|
+
* Resets the index counter to zero.
|
|
54370
|
+
* Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
|
|
54371
|
+
*/
|
|
54372
|
+
static clearCounter() {
|
|
54373
|
+
IntervalFileInstance._indexCounter = 0;
|
|
54374
|
+
}
|
|
54375
|
+
/**
|
|
54376
|
+
* Creates a new IntervalFileInstance.
|
|
54377
|
+
*
|
|
54378
|
+
* @param fn - Async signal function to fire once per interval
|
|
54379
|
+
* @param interval - Candle interval that controls the firing boundary
|
|
54380
|
+
* @param name - Human-readable bucket name used as the directory prefix
|
|
54381
|
+
*/
|
|
54382
|
+
constructor(fn, interval, name) {
|
|
54383
|
+
this.fn = fn;
|
|
54384
|
+
this.interval = interval;
|
|
54385
|
+
this.name = name;
|
|
54386
|
+
/**
|
|
54387
|
+
* Execute the async function with persistent once-per-interval enforcement.
|
|
54388
|
+
*
|
|
54389
|
+
* Algorithm:
|
|
54390
|
+
* 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
|
|
54391
|
+
* 2. Align execution context `when` to interval boundary → `alignedTs`.
|
|
54392
|
+
* 3. Build entity key = `${symbol}_${alignedTs}`.
|
|
54393
|
+
* 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
|
|
54394
|
+
* 5. On hit — return `null` (interval already fired).
|
|
54395
|
+
* 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
|
|
54396
|
+
*
|
|
54397
|
+
* Requires active method context and execution context.
|
|
54398
|
+
*
|
|
54399
|
+
* @param symbol - Trading pair symbol (e.g. "BTCUSDT")
|
|
54400
|
+
* @returns The value on the first non-null fire, `null` if already fired this interval
|
|
54401
|
+
* or if `fn` itself returned `null`
|
|
54402
|
+
* @throws Error if method context, execution context, or interval is missing
|
|
54403
|
+
*/
|
|
54404
|
+
this.run = async (symbol) => {
|
|
54405
|
+
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { symbol });
|
|
54406
|
+
const step = INTERVAL_MINUTES[this.interval];
|
|
54407
|
+
{
|
|
54408
|
+
if (!MethodContextService.hasContext()) {
|
|
54409
|
+
throw new Error("IntervalFileInstance run requires method context");
|
|
54410
|
+
}
|
|
54411
|
+
if (!ExecutionContextService.hasContext()) {
|
|
54412
|
+
throw new Error("IntervalFileInstance run requires execution context");
|
|
54413
|
+
}
|
|
54414
|
+
if (!step) {
|
|
54415
|
+
throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
|
|
54416
|
+
}
|
|
54417
|
+
}
|
|
54418
|
+
const { when } = backtest.executionContextService.context;
|
|
54419
|
+
const alignedTs = align(when.getTime(), this.interval);
|
|
54420
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
54421
|
+
const entityKey = `${symbol}_${alignedTs}`;
|
|
54422
|
+
const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
|
|
54423
|
+
if (cached !== null) {
|
|
54424
|
+
return null;
|
|
54425
|
+
}
|
|
54426
|
+
const result = await this.fn(symbol, when);
|
|
54427
|
+
if (result !== null) {
|
|
54428
|
+
await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
54429
|
+
}
|
|
54430
|
+
return result;
|
|
54431
|
+
};
|
|
54432
|
+
/**
|
|
54433
|
+
* Soft-delete all persisted records for this instance's bucket.
|
|
54434
|
+
* After this call the function will fire again on the next `run()`.
|
|
54435
|
+
*/
|
|
54436
|
+
this.clear = async () => {
|
|
54437
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
54438
|
+
for await (const key of PersistIntervalAdapter.listIntervalData(bucket)) {
|
|
54439
|
+
await PersistIntervalAdapter.removeIntervalData(bucket, key);
|
|
54440
|
+
}
|
|
54441
|
+
};
|
|
54442
|
+
this.index = IntervalFileInstance.createIndex();
|
|
54443
|
+
}
|
|
54444
|
+
}
|
|
54445
|
+
/** Global counter — incremented once per IntervalFileInstance construction. */
|
|
54446
|
+
IntervalFileInstance._indexCounter = 0;
|
|
54447
|
+
/**
|
|
54448
|
+
* Utility class for wrapping signal functions with once-per-interval firing.
|
|
54449
|
+
* Provides two modes: in-memory (`fn`) and persistent file-based (`file`).
|
|
54450
|
+
* Exported as singleton instance `Interval` for convenient usage.
|
|
54451
|
+
*
|
|
54452
|
+
* @example
|
|
54453
|
+
* ```typescript
|
|
54454
|
+
* import { Interval } from "./classes/Interval";
|
|
54455
|
+
*
|
|
54456
|
+
* const fireOncePerHour = Interval.fn(mySignalFn, { interval: "1h" });
|
|
54457
|
+
* await fireOncePerHour("BTCUSDT"); // fn called — returns its result
|
|
54458
|
+
* await fireOncePerHour("BTCUSDT"); // returns null (same interval)
|
|
54459
|
+
* ```
|
|
54460
|
+
*/
|
|
54461
|
+
class IntervalUtils {
|
|
54462
|
+
constructor() {
|
|
54463
|
+
/**
|
|
54464
|
+
* Memoized factory to get or create an `IntervalFnInstance` for a function.
|
|
54465
|
+
* Each function reference gets its own isolated instance.
|
|
54466
|
+
*/
|
|
54467
|
+
this._getInstance = functoolsKit.memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
|
|
54468
|
+
/**
|
|
54469
|
+
* Memoized factory to get or create an `IntervalFileInstance` for an async function.
|
|
54470
|
+
* Each function reference gets its own isolated persistent instance.
|
|
54471
|
+
*/
|
|
54472
|
+
this._getFileInstance = functoolsKit.memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
|
|
54473
|
+
/**
|
|
54474
|
+
* Wrap a signal function with in-memory once-per-interval firing.
|
|
54475
|
+
*
|
|
54476
|
+
* Returns a wrapped version of the function that fires at most once per interval boundary.
|
|
54477
|
+
* If the function returns `null`, the countdown does not start and the next call retries.
|
|
54478
|
+
*
|
|
54479
|
+
* The `run` function reference is used as the memoization key for the underlying
|
|
54480
|
+
* `IntervalFnInstance`, so each unique function reference gets its own isolated instance.
|
|
54481
|
+
*
|
|
54482
|
+
* @param run - Signal function to wrap
|
|
54483
|
+
* @param context.interval - Candle interval that controls the firing boundary
|
|
54484
|
+
* @returns Wrapped function with the same signature as `TIntervalFn<T>`, plus a `clear()` method
|
|
54485
|
+
*
|
|
54486
|
+
* @example
|
|
54487
|
+
* ```typescript
|
|
54488
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
|
|
54489
|
+
*
|
|
54490
|
+
* await fireOnce("BTCUSDT"); // → T or null (fn called)
|
|
54491
|
+
* await fireOnce("BTCUSDT"); // → null (same interval, skipped)
|
|
54492
|
+
* ```
|
|
54493
|
+
*/
|
|
54494
|
+
this.fn = (run, context) => {
|
|
54495
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
|
|
54496
|
+
const wrappedFn = (symbol) => {
|
|
54497
|
+
const instance = this._getInstance(run, context.interval);
|
|
54498
|
+
return instance.run(symbol);
|
|
54499
|
+
};
|
|
54500
|
+
wrappedFn.clear = () => {
|
|
54501
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
|
|
54502
|
+
if (!MethodContextService.hasContext()) {
|
|
54503
|
+
backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
|
|
54504
|
+
return;
|
|
54505
|
+
}
|
|
54506
|
+
if (!ExecutionContextService.hasContext()) {
|
|
54507
|
+
backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
|
|
54508
|
+
return;
|
|
54509
|
+
}
|
|
54510
|
+
this._getInstance.get(run)?.clear();
|
|
54511
|
+
};
|
|
54512
|
+
return wrappedFn;
|
|
54513
|
+
};
|
|
54514
|
+
/**
|
|
54515
|
+
* Wrap an async signal function with persistent file-based once-per-interval firing.
|
|
54516
|
+
*
|
|
54517
|
+
* Returns a wrapped version of the function that reads from disk on hit (returns `null`)
|
|
54518
|
+
* and writes the fired signal to disk on the first successful fire.
|
|
54519
|
+
* Fired state survives process restarts.
|
|
54520
|
+
*
|
|
54521
|
+
* The `run` function reference is used as the memoization key for the underlying
|
|
54522
|
+
* `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
|
|
54523
|
+
*
|
|
54524
|
+
* @template T - Async function type to wrap
|
|
54525
|
+
* @param run - Async signal function to wrap with persistent once-per-interval firing
|
|
54526
|
+
* @param context.interval - Candle interval that controls the firing boundary
|
|
54527
|
+
* @param context.name - Human-readable bucket name; becomes the directory prefix
|
|
54528
|
+
* @returns Wrapped function with the same signature as `T`, plus an async `clear()` method
|
|
54529
|
+
* that deletes persisted records from disk and disposes the memoized instance
|
|
54530
|
+
*
|
|
54531
|
+
* @example
|
|
54532
|
+
* ```typescript
|
|
54533
|
+
* const fetchSignal = async (symbol: string, when: Date) => { ... };
|
|
54534
|
+
* const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
|
|
54535
|
+
* await fireOnce.clear(); // delete disk records so the function fires again next call
|
|
54536
|
+
* ```
|
|
54537
|
+
*/
|
|
54538
|
+
this.file = (run, context) => {
|
|
54539
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
|
|
54540
|
+
{
|
|
54541
|
+
this._getFileInstance(run, context.interval, context.name);
|
|
54542
|
+
}
|
|
54543
|
+
const wrappedFn = (symbol) => {
|
|
54544
|
+
const instance = this._getFileInstance(run, context.interval, context.name);
|
|
54545
|
+
return instance.run(symbol);
|
|
54546
|
+
};
|
|
54547
|
+
wrappedFn.clear = async () => {
|
|
54548
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
|
|
54549
|
+
await this._getFileInstance.get(run)?.clear();
|
|
54550
|
+
};
|
|
54551
|
+
return wrappedFn;
|
|
54552
|
+
};
|
|
54553
|
+
/**
|
|
54554
|
+
* Dispose (remove) the memoized `IntervalFnInstance` for a specific function.
|
|
54555
|
+
*
|
|
54556
|
+
* Removes the instance from the internal memoization cache, discarding all in-memory
|
|
54557
|
+
* fired-interval state across all contexts for that function.
|
|
54558
|
+
* The next call to the wrapped function will create a fresh `IntervalFnInstance`.
|
|
54559
|
+
*
|
|
54560
|
+
* @param run - Function whose `IntervalFnInstance` should be disposed
|
|
54561
|
+
*
|
|
54562
|
+
* @example
|
|
54563
|
+
* ```typescript
|
|
54564
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
|
|
54565
|
+
* Interval.dispose(mySignalFn);
|
|
54566
|
+
* ```
|
|
54567
|
+
*/
|
|
54568
|
+
this.dispose = (run) => {
|
|
54569
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
|
|
54570
|
+
this._getInstance.clear(run);
|
|
54571
|
+
};
|
|
54572
|
+
/**
|
|
54573
|
+
* Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects and
|
|
54574
|
+
* resets the `IntervalFileInstance` index counter.
|
|
54575
|
+
* Call this when `process.cwd()` changes between strategy iterations
|
|
54576
|
+
* so new instances are created with the updated base path.
|
|
54577
|
+
*/
|
|
54578
|
+
this.clear = () => {
|
|
54579
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
|
|
54580
|
+
this._getInstance.clear();
|
|
54581
|
+
this._getFileInstance.clear();
|
|
54582
|
+
IntervalFileInstance.clearCounter();
|
|
54583
|
+
};
|
|
54584
|
+
}
|
|
54585
|
+
}
|
|
54586
|
+
/**
|
|
54587
|
+
* Singleton instance of `IntervalUtils` for convenient once-per-interval signal firing.
|
|
54588
|
+
*
|
|
54589
|
+
* @example
|
|
54590
|
+
* ```typescript
|
|
54591
|
+
* import { Interval } from "./classes/Interval";
|
|
54592
|
+
*
|
|
54593
|
+
* // In-memory: fires once per hour, resets on process restart
|
|
54594
|
+
* const fireOnce = Interval.fn(mySignalFn, { interval: "1h" });
|
|
54595
|
+
*
|
|
54596
|
+
* // Persistent: fired state survives restarts
|
|
54597
|
+
* const fireOncePersist = Interval.file(mySignalFn, { interval: "1h", name: "mySignal" });
|
|
54598
|
+
* ```
|
|
54599
|
+
*/
|
|
54600
|
+
const Interval = new IntervalUtils();
|
|
54601
|
+
|
|
53328
54602
|
const BREAKEVEN_METHOD_NAME_GET_DATA = "BreakevenUtils.getData";
|
|
53329
54603
|
const BREAKEVEN_METHOD_NAME_GET_REPORT = "BreakevenUtils.getReport";
|
|
53330
54604
|
const BREAKEVEN_METHOD_NAME_DUMP = "BreakevenUtils.dump";
|
|
@@ -54451,6 +55725,7 @@ exports.Exchange = Exchange;
|
|
|
54451
55725
|
exports.ExecutionContextService = ExecutionContextService;
|
|
54452
55726
|
exports.Heat = Heat;
|
|
54453
55727
|
exports.HighestProfit = HighestProfit;
|
|
55728
|
+
exports.Interval = Interval;
|
|
54454
55729
|
exports.Live = Live;
|
|
54455
55730
|
exports.Log = Log;
|
|
54456
55731
|
exports.Markdown = Markdown;
|
|
@@ -54468,6 +55743,7 @@ exports.Performance = Performance;
|
|
|
54468
55743
|
exports.PersistBase = PersistBase;
|
|
54469
55744
|
exports.PersistBreakevenAdapter = PersistBreakevenAdapter;
|
|
54470
55745
|
exports.PersistCandleAdapter = PersistCandleAdapter;
|
|
55746
|
+
exports.PersistIntervalAdapter = PersistIntervalAdapter;
|
|
54471
55747
|
exports.PersistLogAdapter = PersistLogAdapter;
|
|
54472
55748
|
exports.PersistMeasureAdapter = PersistMeasureAdapter;
|
|
54473
55749
|
exports.PersistMemoryAdapter = PersistMemoryAdapter;
|
|
@@ -54477,6 +55753,7 @@ exports.PersistRiskAdapter = PersistRiskAdapter;
|
|
|
54477
55753
|
exports.PersistScheduleAdapter = PersistScheduleAdapter;
|
|
54478
55754
|
exports.PersistSignalAdapter = PersistSignalAdapter;
|
|
54479
55755
|
exports.PersistStorageAdapter = PersistStorageAdapter;
|
|
55756
|
+
exports.Position = Position;
|
|
54480
55757
|
exports.PositionSize = PositionSize;
|
|
54481
55758
|
exports.Report = Report;
|
|
54482
55759
|
exports.ReportBase = ReportBase;
|
|
@@ -54546,9 +55823,13 @@ exports.getPositionEffectivePrice = getPositionEffectivePrice;
|
|
|
54546
55823
|
exports.getPositionEntries = getPositionEntries;
|
|
54547
55824
|
exports.getPositionEntryOverlap = getPositionEntryOverlap;
|
|
54548
55825
|
exports.getPositionEstimateMinutes = getPositionEstimateMinutes;
|
|
55826
|
+
exports.getPositionHighestMaxDrawdownPnlCost = getPositionHighestMaxDrawdownPnlCost;
|
|
55827
|
+
exports.getPositionHighestMaxDrawdownPnlPercentage = getPositionHighestMaxDrawdownPnlPercentage;
|
|
54549
55828
|
exports.getPositionHighestPnlCost = getPositionHighestPnlCost;
|
|
54550
55829
|
exports.getPositionHighestPnlPercentage = getPositionHighestPnlPercentage;
|
|
54551
55830
|
exports.getPositionHighestProfitBreakeven = getPositionHighestProfitBreakeven;
|
|
55831
|
+
exports.getPositionHighestProfitDistancePnlCost = getPositionHighestProfitDistancePnlCost;
|
|
55832
|
+
exports.getPositionHighestProfitDistancePnlPercentage = getPositionHighestProfitDistancePnlPercentage;
|
|
54552
55833
|
exports.getPositionHighestProfitMinutes = getPositionHighestProfitMinutes;
|
|
54553
55834
|
exports.getPositionHighestProfitPrice = getPositionHighestProfitPrice;
|
|
54554
55835
|
exports.getPositionHighestProfitTimestamp = getPositionHighestProfitTimestamp;
|