backtest-kit 10.2.0 → 11.2.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 +118 -10
- package/build/index.mjs +122 -14
- package/package.json +1 -1
- package/types.d.ts +70 -8
package/build/index.cjs
CHANGED
|
@@ -818,6 +818,13 @@ const beforeStartSubject = new functoolsKit.Subject();
|
|
|
818
818
|
* Emits when the engine has completed processing a signal.
|
|
819
819
|
*/
|
|
820
820
|
const afterEndSubject = new functoolsKit.Subject();
|
|
821
|
+
/**
|
|
822
|
+
* Emitter for `@backtest-kit/cli`, which notifies the application
|
|
823
|
+
* that all modules have been initialized.
|
|
824
|
+
*
|
|
825
|
+
* Send entry absolute path to the consumer
|
|
826
|
+
*/
|
|
827
|
+
const entrySubject = new functoolsKit.BehaviorSubject();
|
|
821
828
|
|
|
822
829
|
var emitters = /*#__PURE__*/Object.freeze({
|
|
823
830
|
__proto__: null,
|
|
@@ -829,6 +836,7 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
829
836
|
doneBacktestSubject: doneBacktestSubject,
|
|
830
837
|
doneLiveSubject: doneLiveSubject,
|
|
831
838
|
doneWalkerSubject: doneWalkerSubject,
|
|
839
|
+
entrySubject: entrySubject,
|
|
832
840
|
errorEmitter: errorEmitter,
|
|
833
841
|
exitEmitter: exitEmitter,
|
|
834
842
|
highestProfitSubject: highestProfitSubject,
|
|
@@ -6584,7 +6592,7 @@ const INTERVAL_MINUTES$8 = {
|
|
|
6584
6592
|
* Used to indicate that the actual pendingAt will be set upon activation.
|
|
6585
6593
|
*/
|
|
6586
6594
|
const SCHEDULED_SIGNAL_PENDING_MOCK = 0;
|
|
6587
|
-
const TIMEOUT_SYMBOL = Symbol('timeout');
|
|
6595
|
+
const TIMEOUT_SYMBOL$1 = Symbol('timeout');
|
|
6588
6596
|
/**
|
|
6589
6597
|
* Calls onSignalSync callback for signal-open event.
|
|
6590
6598
|
*
|
|
@@ -7006,7 +7014,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
7006
7014
|
const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
|
|
7007
7015
|
const signal = await Promise.race([
|
|
7008
7016
|
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when, currentPrice),
|
|
7009
|
-
functoolsKit.sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
|
|
7017
|
+
functoolsKit.sleep(timeoutMs).then(() => TIMEOUT_SYMBOL$1),
|
|
7010
7018
|
]);
|
|
7011
7019
|
if (typeof signal === "symbol") {
|
|
7012
7020
|
throw new Error(`Timeout for ${self.params.method.context.strategyName} symbol=${self.params.execution.context.symbol}`);
|
|
@@ -37581,8 +37589,9 @@ function getActionSchema(actionName) {
|
|
|
37581
37589
|
}
|
|
37582
37590
|
|
|
37583
37591
|
const WAIT_FOR_READY_METHOD_NAME = "init.waitForReady";
|
|
37584
|
-
const MAX_WAIT_SECONDS =
|
|
37592
|
+
const MAX_WAIT_SECONDS = 45;
|
|
37585
37593
|
const SECOND_DELAY = 1000;
|
|
37594
|
+
const TIMEOUT_SYMBOL = Symbol('timeout');
|
|
37586
37595
|
/**
|
|
37587
37596
|
* Blocks until the schema registries needed to start trading are populated.
|
|
37588
37597
|
*
|
|
@@ -37620,6 +37629,18 @@ const SECOND_DELAY = 1000;
|
|
|
37620
37629
|
*/
|
|
37621
37630
|
async function waitForReady(isBacktest = true) {
|
|
37622
37631
|
backtest.loggerService.info(WAIT_FOR_READY_METHOD_NAME, { isBacktest });
|
|
37632
|
+
if (entrySubject.data) {
|
|
37633
|
+
return;
|
|
37634
|
+
}
|
|
37635
|
+
if (entrySubject.hasListeners) {
|
|
37636
|
+
backtest.loggerService.debug(`${WAIT_FOR_READY_METHOD_NAME} waiting for entrySubject`);
|
|
37637
|
+
const result = await Promise.race([
|
|
37638
|
+
entrySubject.toPromise(),
|
|
37639
|
+
functoolsKit.sleep(MAX_WAIT_SECONDS * SECOND_DELAY).then(() => TIMEOUT_SYMBOL)
|
|
37640
|
+
]);
|
|
37641
|
+
typeof result === "symbol" && console.log("waitForReady timeout");
|
|
37642
|
+
return;
|
|
37643
|
+
}
|
|
37623
37644
|
for (let i = 0; i !== MAX_WAIT_SECONDS; i++) {
|
|
37624
37645
|
const [exchangeList, frameList, strategyList] = await Promise.all([
|
|
37625
37646
|
backtest.exchangeValidationService.list(),
|
|
@@ -37650,6 +37671,9 @@ async function waitForReady(isBacktest = true) {
|
|
|
37650
37671
|
await functoolsKit.sleep(SECOND_DELAY);
|
|
37651
37672
|
continue;
|
|
37652
37673
|
}
|
|
37674
|
+
if (i === MAX_WAIT_SECONDS - 1) {
|
|
37675
|
+
console.log("waitForReady timeout");
|
|
37676
|
+
}
|
|
37653
37677
|
break;
|
|
37654
37678
|
}
|
|
37655
37679
|
}
|
|
@@ -63835,6 +63859,34 @@ class CronUtils {
|
|
|
63835
63859
|
* on successful settle.
|
|
63836
63860
|
*/
|
|
63837
63861
|
this._firedOnce = new Set();
|
|
63862
|
+
/**
|
|
63863
|
+
* Last interval boundary already fired per periodic slot.
|
|
63864
|
+
*
|
|
63865
|
+
* Key shape (no `alignedMs` segment — one entry per logical slot, not per
|
|
63866
|
+
* boundary; always carries the generation suffix `:g${generation}`, and the
|
|
63867
|
+
* `:${symbol}` scope only in fan-out mode):
|
|
63868
|
+
* - Periodic global: `${name}${genSuffix}`.
|
|
63869
|
+
* - Periodic fan-out: `${name}:${symbol}${genSuffix}`.
|
|
63870
|
+
*
|
|
63871
|
+
* Value is the aligned-boundary epoch ms (`alignedMs`) most recently opened
|
|
63872
|
+
* for that slot. `_tick` fires a periodic entry whenever the incoming tick's
|
|
63873
|
+
* aligned boundary is **strictly greater** than the stored value, instead of
|
|
63874
|
+
* requiring the tick to land *exactly* on the boundary. This fixes the
|
|
63875
|
+
* dropped-boundary bug: when virtual time jumps over a boundary (e.g. a
|
|
63876
|
+
* `5m`-driven loop skipping from 00:14 to 00:29 never lands on the `15m`
|
|
63877
|
+
* 00:15 boundary), the old `ts === alignedMs` check silently lost the tick.
|
|
63878
|
+
* With the watermark, the next tick whose `alignedMs` advanced past the
|
|
63879
|
+
* stored value fires once for the newest crossed boundary (catch-up
|
|
63880
|
+
* collapses multiple skipped boundaries into a single invocation at the
|
|
63881
|
+
* latest one).
|
|
63882
|
+
*
|
|
63883
|
+
* Written synchronously in `_tick` at slot-open time (before the `await`),
|
|
63884
|
+
* so a still-in-flight handler does not let a later tick re-open the same
|
|
63885
|
+
* (or an already-passed) boundary. Fire-once entries never touch this map —
|
|
63886
|
+
* they use `_firedOnce`. Pruned by `_clearBoundaryFor` on
|
|
63887
|
+
* `register`/`unregister` and wiped by `dispose`.
|
|
63888
|
+
*/
|
|
63889
|
+
this._lastBoundary = new Map();
|
|
63838
63890
|
/**
|
|
63839
63891
|
* Register a periodic cron entry.
|
|
63840
63892
|
*
|
|
@@ -63880,6 +63932,7 @@ class CronUtils {
|
|
|
63880
63932
|
}
|
|
63881
63933
|
}
|
|
63882
63934
|
this._clearFiredOnceFor(entry.name);
|
|
63935
|
+
this._clearBoundaryFor(entry.name);
|
|
63883
63936
|
const generation = ++this._generationCounter;
|
|
63884
63937
|
this._entries.set(entry.name, { entry, generation });
|
|
63885
63938
|
return () => this.unregister(entry.name);
|
|
@@ -63896,6 +63949,7 @@ class CronUtils {
|
|
|
63896
63949
|
LOGGER_SERVICE$1.info(CRON_METHOD_NAME_UNREGISTER, { name });
|
|
63897
63950
|
this._entries.delete(name);
|
|
63898
63951
|
this._clearFiredOnceFor(name);
|
|
63952
|
+
this._clearBoundaryFor(name);
|
|
63899
63953
|
};
|
|
63900
63954
|
/**
|
|
63901
63955
|
* Clear fire-once marks so that fire-once entries can fire again.
|
|
@@ -63972,11 +64026,24 @@ class CronUtils {
|
|
|
63972
64026
|
* - Slot key: `${name}:once` (+ scope) (+ gen).
|
|
63973
64027
|
* - `aligned` = the 1-minute-aligned `when` from step 0.
|
|
63974
64028
|
* 5. **Periodic** (`entry.interval` set):
|
|
63975
|
-
* - Align `when`
|
|
63976
|
-
*
|
|
63977
|
-
*
|
|
63978
|
-
*
|
|
63979
|
-
*
|
|
64029
|
+
* - Align `when` to the entry's interval via {@link alignToInterval} to
|
|
64030
|
+
* get `alignedMs`, the boundary this tick belongs to.
|
|
64031
|
+
* - Compare against the slot's watermark in `_lastBoundary` (keyed by
|
|
64032
|
+
* `${name}` + scope + gen, without the `alignedMs` segment). If a
|
|
64033
|
+
* watermark exists and `alignedMs <= lastBoundary`, this boundary was
|
|
64034
|
+
* already fired — skip.
|
|
64035
|
+
* - This **watermark** check replaces the old exact `ts === alignedMs`
|
|
64036
|
+
* match. The exact match required virtual time to land *precisely* on
|
|
64037
|
+
* the boundary; when a tick jumped clean over a boundary (e.g. a `5m`
|
|
64038
|
+
* loop going 00:14 → 00:29 never touching the `15m` 00:15 boundary)
|
|
64039
|
+
* the boundary was silently lost. With the watermark, the first tick
|
|
64040
|
+
* whose `alignedMs` advanced past the stored value fires once, at the
|
|
64041
|
+
* newest crossed boundary (catch-up collapses several skipped
|
|
64042
|
+
* boundaries into a single invocation at the latest one).
|
|
64043
|
+
* - The watermark is advanced to `alignedMs` synchronously when the slot
|
|
64044
|
+
* is opened (before the `await`), so a concurrent tick on the same or
|
|
64045
|
+
* an already-passed boundary cannot open a duplicate slot while the
|
|
64046
|
+
* handler is still in flight.
|
|
63980
64047
|
* - Slot key: `${name}:${alignedMs}` (+ scope) (+ gen).
|
|
63981
64048
|
* 6. Singleshot per slot key: look up the slot in `_inFlight`. If a promise
|
|
63982
64049
|
* already exists, `await` the same promise. Otherwise invoke
|
|
@@ -64025,6 +64092,9 @@ class CronUtils {
|
|
|
64025
64092
|
let alignedMs;
|
|
64026
64093
|
let slotKey;
|
|
64027
64094
|
let firedKey;
|
|
64095
|
+
// Periodic-only watermark key (no `alignedMs` segment); null for
|
|
64096
|
+
// fire-once entries, which coordinate via `_firedOnce` instead.
|
|
64097
|
+
let boundaryKey;
|
|
64028
64098
|
if (entry.interval === undefined) {
|
|
64029
64099
|
const onceKey = `${entry.name}${scope}${genSuffix}`;
|
|
64030
64100
|
if (this._firedOnce.has(onceKey)) {
|
|
@@ -64034,11 +64104,18 @@ class CronUtils {
|
|
|
64034
64104
|
alignedMs = ts;
|
|
64035
64105
|
slotKey = `${entry.name}:once${scope}${genSuffix}`;
|
|
64036
64106
|
firedKey = onceKey;
|
|
64107
|
+
boundaryKey = null;
|
|
64037
64108
|
}
|
|
64038
64109
|
else {
|
|
64039
64110
|
aligned = alignToInterval(when, entry.interval);
|
|
64040
64111
|
alignedMs = aligned.getTime();
|
|
64041
|
-
|
|
64112
|
+
boundaryKey = `${entry.name}${scope}${genSuffix}`;
|
|
64113
|
+
const lastBoundary = this._lastBoundary.get(boundaryKey);
|
|
64114
|
+
// Fire when the tick's aligned boundary has advanced past the last one
|
|
64115
|
+
// we fired for this slot. Using `>` instead of the old `ts === alignedMs`
|
|
64116
|
+
// means a virtual-time jump that skips clean over a boundary still
|
|
64117
|
+
// fires once, at the newest crossed boundary, rather than dropping it.
|
|
64118
|
+
if (lastBoundary !== undefined && alignedMs <= lastBoundary) {
|
|
64042
64119
|
continue;
|
|
64043
64120
|
}
|
|
64044
64121
|
slotKey = `${entry.name}:${alignedMs}${scope}${genSuffix}`;
|
|
@@ -64046,6 +64123,13 @@ class CronUtils {
|
|
|
64046
64123
|
}
|
|
64047
64124
|
let pending = this._inFlight.get(slotKey);
|
|
64048
64125
|
if (!pending) {
|
|
64126
|
+
// Advance the watermark synchronously at slot-open time, before the
|
|
64127
|
+
// await below. Otherwise a later tick on the same (or an already
|
|
64128
|
+
// crossed) boundary, arriving while this handler is still in flight,
|
|
64129
|
+
// would see the stale watermark and open a duplicate slot.
|
|
64130
|
+
if (boundaryKey !== null) {
|
|
64131
|
+
this._lastBoundary.set(boundaryKey, alignedMs);
|
|
64132
|
+
}
|
|
64049
64133
|
pending = this._runEntry(entry, symbol, aligned, alignedMs, slotKey, firedKey, backtest);
|
|
64050
64134
|
this._inFlight.set(slotKey, pending);
|
|
64051
64135
|
}
|
|
@@ -64137,7 +64221,10 @@ class CronUtils {
|
|
|
64137
64221
|
* 3. Wipes `_firedOnce` — all fire-once marks are dropped, so any future
|
|
64138
64222
|
* re-registration of the same `name` fires again on the next matching
|
|
64139
64223
|
* tick.
|
|
64140
|
-
* 4.
|
|
64224
|
+
* 4. Wipes `_lastBoundary` — all periodic watermarks are dropped, so a
|
|
64225
|
+
* re-registered periodic entry starts firing from its next crossed
|
|
64226
|
+
* boundary again.
|
|
64227
|
+
* 5. Does **not** touch `_inFlight` — in-flight handlers continue to
|
|
64141
64228
|
* settle in the background and clear their own slots via `.finally()`.
|
|
64142
64229
|
* Their final `_firedOnce.add(firedKey)` writes carry old-generation
|
|
64143
64230
|
* keys and are harmless (lookup uses the post-dispose generation).
|
|
@@ -64156,8 +64243,29 @@ class CronUtils {
|
|
|
64156
64243
|
this.disable();
|
|
64157
64244
|
this._entries.clear();
|
|
64158
64245
|
this._firedOnce.clear();
|
|
64246
|
+
this._lastBoundary.clear();
|
|
64159
64247
|
};
|
|
64160
64248
|
}
|
|
64249
|
+
/**
|
|
64250
|
+
* Garbage-collect every `_lastBoundary` key that belongs to the entry `name`
|
|
64251
|
+
* (any generation, global or fan-out).
|
|
64252
|
+
*
|
|
64253
|
+
* Called from `register`/`unregister` alongside `_clearFiredOnceFor`. Like
|
|
64254
|
+
* that helper this is memory hygiene, not correctness — the generation suffix
|
|
64255
|
+
* already isolates re-registrations, so a stale watermark from an old
|
|
64256
|
+
* generation can never gate a new entry.
|
|
64257
|
+
*/
|
|
64258
|
+
_clearBoundaryFor(name) {
|
|
64259
|
+
if (!name) {
|
|
64260
|
+
return;
|
|
64261
|
+
}
|
|
64262
|
+
const prefix = `${name}:`;
|
|
64263
|
+
for (const key of this._lastBoundary.keys()) {
|
|
64264
|
+
if (key === name || key.startsWith(prefix)) {
|
|
64265
|
+
this._lastBoundary.delete(key);
|
|
64266
|
+
}
|
|
64267
|
+
}
|
|
64268
|
+
}
|
|
64161
64269
|
/**
|
|
64162
64270
|
* Garbage-collect every `_firedOnce` key that belongs to the entry `name`
|
|
64163
64271
|
* (any generation, global or fan-out).
|
package/build/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createActivator } from 'di-kit';
|
|
2
2
|
import { scoped } from 'di-scoped';
|
|
3
3
|
import { singleton } from 'di-singleton';
|
|
4
|
-
import { Subject, makeExtendable, singleshot, getErrorMessage, memoize, not, errorData, trycatch, retry, queued, sleep, randomString, str, isObject, ToolRegistry, typo, and, Source, resolveDocuments, timeout, TIMEOUT_SYMBOL as TIMEOUT_SYMBOL$
|
|
4
|
+
import { Subject, BehaviorSubject, makeExtendable, singleshot, getErrorMessage, memoize, not, errorData, trycatch, retry, queued, sleep, randomString, str, isObject, ToolRegistry, typo, and, Source, resolveDocuments, timeout, TIMEOUT_SYMBOL as TIMEOUT_SYMBOL$2, compose, waitForNext, singlerun } from 'functools-kit';
|
|
5
5
|
import * as fs from 'fs/promises';
|
|
6
6
|
import fs__default from 'fs/promises';
|
|
7
7
|
import path, { join, dirname } from 'path';
|
|
@@ -798,6 +798,13 @@ const beforeStartSubject = new Subject();
|
|
|
798
798
|
* Emits when the engine has completed processing a signal.
|
|
799
799
|
*/
|
|
800
800
|
const afterEndSubject = new Subject();
|
|
801
|
+
/**
|
|
802
|
+
* Emitter for `@backtest-kit/cli`, which notifies the application
|
|
803
|
+
* that all modules have been initialized.
|
|
804
|
+
*
|
|
805
|
+
* Send entry absolute path to the consumer
|
|
806
|
+
*/
|
|
807
|
+
const entrySubject = new BehaviorSubject();
|
|
801
808
|
|
|
802
809
|
var emitters = /*#__PURE__*/Object.freeze({
|
|
803
810
|
__proto__: null,
|
|
@@ -809,6 +816,7 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
809
816
|
doneBacktestSubject: doneBacktestSubject,
|
|
810
817
|
doneLiveSubject: doneLiveSubject,
|
|
811
818
|
doneWalkerSubject: doneWalkerSubject,
|
|
819
|
+
entrySubject: entrySubject,
|
|
812
820
|
errorEmitter: errorEmitter,
|
|
813
821
|
exitEmitter: exitEmitter,
|
|
814
822
|
highestProfitSubject: highestProfitSubject,
|
|
@@ -6564,7 +6572,7 @@ const INTERVAL_MINUTES$8 = {
|
|
|
6564
6572
|
* Used to indicate that the actual pendingAt will be set upon activation.
|
|
6565
6573
|
*/
|
|
6566
6574
|
const SCHEDULED_SIGNAL_PENDING_MOCK = 0;
|
|
6567
|
-
const TIMEOUT_SYMBOL = Symbol('timeout');
|
|
6575
|
+
const TIMEOUT_SYMBOL$1 = Symbol('timeout');
|
|
6568
6576
|
/**
|
|
6569
6577
|
* Calls onSignalSync callback for signal-open event.
|
|
6570
6578
|
*
|
|
@@ -6986,7 +6994,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
6986
6994
|
const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
|
|
6987
6995
|
const signal = await Promise.race([
|
|
6988
6996
|
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when, currentPrice),
|
|
6989
|
-
sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
|
|
6997
|
+
sleep(timeoutMs).then(() => TIMEOUT_SYMBOL$1),
|
|
6990
6998
|
]);
|
|
6991
6999
|
if (typeof signal === "symbol") {
|
|
6992
7000
|
throw new Error(`Timeout for ${self.params.method.context.strategyName} symbol=${self.params.execution.context.symbol}`);
|
|
@@ -23252,7 +23260,7 @@ class MarkdownFileBase {
|
|
|
23252
23260
|
timestamp: getContextTimestamp(),
|
|
23253
23261
|
}) + "\n";
|
|
23254
23262
|
const status = await this[WRITE_SAFE_SYMBOL$1](line);
|
|
23255
|
-
if (status === TIMEOUT_SYMBOL$
|
|
23263
|
+
if (status === TIMEOUT_SYMBOL$2) {
|
|
23256
23264
|
throw new Error(`Timeout writing to markdown ${this.markdownName}`);
|
|
23257
23265
|
}
|
|
23258
23266
|
}
|
|
@@ -23545,7 +23553,7 @@ class ReportBase {
|
|
|
23545
23553
|
timestamp: getContextTimestamp(),
|
|
23546
23554
|
}) + "\n";
|
|
23547
23555
|
const status = await this[WRITE_SAFE_SYMBOL$1](line);
|
|
23548
|
-
if (status === TIMEOUT_SYMBOL$
|
|
23556
|
+
if (status === TIMEOUT_SYMBOL$2) {
|
|
23549
23557
|
throw new Error(`Timeout writing to report ${this.reportName}`);
|
|
23550
23558
|
}
|
|
23551
23559
|
}
|
|
@@ -37561,8 +37569,9 @@ function getActionSchema(actionName) {
|
|
|
37561
37569
|
}
|
|
37562
37570
|
|
|
37563
37571
|
const WAIT_FOR_READY_METHOD_NAME = "init.waitForReady";
|
|
37564
|
-
const MAX_WAIT_SECONDS =
|
|
37572
|
+
const MAX_WAIT_SECONDS = 45;
|
|
37565
37573
|
const SECOND_DELAY = 1000;
|
|
37574
|
+
const TIMEOUT_SYMBOL = Symbol('timeout');
|
|
37566
37575
|
/**
|
|
37567
37576
|
* Blocks until the schema registries needed to start trading are populated.
|
|
37568
37577
|
*
|
|
@@ -37600,6 +37609,18 @@ const SECOND_DELAY = 1000;
|
|
|
37600
37609
|
*/
|
|
37601
37610
|
async function waitForReady(isBacktest = true) {
|
|
37602
37611
|
backtest.loggerService.info(WAIT_FOR_READY_METHOD_NAME, { isBacktest });
|
|
37612
|
+
if (entrySubject.data) {
|
|
37613
|
+
return;
|
|
37614
|
+
}
|
|
37615
|
+
if (entrySubject.hasListeners) {
|
|
37616
|
+
backtest.loggerService.debug(`${WAIT_FOR_READY_METHOD_NAME} waiting for entrySubject`);
|
|
37617
|
+
const result = await Promise.race([
|
|
37618
|
+
entrySubject.toPromise(),
|
|
37619
|
+
sleep(MAX_WAIT_SECONDS * SECOND_DELAY).then(() => TIMEOUT_SYMBOL)
|
|
37620
|
+
]);
|
|
37621
|
+
typeof result === "symbol" && console.log("waitForReady timeout");
|
|
37622
|
+
return;
|
|
37623
|
+
}
|
|
37603
37624
|
for (let i = 0; i !== MAX_WAIT_SECONDS; i++) {
|
|
37604
37625
|
const [exchangeList, frameList, strategyList] = await Promise.all([
|
|
37605
37626
|
backtest.exchangeValidationService.list(),
|
|
@@ -37630,6 +37651,9 @@ async function waitForReady(isBacktest = true) {
|
|
|
37630
37651
|
await sleep(SECOND_DELAY);
|
|
37631
37652
|
continue;
|
|
37632
37653
|
}
|
|
37654
|
+
if (i === MAX_WAIT_SECONDS - 1) {
|
|
37655
|
+
console.log("waitForReady timeout");
|
|
37656
|
+
}
|
|
37633
37657
|
break;
|
|
37634
37658
|
}
|
|
37635
37659
|
}
|
|
@@ -55421,7 +55445,7 @@ class LogJsonlUtils {
|
|
|
55421
55445
|
await this[WAIT_FOR_INIT_SYMBOL]();
|
|
55422
55446
|
const line = JSON.stringify(entry) + "\n";
|
|
55423
55447
|
const status = await this[WRITE_SAFE_SYMBOL](line);
|
|
55424
|
-
if (status === TIMEOUT_SYMBOL$
|
|
55448
|
+
if (status === TIMEOUT_SYMBOL$2) {
|
|
55425
55449
|
throw new Error(`LogJsonlUtils timeout writing to file=${this._filePath}`);
|
|
55426
55450
|
}
|
|
55427
55451
|
};
|
|
@@ -63815,6 +63839,34 @@ class CronUtils {
|
|
|
63815
63839
|
* on successful settle.
|
|
63816
63840
|
*/
|
|
63817
63841
|
this._firedOnce = new Set();
|
|
63842
|
+
/**
|
|
63843
|
+
* Last interval boundary already fired per periodic slot.
|
|
63844
|
+
*
|
|
63845
|
+
* Key shape (no `alignedMs` segment — one entry per logical slot, not per
|
|
63846
|
+
* boundary; always carries the generation suffix `:g${generation}`, and the
|
|
63847
|
+
* `:${symbol}` scope only in fan-out mode):
|
|
63848
|
+
* - Periodic global: `${name}${genSuffix}`.
|
|
63849
|
+
* - Periodic fan-out: `${name}:${symbol}${genSuffix}`.
|
|
63850
|
+
*
|
|
63851
|
+
* Value is the aligned-boundary epoch ms (`alignedMs`) most recently opened
|
|
63852
|
+
* for that slot. `_tick` fires a periodic entry whenever the incoming tick's
|
|
63853
|
+
* aligned boundary is **strictly greater** than the stored value, instead of
|
|
63854
|
+
* requiring the tick to land *exactly* on the boundary. This fixes the
|
|
63855
|
+
* dropped-boundary bug: when virtual time jumps over a boundary (e.g. a
|
|
63856
|
+
* `5m`-driven loop skipping from 00:14 to 00:29 never lands on the `15m`
|
|
63857
|
+
* 00:15 boundary), the old `ts === alignedMs` check silently lost the tick.
|
|
63858
|
+
* With the watermark, the next tick whose `alignedMs` advanced past the
|
|
63859
|
+
* stored value fires once for the newest crossed boundary (catch-up
|
|
63860
|
+
* collapses multiple skipped boundaries into a single invocation at the
|
|
63861
|
+
* latest one).
|
|
63862
|
+
*
|
|
63863
|
+
* Written synchronously in `_tick` at slot-open time (before the `await`),
|
|
63864
|
+
* so a still-in-flight handler does not let a later tick re-open the same
|
|
63865
|
+
* (or an already-passed) boundary. Fire-once entries never touch this map —
|
|
63866
|
+
* they use `_firedOnce`. Pruned by `_clearBoundaryFor` on
|
|
63867
|
+
* `register`/`unregister` and wiped by `dispose`.
|
|
63868
|
+
*/
|
|
63869
|
+
this._lastBoundary = new Map();
|
|
63818
63870
|
/**
|
|
63819
63871
|
* Register a periodic cron entry.
|
|
63820
63872
|
*
|
|
@@ -63860,6 +63912,7 @@ class CronUtils {
|
|
|
63860
63912
|
}
|
|
63861
63913
|
}
|
|
63862
63914
|
this._clearFiredOnceFor(entry.name);
|
|
63915
|
+
this._clearBoundaryFor(entry.name);
|
|
63863
63916
|
const generation = ++this._generationCounter;
|
|
63864
63917
|
this._entries.set(entry.name, { entry, generation });
|
|
63865
63918
|
return () => this.unregister(entry.name);
|
|
@@ -63876,6 +63929,7 @@ class CronUtils {
|
|
|
63876
63929
|
LOGGER_SERVICE$1.info(CRON_METHOD_NAME_UNREGISTER, { name });
|
|
63877
63930
|
this._entries.delete(name);
|
|
63878
63931
|
this._clearFiredOnceFor(name);
|
|
63932
|
+
this._clearBoundaryFor(name);
|
|
63879
63933
|
};
|
|
63880
63934
|
/**
|
|
63881
63935
|
* Clear fire-once marks so that fire-once entries can fire again.
|
|
@@ -63952,11 +64006,24 @@ class CronUtils {
|
|
|
63952
64006
|
* - Slot key: `${name}:once` (+ scope) (+ gen).
|
|
63953
64007
|
* - `aligned` = the 1-minute-aligned `when` from step 0.
|
|
63954
64008
|
* 5. **Periodic** (`entry.interval` set):
|
|
63955
|
-
* - Align `when`
|
|
63956
|
-
*
|
|
63957
|
-
*
|
|
63958
|
-
*
|
|
63959
|
-
*
|
|
64009
|
+
* - Align `when` to the entry's interval via {@link alignToInterval} to
|
|
64010
|
+
* get `alignedMs`, the boundary this tick belongs to.
|
|
64011
|
+
* - Compare against the slot's watermark in `_lastBoundary` (keyed by
|
|
64012
|
+
* `${name}` + scope + gen, without the `alignedMs` segment). If a
|
|
64013
|
+
* watermark exists and `alignedMs <= lastBoundary`, this boundary was
|
|
64014
|
+
* already fired — skip.
|
|
64015
|
+
* - This **watermark** check replaces the old exact `ts === alignedMs`
|
|
64016
|
+
* match. The exact match required virtual time to land *precisely* on
|
|
64017
|
+
* the boundary; when a tick jumped clean over a boundary (e.g. a `5m`
|
|
64018
|
+
* loop going 00:14 → 00:29 never touching the `15m` 00:15 boundary)
|
|
64019
|
+
* the boundary was silently lost. With the watermark, the first tick
|
|
64020
|
+
* whose `alignedMs` advanced past the stored value fires once, at the
|
|
64021
|
+
* newest crossed boundary (catch-up collapses several skipped
|
|
64022
|
+
* boundaries into a single invocation at the latest one).
|
|
64023
|
+
* - The watermark is advanced to `alignedMs` synchronously when the slot
|
|
64024
|
+
* is opened (before the `await`), so a concurrent tick on the same or
|
|
64025
|
+
* an already-passed boundary cannot open a duplicate slot while the
|
|
64026
|
+
* handler is still in flight.
|
|
63960
64027
|
* - Slot key: `${name}:${alignedMs}` (+ scope) (+ gen).
|
|
63961
64028
|
* 6. Singleshot per slot key: look up the slot in `_inFlight`. If a promise
|
|
63962
64029
|
* already exists, `await` the same promise. Otherwise invoke
|
|
@@ -64005,6 +64072,9 @@ class CronUtils {
|
|
|
64005
64072
|
let alignedMs;
|
|
64006
64073
|
let slotKey;
|
|
64007
64074
|
let firedKey;
|
|
64075
|
+
// Periodic-only watermark key (no `alignedMs` segment); null for
|
|
64076
|
+
// fire-once entries, which coordinate via `_firedOnce` instead.
|
|
64077
|
+
let boundaryKey;
|
|
64008
64078
|
if (entry.interval === undefined) {
|
|
64009
64079
|
const onceKey = `${entry.name}${scope}${genSuffix}`;
|
|
64010
64080
|
if (this._firedOnce.has(onceKey)) {
|
|
@@ -64014,11 +64084,18 @@ class CronUtils {
|
|
|
64014
64084
|
alignedMs = ts;
|
|
64015
64085
|
slotKey = `${entry.name}:once${scope}${genSuffix}`;
|
|
64016
64086
|
firedKey = onceKey;
|
|
64087
|
+
boundaryKey = null;
|
|
64017
64088
|
}
|
|
64018
64089
|
else {
|
|
64019
64090
|
aligned = alignToInterval(when, entry.interval);
|
|
64020
64091
|
alignedMs = aligned.getTime();
|
|
64021
|
-
|
|
64092
|
+
boundaryKey = `${entry.name}${scope}${genSuffix}`;
|
|
64093
|
+
const lastBoundary = this._lastBoundary.get(boundaryKey);
|
|
64094
|
+
// Fire when the tick's aligned boundary has advanced past the last one
|
|
64095
|
+
// we fired for this slot. Using `>` instead of the old `ts === alignedMs`
|
|
64096
|
+
// means a virtual-time jump that skips clean over a boundary still
|
|
64097
|
+
// fires once, at the newest crossed boundary, rather than dropping it.
|
|
64098
|
+
if (lastBoundary !== undefined && alignedMs <= lastBoundary) {
|
|
64022
64099
|
continue;
|
|
64023
64100
|
}
|
|
64024
64101
|
slotKey = `${entry.name}:${alignedMs}${scope}${genSuffix}`;
|
|
@@ -64026,6 +64103,13 @@ class CronUtils {
|
|
|
64026
64103
|
}
|
|
64027
64104
|
let pending = this._inFlight.get(slotKey);
|
|
64028
64105
|
if (!pending) {
|
|
64106
|
+
// Advance the watermark synchronously at slot-open time, before the
|
|
64107
|
+
// await below. Otherwise a later tick on the same (or an already
|
|
64108
|
+
// crossed) boundary, arriving while this handler is still in flight,
|
|
64109
|
+
// would see the stale watermark and open a duplicate slot.
|
|
64110
|
+
if (boundaryKey !== null) {
|
|
64111
|
+
this._lastBoundary.set(boundaryKey, alignedMs);
|
|
64112
|
+
}
|
|
64029
64113
|
pending = this._runEntry(entry, symbol, aligned, alignedMs, slotKey, firedKey, backtest);
|
|
64030
64114
|
this._inFlight.set(slotKey, pending);
|
|
64031
64115
|
}
|
|
@@ -64117,7 +64201,10 @@ class CronUtils {
|
|
|
64117
64201
|
* 3. Wipes `_firedOnce` — all fire-once marks are dropped, so any future
|
|
64118
64202
|
* re-registration of the same `name` fires again on the next matching
|
|
64119
64203
|
* tick.
|
|
64120
|
-
* 4.
|
|
64204
|
+
* 4. Wipes `_lastBoundary` — all periodic watermarks are dropped, so a
|
|
64205
|
+
* re-registered periodic entry starts firing from its next crossed
|
|
64206
|
+
* boundary again.
|
|
64207
|
+
* 5. Does **not** touch `_inFlight` — in-flight handlers continue to
|
|
64121
64208
|
* settle in the background and clear their own slots via `.finally()`.
|
|
64122
64209
|
* Their final `_firedOnce.add(firedKey)` writes carry old-generation
|
|
64123
64210
|
* keys and are harmless (lookup uses the post-dispose generation).
|
|
@@ -64136,8 +64223,29 @@ class CronUtils {
|
|
|
64136
64223
|
this.disable();
|
|
64137
64224
|
this._entries.clear();
|
|
64138
64225
|
this._firedOnce.clear();
|
|
64226
|
+
this._lastBoundary.clear();
|
|
64139
64227
|
};
|
|
64140
64228
|
}
|
|
64229
|
+
/**
|
|
64230
|
+
* Garbage-collect every `_lastBoundary` key that belongs to the entry `name`
|
|
64231
|
+
* (any generation, global or fan-out).
|
|
64232
|
+
*
|
|
64233
|
+
* Called from `register`/`unregister` alongside `_clearFiredOnceFor`. Like
|
|
64234
|
+
* that helper this is memory hygiene, not correctness — the generation suffix
|
|
64235
|
+
* already isolates re-registrations, so a stale watermark from an old
|
|
64236
|
+
* generation can never gate a new entry.
|
|
64237
|
+
*/
|
|
64238
|
+
_clearBoundaryFor(name) {
|
|
64239
|
+
if (!name) {
|
|
64240
|
+
return;
|
|
64241
|
+
}
|
|
64242
|
+
const prefix = `${name}:`;
|
|
64243
|
+
for (const key of this._lastBoundary.keys()) {
|
|
64244
|
+
if (key === name || key.startsWith(prefix)) {
|
|
64245
|
+
this._lastBoundary.delete(key);
|
|
64246
|
+
}
|
|
64247
|
+
}
|
|
64248
|
+
}
|
|
64141
64249
|
/**
|
|
64142
64250
|
* Garbage-collect every `_firedOnce` key that belongs to the entry `name`
|
|
64143
64251
|
* (any generation, global or fan-out).
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as di_scoped from 'di-scoped';
|
|
2
2
|
import * as functools_kit from 'functools-kit';
|
|
3
|
-
import { Subject } from 'functools-kit';
|
|
3
|
+
import { Subject, BehaviorSubject } from 'functools-kit';
|
|
4
4
|
import { WriteStream } from 'fs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -26322,6 +26322,44 @@ declare class CronUtils {
|
|
|
26322
26322
|
* on successful settle.
|
|
26323
26323
|
*/
|
|
26324
26324
|
private readonly _firedOnce;
|
|
26325
|
+
/**
|
|
26326
|
+
* Last interval boundary already fired per periodic slot.
|
|
26327
|
+
*
|
|
26328
|
+
* Key shape (no `alignedMs` segment — one entry per logical slot, not per
|
|
26329
|
+
* boundary; always carries the generation suffix `:g${generation}`, and the
|
|
26330
|
+
* `:${symbol}` scope only in fan-out mode):
|
|
26331
|
+
* - Periodic global: `${name}${genSuffix}`.
|
|
26332
|
+
* - Periodic fan-out: `${name}:${symbol}${genSuffix}`.
|
|
26333
|
+
*
|
|
26334
|
+
* Value is the aligned-boundary epoch ms (`alignedMs`) most recently opened
|
|
26335
|
+
* for that slot. `_tick` fires a periodic entry whenever the incoming tick's
|
|
26336
|
+
* aligned boundary is **strictly greater** than the stored value, instead of
|
|
26337
|
+
* requiring the tick to land *exactly* on the boundary. This fixes the
|
|
26338
|
+
* dropped-boundary bug: when virtual time jumps over a boundary (e.g. a
|
|
26339
|
+
* `5m`-driven loop skipping from 00:14 to 00:29 never lands on the `15m`
|
|
26340
|
+
* 00:15 boundary), the old `ts === alignedMs` check silently lost the tick.
|
|
26341
|
+
* With the watermark, the next tick whose `alignedMs` advanced past the
|
|
26342
|
+
* stored value fires once for the newest crossed boundary (catch-up
|
|
26343
|
+
* collapses multiple skipped boundaries into a single invocation at the
|
|
26344
|
+
* latest one).
|
|
26345
|
+
*
|
|
26346
|
+
* Written synchronously in `_tick` at slot-open time (before the `await`),
|
|
26347
|
+
* so a still-in-flight handler does not let a later tick re-open the same
|
|
26348
|
+
* (or an already-passed) boundary. Fire-once entries never touch this map —
|
|
26349
|
+
* they use `_firedOnce`. Pruned by `_clearBoundaryFor` on
|
|
26350
|
+
* `register`/`unregister` and wiped by `dispose`.
|
|
26351
|
+
*/
|
|
26352
|
+
private readonly _lastBoundary;
|
|
26353
|
+
/**
|
|
26354
|
+
* Garbage-collect every `_lastBoundary` key that belongs to the entry `name`
|
|
26355
|
+
* (any generation, global or fan-out).
|
|
26356
|
+
*
|
|
26357
|
+
* Called from `register`/`unregister` alongside `_clearFiredOnceFor`. Like
|
|
26358
|
+
* that helper this is memory hygiene, not correctness — the generation suffix
|
|
26359
|
+
* already isolates re-registrations, so a stale watermark from an old
|
|
26360
|
+
* generation can never gate a new entry.
|
|
26361
|
+
*/
|
|
26362
|
+
private _clearBoundaryFor;
|
|
26325
26363
|
/**
|
|
26326
26364
|
* Garbage-collect every `_firedOnce` key that belongs to the entry `name`
|
|
26327
26365
|
* (any generation, global or fan-out).
|
|
@@ -26443,11 +26481,24 @@ declare class CronUtils {
|
|
|
26443
26481
|
* - Slot key: `${name}:once` (+ scope) (+ gen).
|
|
26444
26482
|
* - `aligned` = the 1-minute-aligned `when` from step 0.
|
|
26445
26483
|
* 5. **Periodic** (`entry.interval` set):
|
|
26446
|
-
* - Align `when`
|
|
26447
|
-
*
|
|
26448
|
-
*
|
|
26449
|
-
*
|
|
26450
|
-
*
|
|
26484
|
+
* - Align `when` to the entry's interval via {@link alignToInterval} to
|
|
26485
|
+
* get `alignedMs`, the boundary this tick belongs to.
|
|
26486
|
+
* - Compare against the slot's watermark in `_lastBoundary` (keyed by
|
|
26487
|
+
* `${name}` + scope + gen, without the `alignedMs` segment). If a
|
|
26488
|
+
* watermark exists and `alignedMs <= lastBoundary`, this boundary was
|
|
26489
|
+
* already fired — skip.
|
|
26490
|
+
* - This **watermark** check replaces the old exact `ts === alignedMs`
|
|
26491
|
+
* match. The exact match required virtual time to land *precisely* on
|
|
26492
|
+
* the boundary; when a tick jumped clean over a boundary (e.g. a `5m`
|
|
26493
|
+
* loop going 00:14 → 00:29 never touching the `15m` 00:15 boundary)
|
|
26494
|
+
* the boundary was silently lost. With the watermark, the first tick
|
|
26495
|
+
* whose `alignedMs` advanced past the stored value fires once, at the
|
|
26496
|
+
* newest crossed boundary (catch-up collapses several skipped
|
|
26497
|
+
* boundaries into a single invocation at the latest one).
|
|
26498
|
+
* - The watermark is advanced to `alignedMs` synchronously when the slot
|
|
26499
|
+
* is opened (before the `await`), so a concurrent tick on the same or
|
|
26500
|
+
* an already-passed boundary cannot open a duplicate slot while the
|
|
26501
|
+
* handler is still in flight.
|
|
26451
26502
|
* - Slot key: `${name}:${alignedMs}` (+ scope) (+ gen).
|
|
26452
26503
|
* 6. Singleshot per slot key: look up the slot in `_inFlight`. If a promise
|
|
26453
26504
|
* already exists, `await` the same promise. Otherwise invoke
|
|
@@ -26541,7 +26592,10 @@ declare class CronUtils {
|
|
|
26541
26592
|
* 3. Wipes `_firedOnce` — all fire-once marks are dropped, so any future
|
|
26542
26593
|
* re-registration of the same `name` fires again on the next matching
|
|
26543
26594
|
* tick.
|
|
26544
|
-
* 4.
|
|
26595
|
+
* 4. Wipes `_lastBoundary` — all periodic watermarks are dropped, so a
|
|
26596
|
+
* re-registered periodic entry starts firing from its next crossed
|
|
26597
|
+
* boundary again.
|
|
26598
|
+
* 5. Does **not** touch `_inFlight` — in-flight handlers continue to
|
|
26545
26599
|
* settle in the background and clear their own slots via `.finally()`.
|
|
26546
26600
|
* Their final `_firedOnce.add(firedKey)` writes carry old-generation
|
|
26547
26601
|
* keys and are harmless (lookup uses the post-dispose generation).
|
|
@@ -29091,6 +29145,13 @@ declare const beforeStartSubject: Subject<BeforeStartContract>;
|
|
|
29091
29145
|
* Emits when the engine has completed processing a signal.
|
|
29092
29146
|
*/
|
|
29093
29147
|
declare const afterEndSubject: Subject<AfterEndContract>;
|
|
29148
|
+
/**
|
|
29149
|
+
* Emitter for `@backtest-kit/cli`, which notifies the application
|
|
29150
|
+
* that all modules have been initialized.
|
|
29151
|
+
*
|
|
29152
|
+
* Send entry absolute path to the consumer
|
|
29153
|
+
*/
|
|
29154
|
+
declare const entrySubject: BehaviorSubject<string>;
|
|
29094
29155
|
|
|
29095
29156
|
declare const emitters_activePingSubject: typeof activePingSubject;
|
|
29096
29157
|
declare const emitters_afterEndSubject: typeof afterEndSubject;
|
|
@@ -29100,6 +29161,7 @@ declare const emitters_breakevenSubject: typeof breakevenSubject;
|
|
|
29100
29161
|
declare const emitters_doneBacktestSubject: typeof doneBacktestSubject;
|
|
29101
29162
|
declare const emitters_doneLiveSubject: typeof doneLiveSubject;
|
|
29102
29163
|
declare const emitters_doneWalkerSubject: typeof doneWalkerSubject;
|
|
29164
|
+
declare const emitters_entrySubject: typeof entrySubject;
|
|
29103
29165
|
declare const emitters_errorEmitter: typeof errorEmitter;
|
|
29104
29166
|
declare const emitters_exitEmitter: typeof exitEmitter;
|
|
29105
29167
|
declare const emitters_highestProfitSubject: typeof highestProfitSubject;
|
|
@@ -29124,7 +29186,7 @@ declare const emitters_walkerCompleteSubject: typeof walkerCompleteSubject;
|
|
|
29124
29186
|
declare const emitters_walkerEmitter: typeof walkerEmitter;
|
|
29125
29187
|
declare const emitters_walkerStopSubject: typeof walkerStopSubject;
|
|
29126
29188
|
declare namespace emitters {
|
|
29127
|
-
export { emitters_activePingSubject as activePingSubject, emitters_afterEndSubject as afterEndSubject, emitters_backtestScheduleOpenSubject as backtestScheduleOpenSubject, emitters_beforeStartSubject as beforeStartSubject, emitters_breakevenSubject as breakevenSubject, emitters_doneBacktestSubject as doneBacktestSubject, emitters_doneLiveSubject as doneLiveSubject, emitters_doneWalkerSubject as doneWalkerSubject, emitters_errorEmitter as errorEmitter, emitters_exitEmitter as exitEmitter, emitters_highestProfitSubject as highestProfitSubject, emitters_idlePingSubject as idlePingSubject, emitters_maxDrawdownSubject as maxDrawdownSubject, emitters_partialLossSubject as partialLossSubject, emitters_partialProfitSubject as partialProfitSubject, emitters_performanceEmitter as performanceEmitter, emitters_progressBacktestEmitter as progressBacktestEmitter, emitters_progressWalkerEmitter as progressWalkerEmitter, emitters_riskSubject as riskSubject, emitters_schedulePingSubject as schedulePingSubject, emitters_shutdownEmitter as shutdownEmitter, emitters_signalBacktestEmitter as signalBacktestEmitter, emitters_signalEmitter as signalEmitter, emitters_signalLiveEmitter as signalLiveEmitter, emitters_signalNotifySubject as signalNotifySubject, emitters_strategyCommitSubject as strategyCommitSubject, emitters_syncSubject as syncSubject, emitters_validationSubject as validationSubject, emitters_walkerCompleteSubject as walkerCompleteSubject, emitters_walkerEmitter as walkerEmitter, emitters_walkerStopSubject as walkerStopSubject };
|
|
29189
|
+
export { emitters_activePingSubject as activePingSubject, emitters_afterEndSubject as afterEndSubject, emitters_backtestScheduleOpenSubject as backtestScheduleOpenSubject, emitters_beforeStartSubject as beforeStartSubject, emitters_breakevenSubject as breakevenSubject, emitters_doneBacktestSubject as doneBacktestSubject, emitters_doneLiveSubject as doneLiveSubject, emitters_doneWalkerSubject as doneWalkerSubject, emitters_entrySubject as entrySubject, emitters_errorEmitter as errorEmitter, emitters_exitEmitter as exitEmitter, emitters_highestProfitSubject as highestProfitSubject, emitters_idlePingSubject as idlePingSubject, emitters_maxDrawdownSubject as maxDrawdownSubject, emitters_partialLossSubject as partialLossSubject, emitters_partialProfitSubject as partialProfitSubject, emitters_performanceEmitter as performanceEmitter, emitters_progressBacktestEmitter as progressBacktestEmitter, emitters_progressWalkerEmitter as progressWalkerEmitter, emitters_riskSubject as riskSubject, emitters_schedulePingSubject as schedulePingSubject, emitters_shutdownEmitter as shutdownEmitter, emitters_signalBacktestEmitter as signalBacktestEmitter, emitters_signalEmitter as signalEmitter, emitters_signalLiveEmitter as signalLiveEmitter, emitters_signalNotifySubject as signalNotifySubject, emitters_strategyCommitSubject as strategyCommitSubject, emitters_syncSubject as syncSubject, emitters_validationSubject as validationSubject, emitters_walkerCompleteSubject as walkerCompleteSubject, emitters_walkerEmitter as walkerEmitter, emitters_walkerStopSubject as walkerStopSubject };
|
|
29128
29190
|
}
|
|
29129
29191
|
|
|
29130
29192
|
/**
|