backtest-kit 6.15.0 → 6.16.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 +419 -1
- package/build/index.mjs +417 -2
- package/package.json +1 -1
- package/types.d.ts +228 -2
package/build/index.cjs
CHANGED
|
@@ -734,6 +734,11 @@ const schedulePingSubject = new functoolsKit.Subject();
|
|
|
734
734
|
* Allows users to track active signal lifecycle and implement custom dynamic management logic.
|
|
735
735
|
*/
|
|
736
736
|
const activePingSubject = new functoolsKit.Subject();
|
|
737
|
+
/**
|
|
738
|
+
* Idle ping emitter for strategy idle state events.
|
|
739
|
+
* Emits every tick when there is no pending or scheduled signal being monitored.
|
|
740
|
+
*/
|
|
741
|
+
const idlePingSubject = new functoolsKit.Subject();
|
|
737
742
|
/**
|
|
738
743
|
* Strategy management signal emitter.
|
|
739
744
|
* Emits when strategy management actions are executed:
|
|
@@ -785,6 +790,7 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
785
790
|
errorEmitter: errorEmitter,
|
|
786
791
|
exitEmitter: exitEmitter,
|
|
787
792
|
highestProfitSubject: highestProfitSubject,
|
|
793
|
+
idlePingSubject: idlePingSubject,
|
|
788
794
|
maxDrawdownSubject: maxDrawdownSubject,
|
|
789
795
|
partialLossSubject: partialLossSubject,
|
|
790
796
|
partialProfitSubject: partialProfitSubject,
|
|
@@ -6048,6 +6054,27 @@ const CALL_ACTIVE_PING_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (sel
|
|
|
6048
6054
|
errorEmitter.next(error);
|
|
6049
6055
|
},
|
|
6050
6056
|
});
|
|
6057
|
+
const CALL_IDLE_PING_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, timestamp, backtest, currentPrice) => {
|
|
6058
|
+
await ExecutionContextService.runInContext(async () => {
|
|
6059
|
+
// Call system onIdlePing callback (emits to idlePingSubject)
|
|
6060
|
+
await self.params.onIdlePing(self.params.execution.context.symbol, self.params.method.context.strategyName, self.params.method.context.exchangeName, currentPrice, self.params.execution.context.backtest, timestamp);
|
|
6061
|
+
}, {
|
|
6062
|
+
when: new Date(timestamp),
|
|
6063
|
+
symbol: symbol,
|
|
6064
|
+
backtest: backtest,
|
|
6065
|
+
});
|
|
6066
|
+
}), {
|
|
6067
|
+
fallback: (error, self) => {
|
|
6068
|
+
const message = "ClientStrategy CALL_IDLE_PING_CALLBACKS_FN thrown";
|
|
6069
|
+
const payload = {
|
|
6070
|
+
error: functoolsKit.errorData(error),
|
|
6071
|
+
message: functoolsKit.getErrorMessage(error),
|
|
6072
|
+
};
|
|
6073
|
+
self.params.logger.warn(message, payload);
|
|
6074
|
+
console.warn(message, payload);
|
|
6075
|
+
errorEmitter.next(error);
|
|
6076
|
+
},
|
|
6077
|
+
});
|
|
6051
6078
|
const CALL_ACTIVE_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
|
|
6052
6079
|
await ExecutionContextService.runInContext(async () => {
|
|
6053
6080
|
if (self.params.callbacks?.onActive) {
|
|
@@ -6703,6 +6730,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice, backt
|
|
|
6703
6730
|
};
|
|
6704
6731
|
const RETURN_IDLE_FN = async (self, currentPrice) => {
|
|
6705
6732
|
const currentTime = self.params.execution.context.when.getTime();
|
|
6733
|
+
await CALL_IDLE_PING_CALLBACKS_FN(self, self.params.execution.context.symbol, currentTime, self.params.execution.context.backtest, currentPrice);
|
|
6706
6734
|
await CALL_IDLE_CALLBACKS_FN(self, self.params.execution.context.symbol, currentPrice, currentTime, self.params.execution.context.backtest);
|
|
6707
6735
|
const result = {
|
|
6708
6736
|
action: "idle",
|
|
@@ -10375,11 +10403,44 @@ const CREATE_COMMIT_SCHEDULE_PING_FN = (self) => functoolsKit.trycatch(async (sy
|
|
|
10375
10403
|
},
|
|
10376
10404
|
defaultValue: null,
|
|
10377
10405
|
});
|
|
10406
|
+
/**
|
|
10407
|
+
* Creates a callback function for emitting idle ping events.
|
|
10408
|
+
*
|
|
10409
|
+
* Called by ClientStrategy when no active or scheduled signals are present.
|
|
10410
|
+
*
|
|
10411
|
+
* @param self - Reference to StrategyConnectionService instance
|
|
10412
|
+
* @returns Callback function for idle ping events
|
|
10413
|
+
*/
|
|
10414
|
+
const CREATE_COMMIT_IDLE_PING_FN = (self) => functoolsKit.trycatch(async (symbol, strategyName, exchangeName, currentPrice, backtest, timestamp) => {
|
|
10415
|
+
const frameName = self.methodContextService.context.frameName;
|
|
10416
|
+
const event = {
|
|
10417
|
+
symbol,
|
|
10418
|
+
strategyName,
|
|
10419
|
+
exchangeName,
|
|
10420
|
+
frameName,
|
|
10421
|
+
currentPrice,
|
|
10422
|
+
backtest,
|
|
10423
|
+
timestamp,
|
|
10424
|
+
};
|
|
10425
|
+
await idlePingSubject.next(event);
|
|
10426
|
+
await self.actionCoreService.pingIdle(backtest, event, { strategyName, exchangeName, frameName });
|
|
10427
|
+
}, {
|
|
10428
|
+
fallback: (error) => {
|
|
10429
|
+
const message = "StrategyConnectionService CREATE_COMMIT_IDLE_PING_FN thrown";
|
|
10430
|
+
const payload = {
|
|
10431
|
+
error: functoolsKit.errorData(error),
|
|
10432
|
+
message: functoolsKit.getErrorMessage(error),
|
|
10433
|
+
};
|
|
10434
|
+
self.loggerService.warn(message, payload);
|
|
10435
|
+
console.warn(message, payload);
|
|
10436
|
+
errorEmitter.next(error);
|
|
10437
|
+
},
|
|
10438
|
+
defaultValue: null,
|
|
10439
|
+
});
|
|
10378
10440
|
/**
|
|
10379
10441
|
* Creates a callback function for emitting active ping events.
|
|
10380
10442
|
*
|
|
10381
10443
|
* Called by ClientStrategy when an active pending signal is being monitored every minute.
|
|
10382
|
-
* Placeholder for future activePingSubject implementation.
|
|
10383
10444
|
*
|
|
10384
10445
|
* @param self - Reference to StrategyConnectionService instance
|
|
10385
10446
|
* @returns Callback function for active ping events
|
|
@@ -10626,6 +10687,7 @@ class StrategyConnectionService {
|
|
|
10626
10687
|
onInit: CREATE_COMMIT_INIT_FN(this),
|
|
10627
10688
|
onSchedulePing: CREATE_COMMIT_SCHEDULE_PING_FN(this),
|
|
10628
10689
|
onActivePing: CREATE_COMMIT_ACTIVE_PING_FN(this),
|
|
10690
|
+
onIdlePing: CREATE_COMMIT_IDLE_PING_FN(this),
|
|
10629
10691
|
onDispose: CREATE_COMMIT_DISPOSE_FN(this),
|
|
10630
10692
|
onCommit: CREATE_COMMIT_FN(this),
|
|
10631
10693
|
onSignalSync: CREATE_SYNC_FN(this, strategyName, exchangeName, frameName, backtest),
|
|
@@ -13104,6 +13166,34 @@ const CALL_PING_SCHEDULED_FN = functoolsKit.trycatch(async (event, self) => {
|
|
|
13104
13166
|
},
|
|
13105
13167
|
defaultValue: null,
|
|
13106
13168
|
});
|
|
13169
|
+
/**
|
|
13170
|
+
* Wrapper to call pingIdle method with error capture.
|
|
13171
|
+
*/
|
|
13172
|
+
const CALL_PING_IDLE_FN = functoolsKit.trycatch(async (event, self) => {
|
|
13173
|
+
if (!self._target.pingIdle) {
|
|
13174
|
+
return;
|
|
13175
|
+
}
|
|
13176
|
+
if (await self.params.strategy.hasPendingSignal(event.backtest, event.symbol, {
|
|
13177
|
+
strategyName: event.strategyName,
|
|
13178
|
+
exchangeName: event.exchangeName,
|
|
13179
|
+
frameName: event.frameName,
|
|
13180
|
+
})) {
|
|
13181
|
+
return;
|
|
13182
|
+
}
|
|
13183
|
+
return await self._target.pingIdle(event);
|
|
13184
|
+
}, {
|
|
13185
|
+
fallback: (error) => {
|
|
13186
|
+
const message = "ActionProxy.pingIdle thrown";
|
|
13187
|
+
const payload = {
|
|
13188
|
+
error: functoolsKit.errorData(error),
|
|
13189
|
+
message: functoolsKit.getErrorMessage(error),
|
|
13190
|
+
};
|
|
13191
|
+
LOGGER_SERVICE$4.warn(message, payload);
|
|
13192
|
+
console.warn(message, payload);
|
|
13193
|
+
errorEmitter.next(error);
|
|
13194
|
+
},
|
|
13195
|
+
defaultValue: null,
|
|
13196
|
+
});
|
|
13107
13197
|
/**
|
|
13108
13198
|
* Wrapper to call pingActive method with error capture.
|
|
13109
13199
|
*/
|
|
@@ -13342,6 +13432,18 @@ class ActionProxy {
|
|
|
13342
13432
|
async pingActive(event) {
|
|
13343
13433
|
return await CALL_PING_ACTIVE_FN(event, this);
|
|
13344
13434
|
}
|
|
13435
|
+
/**
|
|
13436
|
+
* Handles idle ping events with error capture.
|
|
13437
|
+
*
|
|
13438
|
+
* Wraps the user's pingIdle() method to catch and log any errors.
|
|
13439
|
+
* Called every tick while no signal is pending or scheduled.
|
|
13440
|
+
*
|
|
13441
|
+
* @param event - Idle ping data with symbol, strategy info, current price, timestamp
|
|
13442
|
+
* @returns Promise resolving to user's pingIdle() result or null on error
|
|
13443
|
+
*/
|
|
13444
|
+
async pingIdle(event) {
|
|
13445
|
+
return await CALL_PING_IDLE_FN(event, this);
|
|
13446
|
+
}
|
|
13345
13447
|
/**
|
|
13346
13448
|
* Handles risk rejection events with error capture.
|
|
13347
13449
|
*
|
|
@@ -13529,6 +13631,23 @@ const CALL_PING_SCHEDULED_CALLBACK_FN = functoolsKit.trycatch(async (self, event
|
|
|
13529
13631
|
errorEmitter.next(error);
|
|
13530
13632
|
},
|
|
13531
13633
|
});
|
|
13634
|
+
/** Wrapper to call idle ping callback with error handling */
|
|
13635
|
+
const CALL_PING_IDLE_CALLBACK_FN = functoolsKit.trycatch(async (self, event, strategyName, frameName, backtest) => {
|
|
13636
|
+
if (self.params.callbacks?.onPingIdle) {
|
|
13637
|
+
await self.params.callbacks.onPingIdle(event, self.params.actionName, strategyName, frameName, backtest);
|
|
13638
|
+
}
|
|
13639
|
+
}, {
|
|
13640
|
+
fallback: (error, self) => {
|
|
13641
|
+
const message = "ClientAction CALL_PING_IDLE_CALLBACK_FN thrown";
|
|
13642
|
+
const payload = {
|
|
13643
|
+
error: functoolsKit.errorData(error),
|
|
13644
|
+
message: functoolsKit.getErrorMessage(error),
|
|
13645
|
+
};
|
|
13646
|
+
self.params.logger.warn(message, payload);
|
|
13647
|
+
console.warn(message, payload);
|
|
13648
|
+
errorEmitter.next(error);
|
|
13649
|
+
},
|
|
13650
|
+
});
|
|
13532
13651
|
/** Wrapper to call active ping callback with error handling */
|
|
13533
13652
|
const CALL_PING_ACTIVE_CALLBACK_FN = functoolsKit.trycatch(async (self, event, strategyName, frameName, backtest) => {
|
|
13534
13653
|
if (self.params.callbacks?.onPingActive) {
|
|
@@ -13895,6 +14014,24 @@ class ClientAction {
|
|
|
13895
14014
|
await CALL_PING_ACTIVE_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
|
|
13896
14015
|
}
|
|
13897
14016
|
;
|
|
14017
|
+
/**
|
|
14018
|
+
* Handles idle ping events when no signal is active.
|
|
14019
|
+
*/
|
|
14020
|
+
async pingIdle(event) {
|
|
14021
|
+
this.params.logger.debug("ClientAction pingIdle", {
|
|
14022
|
+
actionName: this.params.actionName,
|
|
14023
|
+
strategyName: this.params.strategyName,
|
|
14024
|
+
frameName: this.params.frameName,
|
|
14025
|
+
});
|
|
14026
|
+
if (!this._handlerInstance) {
|
|
14027
|
+
await this.waitForInit();
|
|
14028
|
+
}
|
|
14029
|
+
// Call handler method if defined
|
|
14030
|
+
await this._handlerInstance?.pingIdle(event);
|
|
14031
|
+
// Call callback if defined
|
|
14032
|
+
await CALL_PING_IDLE_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
|
|
14033
|
+
}
|
|
14034
|
+
;
|
|
13898
14035
|
/**
|
|
13899
14036
|
* Handles risk rejection events when signals fail risk validation.
|
|
13900
14037
|
*/
|
|
@@ -14147,6 +14284,21 @@ class ActionConnectionService {
|
|
|
14147
14284
|
const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
14148
14285
|
await action.pingActive(event);
|
|
14149
14286
|
};
|
|
14287
|
+
/**
|
|
14288
|
+
* Routes idle ping event to appropriate ClientAction instance.
|
|
14289
|
+
*
|
|
14290
|
+
* @param event - Idle ping event data
|
|
14291
|
+
* @param backtest - Whether running in backtest mode
|
|
14292
|
+
* @param context - Execution context with action name, strategy name, exchange name, frame name
|
|
14293
|
+
*/
|
|
14294
|
+
this.pingIdle = async (event, backtest, context) => {
|
|
14295
|
+
this.loggerService.log("actionConnectionService pingIdle", {
|
|
14296
|
+
backtest,
|
|
14297
|
+
context,
|
|
14298
|
+
});
|
|
14299
|
+
const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
14300
|
+
await action.pingIdle(event);
|
|
14301
|
+
};
|
|
14150
14302
|
/**
|
|
14151
14303
|
* Routes riskRejection event to appropriate ClientAction instance.
|
|
14152
14304
|
*
|
|
@@ -16250,6 +16402,27 @@ class ActionCoreService {
|
|
|
16250
16402
|
await this.actionConnectionService.pingActive(event, backtest, { actionName, ...context });
|
|
16251
16403
|
}
|
|
16252
16404
|
};
|
|
16405
|
+
/**
|
|
16406
|
+
* Routes idle ping event to all registered actions for the strategy.
|
|
16407
|
+
*
|
|
16408
|
+
* Retrieves action list from strategy schema (IStrategySchema.actions)
|
|
16409
|
+
* and invokes the pingIdle handler on each ClientAction instance sequentially.
|
|
16410
|
+
* Called every tick when there is no pending or scheduled signal being monitored.
|
|
16411
|
+
*
|
|
16412
|
+
* @param backtest - Whether running in backtest mode (true) or live mode (false)
|
|
16413
|
+
* @param event - Idle state monitoring data
|
|
16414
|
+
* @param context - Strategy execution context with strategyName, exchangeName, frameName
|
|
16415
|
+
*/
|
|
16416
|
+
this.pingIdle = async (backtest, event, context) => {
|
|
16417
|
+
this.loggerService.log("actionCoreService pingIdle", {
|
|
16418
|
+
context,
|
|
16419
|
+
});
|
|
16420
|
+
await this.validate(context);
|
|
16421
|
+
const { actions = [] } = this.strategySchemaService.get(context.strategyName);
|
|
16422
|
+
for (const actionName of actions) {
|
|
16423
|
+
await this.actionConnectionService.pingIdle(event, backtest, { actionName, ...context });
|
|
16424
|
+
}
|
|
16425
|
+
};
|
|
16253
16426
|
/**
|
|
16254
16427
|
* Routes risk rejection event to all registered actions for the strategy.
|
|
16255
16428
|
*
|
|
@@ -37820,6 +37993,8 @@ const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
|
|
|
37820
37993
|
const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
|
|
37821
37994
|
const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
|
|
37822
37995
|
const LISTEN_ACTIVE_PING_ONCE_METHOD_NAME = "event.listenActivePingOnce";
|
|
37996
|
+
const LISTEN_IDLE_PING_METHOD_NAME = "event.listenIdlePing";
|
|
37997
|
+
const LISTEN_IDLE_PING_ONCE_METHOD_NAME = "event.listenIdlePingOnce";
|
|
37823
37998
|
const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
|
|
37824
37999
|
const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
|
|
37825
38000
|
const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
|
|
@@ -38995,6 +39170,45 @@ function listenActivePingOnce(filterFn, fn) {
|
|
|
38995
39170
|
};
|
|
38996
39171
|
return disposeFn = listenActivePing(wrappedFn);
|
|
38997
39172
|
}
|
|
39173
|
+
/**
|
|
39174
|
+
* Subscribes to idle ping events with queued async processing.
|
|
39175
|
+
*
|
|
39176
|
+
* Emits every tick when there is no pending or scheduled signal being monitored.
|
|
39177
|
+
*
|
|
39178
|
+
* @param fn - Callback function to handle idle ping events
|
|
39179
|
+
* @returns Unsubscribe function to stop listening
|
|
39180
|
+
*/
|
|
39181
|
+
function listenIdlePing(fn) {
|
|
39182
|
+
backtest.loggerService.log(LISTEN_IDLE_PING_METHOD_NAME);
|
|
39183
|
+
const wrappedFn = async (event) => {
|
|
39184
|
+
if (await functoolsKit.not(backtest.strategyCoreService.hasPendingSignal(event.backtest, event.symbol, {
|
|
39185
|
+
strategyName: event.strategyName,
|
|
39186
|
+
exchangeName: event.exchangeName,
|
|
39187
|
+
frameName: event.frameName,
|
|
39188
|
+
}))) {
|
|
39189
|
+
await fn(event);
|
|
39190
|
+
}
|
|
39191
|
+
};
|
|
39192
|
+
return idlePingSubject.subscribe(functoolsKit.queued(wrappedFn));
|
|
39193
|
+
}
|
|
39194
|
+
/**
|
|
39195
|
+
* Subscribes to filtered idle ping events with one-time execution.
|
|
39196
|
+
*
|
|
39197
|
+
* @param filterFn - Predicate to filter events
|
|
39198
|
+
* @param fn - Callback function to handle the matching event
|
|
39199
|
+
* @returns Unsubscribe function to cancel the listener before it fires
|
|
39200
|
+
*/
|
|
39201
|
+
function listenIdlePingOnce(filterFn, fn) {
|
|
39202
|
+
backtest.loggerService.log(LISTEN_IDLE_PING_ONCE_METHOD_NAME);
|
|
39203
|
+
let disposeFn;
|
|
39204
|
+
const wrappedFn = async (event) => {
|
|
39205
|
+
if (filterFn(event)) {
|
|
39206
|
+
await fn(event);
|
|
39207
|
+
disposeFn && disposeFn();
|
|
39208
|
+
}
|
|
39209
|
+
};
|
|
39210
|
+
return disposeFn = listenIdlePing(wrappedFn);
|
|
39211
|
+
}
|
|
38998
39212
|
/**
|
|
38999
39213
|
* Subscribes to strategy management events with queued async processing.
|
|
39000
39214
|
*
|
|
@@ -45952,6 +46166,7 @@ const RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR = "RecentLiveAdapter.clear";
|
|
|
45952
46166
|
const RECENT_ADAPTER_METHOD_NAME_ENABLE = "RecentAdapter.enable";
|
|
45953
46167
|
const RECENT_ADAPTER_METHOD_NAME_DISABLE = "RecentAdapter.disable";
|
|
45954
46168
|
const RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentAdapter.getLatestSignal";
|
|
46169
|
+
const RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL = "RecentAdapter.getMinutesSinceLatestSignalCreated";
|
|
45955
46170
|
/**
|
|
45956
46171
|
* Builds a composite storage key from context parts.
|
|
45957
46172
|
* Includes backtest flag as the last segment to prevent live/backtest collisions.
|
|
@@ -46010,6 +46225,23 @@ class RecentPersistBacktestUtils {
|
|
|
46010
46225
|
});
|
|
46011
46226
|
return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
46012
46227
|
};
|
|
46228
|
+
/**
|
|
46229
|
+
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
46230
|
+
* @param timestamp - Current timestamp in milliseconds
|
|
46231
|
+
* @param symbol - Trading pair symbol
|
|
46232
|
+
* @param strategyName - Strategy identifier
|
|
46233
|
+
* @param exchangeName - Exchange identifier
|
|
46234
|
+
* @param frameName - Frame identifier
|
|
46235
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
46236
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
46237
|
+
*/
|
|
46238
|
+
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
46239
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
46240
|
+
if (!signal) {
|
|
46241
|
+
return null;
|
|
46242
|
+
}
|
|
46243
|
+
return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
|
|
46244
|
+
};
|
|
46013
46245
|
}
|
|
46014
46246
|
}
|
|
46015
46247
|
/**
|
|
@@ -46052,6 +46284,23 @@ class RecentMemoryBacktestUtils {
|
|
|
46052
46284
|
backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
|
|
46053
46285
|
return this._signals.get(key) ?? null;
|
|
46054
46286
|
};
|
|
46287
|
+
/**
|
|
46288
|
+
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
46289
|
+
* @param timestamp - Current timestamp in milliseconds
|
|
46290
|
+
* @param symbol - Trading pair symbol
|
|
46291
|
+
* @param strategyName - Strategy identifier
|
|
46292
|
+
* @param exchangeName - Exchange identifier
|
|
46293
|
+
* @param frameName - Frame identifier
|
|
46294
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
46295
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
46296
|
+
*/
|
|
46297
|
+
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
46298
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
46299
|
+
if (!signal) {
|
|
46300
|
+
return null;
|
|
46301
|
+
}
|
|
46302
|
+
return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
|
|
46303
|
+
};
|
|
46055
46304
|
}
|
|
46056
46305
|
}
|
|
46057
46306
|
/**
|
|
@@ -46095,6 +46344,23 @@ class RecentPersistLiveUtils {
|
|
|
46095
46344
|
});
|
|
46096
46345
|
return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
46097
46346
|
};
|
|
46347
|
+
/**
|
|
46348
|
+
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
46349
|
+
* @param timestamp - Current timestamp in milliseconds
|
|
46350
|
+
* @param symbol - Trading pair symbol
|
|
46351
|
+
* @param strategyName - Strategy identifier
|
|
46352
|
+
* @param exchangeName - Exchange identifier
|
|
46353
|
+
* @param frameName - Frame identifier
|
|
46354
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
46355
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
46356
|
+
*/
|
|
46357
|
+
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
46358
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
46359
|
+
if (!signal) {
|
|
46360
|
+
return null;
|
|
46361
|
+
}
|
|
46362
|
+
return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
|
|
46363
|
+
};
|
|
46098
46364
|
}
|
|
46099
46365
|
}
|
|
46100
46366
|
/**
|
|
@@ -46137,6 +46403,23 @@ class RecentMemoryLiveUtils {
|
|
|
46137
46403
|
backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
|
|
46138
46404
|
return this._signals.get(key) ?? null;
|
|
46139
46405
|
};
|
|
46406
|
+
/**
|
|
46407
|
+
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
46408
|
+
* @param timestamp - Current timestamp in milliseconds
|
|
46409
|
+
* @param symbol - Trading pair symbol
|
|
46410
|
+
* @param strategyName - Strategy identifier
|
|
46411
|
+
* @param exchangeName - Exchange identifier
|
|
46412
|
+
* @param frameName - Frame identifier
|
|
46413
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
46414
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
46415
|
+
*/
|
|
46416
|
+
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
46417
|
+
const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
|
|
46418
|
+
if (!signal) {
|
|
46419
|
+
return null;
|
|
46420
|
+
}
|
|
46421
|
+
return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
|
|
46422
|
+
};
|
|
46140
46423
|
}
|
|
46141
46424
|
}
|
|
46142
46425
|
/**
|
|
@@ -46183,6 +46466,32 @@ class RecentBacktestAdapter {
|
|
|
46183
46466
|
});
|
|
46184
46467
|
return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
46185
46468
|
};
|
|
46469
|
+
/**
|
|
46470
|
+
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
46471
|
+
* Proxies call to the underlying storage adapter.
|
|
46472
|
+
* @param timestamp - Current timestamp in milliseconds
|
|
46473
|
+
* @param symbol - Trading pair symbol
|
|
46474
|
+
* @param strategyName - Strategy identifier
|
|
46475
|
+
* @param exchangeName - Exchange identifier
|
|
46476
|
+
* @param frameName - Frame identifier
|
|
46477
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
46478
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
46479
|
+
*/
|
|
46480
|
+
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
46481
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
46482
|
+
symbol,
|
|
46483
|
+
strategyName,
|
|
46484
|
+
exchangeName,
|
|
46485
|
+
frameName,
|
|
46486
|
+
backtest: backtest$1,
|
|
46487
|
+
timestamp,
|
|
46488
|
+
});
|
|
46489
|
+
const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
46490
|
+
if (!signal) {
|
|
46491
|
+
return null;
|
|
46492
|
+
}
|
|
46493
|
+
return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
|
|
46494
|
+
};
|
|
46186
46495
|
/**
|
|
46187
46496
|
* Sets the storage adapter constructor.
|
|
46188
46497
|
* All future storage operations will use this adapter.
|
|
@@ -46261,6 +46570,32 @@ class RecentLiveAdapter {
|
|
|
46261
46570
|
});
|
|
46262
46571
|
return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
46263
46572
|
};
|
|
46573
|
+
/**
|
|
46574
|
+
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
46575
|
+
* Proxies call to the underlying storage adapter.
|
|
46576
|
+
* @param timestamp - Current timestamp in milliseconds
|
|
46577
|
+
* @param symbol - Trading pair symbol
|
|
46578
|
+
* @param strategyName - Strategy identifier
|
|
46579
|
+
* @param exchangeName - Exchange identifier
|
|
46580
|
+
* @param frameName - Frame identifier
|
|
46581
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
46582
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
46583
|
+
*/
|
|
46584
|
+
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
46585
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
46586
|
+
symbol,
|
|
46587
|
+
strategyName,
|
|
46588
|
+
exchangeName,
|
|
46589
|
+
frameName,
|
|
46590
|
+
backtest: backtest$1,
|
|
46591
|
+
timestamp,
|
|
46592
|
+
});
|
|
46593
|
+
const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
46594
|
+
if (!signal) {
|
|
46595
|
+
return null;
|
|
46596
|
+
}
|
|
46597
|
+
return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
|
|
46598
|
+
};
|
|
46264
46599
|
/**
|
|
46265
46600
|
* Sets the storage adapter constructor.
|
|
46266
46601
|
* All future storage operations will use this adapter.
|
|
@@ -46369,6 +46704,27 @@ class RecentAdapter {
|
|
|
46369
46704
|
}
|
|
46370
46705
|
return null;
|
|
46371
46706
|
};
|
|
46707
|
+
/**
|
|
46708
|
+
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
46709
|
+
* Searches backtest storage first, then live storage.
|
|
46710
|
+
* @param timestamp - Current timestamp in milliseconds
|
|
46711
|
+
* @param symbol - Trading pair symbol
|
|
46712
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
46713
|
+
* @returns Whole minutes since the latest signal was created, or null if no signal found
|
|
46714
|
+
* @throws Error if RecentAdapter is not enabled
|
|
46715
|
+
*/
|
|
46716
|
+
this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, context) => {
|
|
46717
|
+
backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL, {
|
|
46718
|
+
symbol,
|
|
46719
|
+
context,
|
|
46720
|
+
timestamp,
|
|
46721
|
+
});
|
|
46722
|
+
const signal = await this.getLatestSignal(symbol, context);
|
|
46723
|
+
if (!signal) {
|
|
46724
|
+
return null;
|
|
46725
|
+
}
|
|
46726
|
+
return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
|
|
46727
|
+
};
|
|
46372
46728
|
}
|
|
46373
46729
|
}
|
|
46374
46730
|
/**
|
|
@@ -46388,6 +46744,7 @@ const RecentLive = new RecentLiveAdapter();
|
|
|
46388
46744
|
const RecentBacktest = new RecentBacktestAdapter();
|
|
46389
46745
|
|
|
46390
46746
|
const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
|
|
46747
|
+
const GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME = "signal.getMinutesSinceLatestSignalCreated";
|
|
46391
46748
|
/**
|
|
46392
46749
|
* Returns the latest signal (pending or closed) for the current strategy context.
|
|
46393
46750
|
*
|
|
@@ -46425,6 +46782,42 @@ async function getLatestSignal(symbol) {
|
|
|
46425
46782
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
46426
46783
|
return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
|
|
46427
46784
|
}
|
|
46785
|
+
/**
|
|
46786
|
+
* Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
|
|
46787
|
+
*
|
|
46788
|
+
* Does not distinguish between active and closed signals — measures time since
|
|
46789
|
+
* whichever signal was recorded last. Useful for cooldown logic after a stop-loss.
|
|
46790
|
+
*
|
|
46791
|
+
* Searches backtest storage first, then live storage.
|
|
46792
|
+
* Returns null if no signal exists at all.
|
|
46793
|
+
*
|
|
46794
|
+
* Automatically detects backtest/live mode from execution context.
|
|
46795
|
+
*
|
|
46796
|
+
* @param symbol - Trading pair symbol
|
|
46797
|
+
* @param timestamp - Current timestamp in milliseconds
|
|
46798
|
+
* @returns Promise resolving to whole minutes since the latest signal was created, or null
|
|
46799
|
+
*
|
|
46800
|
+
* @example
|
|
46801
|
+
* ```typescript
|
|
46802
|
+
* import { getMinutesSinceLatestSignalCreated } from "backtest-kit";
|
|
46803
|
+
*
|
|
46804
|
+
* const minutes = await getMinutesSinceLatestSignalCreated("BTCUSDT", Date.now());
|
|
46805
|
+
* if (minutes !== null && minutes < 24 * 60) {
|
|
46806
|
+
* return; // cooldown — skip new signal for 24 hours after last signal
|
|
46807
|
+
* }
|
|
46808
|
+
* ```
|
|
46809
|
+
*/
|
|
46810
|
+
async function getMinutesSinceLatestSignalCreated(symbol, timestamp) {
|
|
46811
|
+
backtest.loggerService.info(GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME, { symbol, timestamp });
|
|
46812
|
+
if (!ExecutionContextService.hasContext()) {
|
|
46813
|
+
throw new Error("getMinutesSinceLatestSignalCreated requires an execution context");
|
|
46814
|
+
}
|
|
46815
|
+
if (!MethodContextService.hasContext()) {
|
|
46816
|
+
throw new Error("getMinutesSinceLatestSignalCreated requires a method context");
|
|
46817
|
+
}
|
|
46818
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
46819
|
+
return await Recent.getMinutesSinceLatestSignalCreated(timestamp, symbol, { exchangeName, frameName, strategyName });
|
|
46820
|
+
}
|
|
46428
46821
|
|
|
46429
46822
|
const DEFAULT_BM25_K1 = 1.5;
|
|
46430
46823
|
const DEFAULT_BM25_B = 0.75;
|
|
@@ -49752,6 +50145,7 @@ const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
|
|
|
49752
50145
|
/** List of all global subjects whose listeners should be snapshotted for session isolation */
|
|
49753
50146
|
const SUBJECT_ISOLATION_LIST = [
|
|
49754
50147
|
activePingSubject,
|
|
50148
|
+
idlePingSubject,
|
|
49755
50149
|
backtestScheduleOpenSubject,
|
|
49756
50150
|
breakevenSubject,
|
|
49757
50151
|
doneBacktestSubject,
|
|
@@ -57872,6 +58266,7 @@ const METHOD_NAME_PARTIAL_PROFIT_AVAILABLE = "ActionBase.partialProfitAvailable"
|
|
|
57872
58266
|
const METHOD_NAME_PARTIAL_LOSS_AVAILABLE = "ActionBase.partialLossAvailable";
|
|
57873
58267
|
const METHOD_NAME_PING_SCHEDULED = "ActionBase.pingScheduled";
|
|
57874
58268
|
const METHOD_NAME_PING_ACTIVE = "ActionBase.pingActive";
|
|
58269
|
+
const METHOD_NAME_PING_IDLE = "ActionBase.pingIdle";
|
|
57875
58270
|
const METHOD_NAME_RISK_REJECTION = "ActionBase.riskRejection";
|
|
57876
58271
|
const METHOD_NAME_DISPOSE = "ActionBase.dispose";
|
|
57877
58272
|
const DEFAULT_SOURCE = "default";
|
|
@@ -58265,6 +58660,26 @@ class ActionBase {
|
|
|
58265
58660
|
source,
|
|
58266
58661
|
});
|
|
58267
58662
|
}
|
|
58663
|
+
/**
|
|
58664
|
+
* Handles idle ping events when no signal is active.
|
|
58665
|
+
*
|
|
58666
|
+
* Called every tick while no signal is pending or scheduled.
|
|
58667
|
+
* Use to monitor idle strategy state and implement entry condition logic.
|
|
58668
|
+
*
|
|
58669
|
+
* Triggered by: ActionCoreService.pingIdle() via StrategyConnectionService
|
|
58670
|
+
* Source: idlePingSubject.next() in CREATE_COMMIT_IDLE_PING_FN callback
|
|
58671
|
+
* Frequency: Every tick while no signal is pending or scheduled
|
|
58672
|
+
*
|
|
58673
|
+
* Default implementation: Logs idle ping event.
|
|
58674
|
+
*
|
|
58675
|
+
* @param event - Idle ping data with symbol, strategy info, current price, timestamp
|
|
58676
|
+
*/
|
|
58677
|
+
pingIdle(event, source = DEFAULT_SOURCE) {
|
|
58678
|
+
LOGGER_SERVICE.info(METHOD_NAME_PING_IDLE, {
|
|
58679
|
+
event,
|
|
58680
|
+
source,
|
|
58681
|
+
});
|
|
58682
|
+
}
|
|
58268
58683
|
/**
|
|
58269
58684
|
* Handles risk rejection events when signals fail risk validation.
|
|
58270
58685
|
*
|
|
@@ -58708,6 +59123,7 @@ exports.getFrameSchema = getFrameSchema;
|
|
|
58708
59123
|
exports.getLatestSignal = getLatestSignal;
|
|
58709
59124
|
exports.getMaxDrawdownDistancePnlCost = getMaxDrawdownDistancePnlCost;
|
|
58710
59125
|
exports.getMaxDrawdownDistancePnlPercentage = getMaxDrawdownDistancePnlPercentage;
|
|
59126
|
+
exports.getMinutesSinceLatestSignalCreated = getMinutesSinceLatestSignalCreated;
|
|
58711
59127
|
exports.getMode = getMode;
|
|
58712
59128
|
exports.getNextCandles = getNextCandles;
|
|
58713
59129
|
exports.getOrderBook = getOrderBook;
|
|
@@ -58780,6 +59196,8 @@ exports.listenError = listenError;
|
|
|
58780
59196
|
exports.listenExit = listenExit;
|
|
58781
59197
|
exports.listenHighestProfit = listenHighestProfit;
|
|
58782
59198
|
exports.listenHighestProfitOnce = listenHighestProfitOnce;
|
|
59199
|
+
exports.listenIdlePing = listenIdlePing;
|
|
59200
|
+
exports.listenIdlePingOnce = listenIdlePingOnce;
|
|
58783
59201
|
exports.listenMaxDrawdown = listenMaxDrawdown;
|
|
58784
59202
|
exports.listenMaxDrawdownOnce = listenMaxDrawdownOnce;
|
|
58785
59203
|
exports.listenPartialLossAvailable = listenPartialLossAvailable;
|