backtest-kit 2.2.1 → 2.2.5
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/README.md +32 -10
- package/build/index.cjs +1997 -178
- package/build/index.mjs +1996 -180
- package/package.json +2 -1
- package/types.d.ts +2039 -963
package/build/index.cjs
CHANGED
|
@@ -121,6 +121,7 @@ const markdownServices$1 = {
|
|
|
121
121
|
breakevenMarkdownService: Symbol('breakevenMarkdownService'),
|
|
122
122
|
outlineMarkdownService: Symbol('outlineMarkdownService'),
|
|
123
123
|
riskMarkdownService: Symbol('riskMarkdownService'),
|
|
124
|
+
strategyMarkdownService: Symbol('strategyMarkdownService'),
|
|
124
125
|
};
|
|
125
126
|
const reportServices$1 = {
|
|
126
127
|
backtestReportService: Symbol('backtestReportService'),
|
|
@@ -132,6 +133,7 @@ const reportServices$1 = {
|
|
|
132
133
|
partialReportService: Symbol('partialReportService'),
|
|
133
134
|
breakevenReportService: Symbol('breakevenReportService'),
|
|
134
135
|
riskReportService: Symbol('riskReportService'),
|
|
136
|
+
strategyReportService: Symbol('strategyReportService'),
|
|
135
137
|
};
|
|
136
138
|
const validationServices$1 = {
|
|
137
139
|
exchangeValidationService: Symbol('exchangeValidationService'),
|
|
@@ -540,6 +542,20 @@ const schedulePingSubject = new functoolsKit.Subject();
|
|
|
540
542
|
* Allows users to track active signal lifecycle and implement custom dynamic management logic.
|
|
541
543
|
*/
|
|
542
544
|
const activePingSubject = new functoolsKit.Subject();
|
|
545
|
+
/**
|
|
546
|
+
* Strategy management signal emitter.
|
|
547
|
+
* Emits when strategy management actions are executed:
|
|
548
|
+
* - cancel-scheduled: Scheduled signal cancelled
|
|
549
|
+
* - close-pending: Pending signal closed
|
|
550
|
+
* - partial-profit: Partial close at profit level
|
|
551
|
+
* - partial-loss: Partial close at loss level
|
|
552
|
+
* - trailing-stop: Stop-loss adjusted
|
|
553
|
+
* - trailing-take: Take-profit adjusted
|
|
554
|
+
* - breakeven: Stop-loss moved to entry price
|
|
555
|
+
*
|
|
556
|
+
* Used by StrategyReportService and StrategyMarkdownService for event logging and reporting.
|
|
557
|
+
*/
|
|
558
|
+
const strategyCommitSubject = new functoolsKit.Subject();
|
|
543
559
|
|
|
544
560
|
var emitters = /*#__PURE__*/Object.freeze({
|
|
545
561
|
__proto__: null,
|
|
@@ -560,6 +576,7 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
560
576
|
signalBacktestEmitter: signalBacktestEmitter,
|
|
561
577
|
signalEmitter: signalEmitter,
|
|
562
578
|
signalLiveEmitter: signalLiveEmitter,
|
|
579
|
+
strategyCommitSubject: strategyCommitSubject,
|
|
563
580
|
validationSubject: validationSubject,
|
|
564
581
|
walkerCompleteSubject: walkerCompleteSubject,
|
|
565
582
|
walkerEmitter: walkerEmitter,
|
|
@@ -1565,8 +1582,15 @@ class PersistCandleUtils {
|
|
|
1565
1582
|
const candle = await stateStorage.readValue(timestamp);
|
|
1566
1583
|
cachedCandles.push(candle);
|
|
1567
1584
|
}
|
|
1568
|
-
catch {
|
|
1569
|
-
|
|
1585
|
+
catch (error) {
|
|
1586
|
+
const message = `PersistCandleUtils.readCandlesData found invalid candle symbol=${symbol} interval=${interval} timestamp=${timestamp}`;
|
|
1587
|
+
const payload = {
|
|
1588
|
+
error: functoolsKit.errorData(error),
|
|
1589
|
+
message: functoolsKit.getErrorMessage(error),
|
|
1590
|
+
};
|
|
1591
|
+
bt.loggerService.warn(message, payload);
|
|
1592
|
+
console.warn(message, payload);
|
|
1593
|
+
errorEmitter.next(error);
|
|
1570
1594
|
continue;
|
|
1571
1595
|
}
|
|
1572
1596
|
}
|
|
@@ -3678,6 +3702,7 @@ const CHECK_SCHEDULED_SIGNAL_TIMEOUT_FN = async (self, scheduled, currentPrice)
|
|
|
3678
3702
|
symbol: self.params.execution.context.symbol,
|
|
3679
3703
|
backtest: self.params.execution.context.backtest,
|
|
3680
3704
|
reason: "timeout",
|
|
3705
|
+
createdAt: currentTime,
|
|
3681
3706
|
};
|
|
3682
3707
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
3683
3708
|
return result;
|
|
@@ -3733,6 +3758,7 @@ const CANCEL_SCHEDULED_SIGNAL_BY_STOPLOSS_FN = async (self, scheduled, currentPr
|
|
|
3733
3758
|
symbol: self.params.execution.context.symbol,
|
|
3734
3759
|
backtest: self.params.execution.context.backtest,
|
|
3735
3760
|
reason: "price_reject",
|
|
3761
|
+
createdAt: currentTime,
|
|
3736
3762
|
};
|
|
3737
3763
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
3738
3764
|
return result;
|
|
@@ -3788,6 +3814,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
|
|
|
3788
3814
|
symbol: self.params.execution.context.symbol,
|
|
3789
3815
|
currentPrice: self._pendingSignal.priceOpen,
|
|
3790
3816
|
backtest: self.params.execution.context.backtest,
|
|
3817
|
+
createdAt: activationTime,
|
|
3791
3818
|
};
|
|
3792
3819
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, activationTime, self.params.execution.context.backtest);
|
|
3793
3820
|
return result;
|
|
@@ -4220,6 +4247,7 @@ const RETURN_SCHEDULED_SIGNAL_ACTIVE_FN = async (self, scheduled, currentPrice)
|
|
|
4220
4247
|
percentSl: 0,
|
|
4221
4248
|
pnl,
|
|
4222
4249
|
backtest: self.params.execution.context.backtest,
|
|
4250
|
+
createdAt: currentTime,
|
|
4223
4251
|
};
|
|
4224
4252
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4225
4253
|
return result;
|
|
@@ -4244,6 +4272,7 @@ const OPEN_NEW_SCHEDULED_SIGNAL_FN = async (self, signal) => {
|
|
|
4244
4272
|
symbol: self.params.execution.context.symbol,
|
|
4245
4273
|
currentPrice: currentPrice,
|
|
4246
4274
|
backtest: self.params.execution.context.backtest,
|
|
4275
|
+
createdAt: currentTime,
|
|
4247
4276
|
};
|
|
4248
4277
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4249
4278
|
return result;
|
|
@@ -4264,6 +4293,7 @@ const OPEN_NEW_PENDING_SIGNAL_FN = async (self, signal) => {
|
|
|
4264
4293
|
symbol: self.params.execution.context.symbol,
|
|
4265
4294
|
currentPrice: signal.priceOpen,
|
|
4266
4295
|
backtest: self.params.execution.context.backtest,
|
|
4296
|
+
createdAt: currentTime,
|
|
4267
4297
|
};
|
|
4268
4298
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4269
4299
|
return result;
|
|
@@ -4328,6 +4358,7 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
|
|
|
4328
4358
|
frameName: self.params.method.context.frameName,
|
|
4329
4359
|
symbol: self.params.execution.context.symbol,
|
|
4330
4360
|
backtest: self.params.execution.context.backtest,
|
|
4361
|
+
createdAt: currentTime,
|
|
4331
4362
|
};
|
|
4332
4363
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4333
4364
|
return result;
|
|
@@ -4401,6 +4432,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
4401
4432
|
percentSl,
|
|
4402
4433
|
pnl,
|
|
4403
4434
|
backtest: self.params.execution.context.backtest,
|
|
4435
|
+
createdAt: currentTime,
|
|
4404
4436
|
};
|
|
4405
4437
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4406
4438
|
return result;
|
|
@@ -4417,6 +4449,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
|
|
|
4417
4449
|
symbol: self.params.execution.context.symbol,
|
|
4418
4450
|
currentPrice: currentPrice,
|
|
4419
4451
|
backtest: self.params.execution.context.backtest,
|
|
4452
|
+
createdAt: currentTime,
|
|
4420
4453
|
};
|
|
4421
4454
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4422
4455
|
return result;
|
|
@@ -4443,6 +4476,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
|
|
|
4443
4476
|
symbol: self.params.execution.context.symbol,
|
|
4444
4477
|
backtest: self.params.execution.context.backtest,
|
|
4445
4478
|
reason,
|
|
4479
|
+
createdAt: closeTimestamp,
|
|
4446
4480
|
};
|
|
4447
4481
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
|
|
4448
4482
|
return result;
|
|
@@ -4523,6 +4557,7 @@ const CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, signal, averagePrice, c
|
|
|
4523
4557
|
frameName: self.params.method.context.frameName,
|
|
4524
4558
|
symbol: self.params.execution.context.symbol,
|
|
4525
4559
|
backtest: self.params.execution.context.backtest,
|
|
4560
|
+
createdAt: closeTimestamp,
|
|
4526
4561
|
};
|
|
4527
4562
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
|
|
4528
4563
|
return result;
|
|
@@ -5039,6 +5074,7 @@ class ClientStrategy {
|
|
|
5039
5074
|
backtest: this.params.execution.context.backtest,
|
|
5040
5075
|
reason: "user",
|
|
5041
5076
|
cancelId: cancelledSignal.cancelId,
|
|
5077
|
+
createdAt: currentTime,
|
|
5042
5078
|
};
|
|
5043
5079
|
await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
|
|
5044
5080
|
return result;
|
|
@@ -5073,6 +5109,7 @@ class ClientStrategy {
|
|
|
5073
5109
|
symbol: this.params.execution.context.symbol,
|
|
5074
5110
|
backtest: this.params.execution.context.backtest,
|
|
5075
5111
|
closeId: closedSignal.closeId,
|
|
5112
|
+
createdAt: currentTime,
|
|
5076
5113
|
};
|
|
5077
5114
|
await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
|
|
5078
5115
|
return result;
|
|
@@ -5187,6 +5224,7 @@ class ClientStrategy {
|
|
|
5187
5224
|
backtest: true,
|
|
5188
5225
|
reason: "user",
|
|
5189
5226
|
cancelId: cancelledSignal.cancelId,
|
|
5227
|
+
createdAt: closeTimestamp,
|
|
5190
5228
|
};
|
|
5191
5229
|
await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledResult, closeTimestamp, this.params.execution.context.backtest);
|
|
5192
5230
|
return cancelledResult;
|
|
@@ -5218,6 +5256,7 @@ class ClientStrategy {
|
|
|
5218
5256
|
symbol: this.params.execution.context.symbol,
|
|
5219
5257
|
backtest: true,
|
|
5220
5258
|
closeId: closedSignal.closeId,
|
|
5259
|
+
createdAt: closeTimestamp,
|
|
5221
5260
|
};
|
|
5222
5261
|
await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, closedResult, closeTimestamp, this.params.execution.context.backtest);
|
|
5223
5262
|
return closedResult;
|
|
@@ -6533,7 +6572,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
6533
6572
|
* @param backtest - Whether running in backtest mode
|
|
6534
6573
|
* @returns Unique string key for memoization
|
|
6535
6574
|
*/
|
|
6536
|
-
const CREATE_KEY_FN$
|
|
6575
|
+
const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
6537
6576
|
const parts = [symbol, strategyName, exchangeName];
|
|
6538
6577
|
if (frameName)
|
|
6539
6578
|
parts.push(frameName);
|
|
@@ -6698,7 +6737,7 @@ class StrategyConnectionService {
|
|
|
6698
6737
|
* @param backtest - Whether running in backtest mode
|
|
6699
6738
|
* @returns Configured ClientStrategy instance
|
|
6700
6739
|
*/
|
|
6701
|
-
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
6740
|
+
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
6702
6741
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
6703
6742
|
return new ClientStrategy({
|
|
6704
6743
|
symbol,
|
|
@@ -6953,7 +6992,7 @@ class StrategyConnectionService {
|
|
|
6953
6992
|
}
|
|
6954
6993
|
return;
|
|
6955
6994
|
}
|
|
6956
|
-
const key = CREATE_KEY_FN$
|
|
6995
|
+
const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
6957
6996
|
if (!this.getStrategy.has(key)) {
|
|
6958
6997
|
return;
|
|
6959
6998
|
}
|
|
@@ -7922,7 +7961,7 @@ class ClientRisk {
|
|
|
7922
7961
|
* @param backtest - Whether running in backtest mode
|
|
7923
7962
|
* @returns Unique string key for memoization
|
|
7924
7963
|
*/
|
|
7925
|
-
const CREATE_KEY_FN$
|
|
7964
|
+
const CREATE_KEY_FN$k = (riskName, exchangeName, frameName, backtest) => {
|
|
7926
7965
|
const parts = [riskName, exchangeName];
|
|
7927
7966
|
if (frameName)
|
|
7928
7967
|
parts.push(frameName);
|
|
@@ -8021,7 +8060,7 @@ class RiskConnectionService {
|
|
|
8021
8060
|
* @param backtest - True if backtest mode, false if live mode
|
|
8022
8061
|
* @returns Configured ClientRisk instance
|
|
8023
8062
|
*/
|
|
8024
|
-
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
8063
|
+
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
8025
8064
|
const schema = this.riskSchemaService.get(riskName);
|
|
8026
8065
|
return new ClientRisk({
|
|
8027
8066
|
...schema,
|
|
@@ -8089,7 +8128,7 @@ class RiskConnectionService {
|
|
|
8089
8128
|
payload,
|
|
8090
8129
|
});
|
|
8091
8130
|
if (payload) {
|
|
8092
|
-
const key = CREATE_KEY_FN$
|
|
8131
|
+
const key = CREATE_KEY_FN$k(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
8093
8132
|
this.getRisk.clear(key);
|
|
8094
8133
|
}
|
|
8095
8134
|
else {
|
|
@@ -9512,7 +9551,7 @@ class ClientAction {
|
|
|
9512
9551
|
* @param backtest - Whether running in backtest mode
|
|
9513
9552
|
* @returns Unique string key for memoization
|
|
9514
9553
|
*/
|
|
9515
|
-
const CREATE_KEY_FN$
|
|
9554
|
+
const CREATE_KEY_FN$j = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
9516
9555
|
const parts = [actionName, strategyName, exchangeName];
|
|
9517
9556
|
if (frameName)
|
|
9518
9557
|
parts.push(frameName);
|
|
@@ -9563,7 +9602,7 @@ class ActionConnectionService {
|
|
|
9563
9602
|
* @param backtest - True if backtest mode, false if live mode
|
|
9564
9603
|
* @returns Configured ClientAction instance
|
|
9565
9604
|
*/
|
|
9566
|
-
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
9605
|
+
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
9567
9606
|
const schema = this.actionSchemaService.get(actionName);
|
|
9568
9607
|
return new ClientAction({
|
|
9569
9608
|
...schema,
|
|
@@ -9756,7 +9795,7 @@ class ActionConnectionService {
|
|
|
9756
9795
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
9757
9796
|
return;
|
|
9758
9797
|
}
|
|
9759
|
-
const key = CREATE_KEY_FN$
|
|
9798
|
+
const key = CREATE_KEY_FN$j(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
9760
9799
|
if (!this.getAction.has(key)) {
|
|
9761
9800
|
return;
|
|
9762
9801
|
}
|
|
@@ -9774,7 +9813,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
9774
9813
|
* @param exchangeName - Exchange name
|
|
9775
9814
|
* @returns Unique string key for memoization
|
|
9776
9815
|
*/
|
|
9777
|
-
const CREATE_KEY_FN$
|
|
9816
|
+
const CREATE_KEY_FN$i = (exchangeName) => {
|
|
9778
9817
|
return exchangeName;
|
|
9779
9818
|
};
|
|
9780
9819
|
/**
|
|
@@ -9798,7 +9837,7 @@ class ExchangeCoreService {
|
|
|
9798
9837
|
* @param exchangeName - Name of the exchange to validate
|
|
9799
9838
|
* @returns Promise that resolves when validation is complete
|
|
9800
9839
|
*/
|
|
9801
|
-
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
9840
|
+
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$i(exchangeName), async (exchangeName) => {
|
|
9802
9841
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
9803
9842
|
exchangeName,
|
|
9804
9843
|
});
|
|
@@ -10022,12 +10061,32 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
10022
10061
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10023
10062
|
* @returns Unique string key for memoization
|
|
10024
10063
|
*/
|
|
10025
|
-
const CREATE_KEY_FN$
|
|
10064
|
+
const CREATE_KEY_FN$h = (context) => {
|
|
10026
10065
|
const parts = [context.strategyName, context.exchangeName];
|
|
10027
10066
|
if (context.frameName)
|
|
10028
10067
|
parts.push(context.frameName);
|
|
10029
10068
|
return parts.join(":");
|
|
10030
10069
|
};
|
|
10070
|
+
/**
|
|
10071
|
+
* Broadcasts StrategyCommitContract event to strategyCommitSubject.
|
|
10072
|
+
*
|
|
10073
|
+
* @param event - The signal commit event to broadcast
|
|
10074
|
+
*/
|
|
10075
|
+
const CALL_STRATEGY_COMMIT_FN = functoolsKit.trycatch(async (event) => {
|
|
10076
|
+
await strategyCommitSubject.next(event);
|
|
10077
|
+
}, {
|
|
10078
|
+
fallback: (error) => {
|
|
10079
|
+
const message = "StrategyCoreService CALL_STRATEGY_COMMIT_FN thrown";
|
|
10080
|
+
const payload = {
|
|
10081
|
+
error: functoolsKit.errorData(error),
|
|
10082
|
+
message: functoolsKit.getErrorMessage(error),
|
|
10083
|
+
};
|
|
10084
|
+
bt.loggerService.warn(message, payload);
|
|
10085
|
+
console.warn(message, payload);
|
|
10086
|
+
errorEmitter.next(error);
|
|
10087
|
+
},
|
|
10088
|
+
defaultValue: null,
|
|
10089
|
+
});
|
|
10031
10090
|
/**
|
|
10032
10091
|
* Global service for strategy operations with execution context injection.
|
|
10033
10092
|
*
|
|
@@ -10054,7 +10113,7 @@ class StrategyCoreService {
|
|
|
10054
10113
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10055
10114
|
* @returns Promise that resolves when validation is complete
|
|
10056
10115
|
*/
|
|
10057
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
10116
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$h(context), async (context) => {
|
|
10058
10117
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
10059
10118
|
context,
|
|
10060
10119
|
});
|
|
@@ -10260,7 +10319,19 @@ class StrategyCoreService {
|
|
|
10260
10319
|
cancelId,
|
|
10261
10320
|
});
|
|
10262
10321
|
await this.validate(context);
|
|
10263
|
-
|
|
10322
|
+
const result = await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
|
|
10323
|
+
{
|
|
10324
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10325
|
+
action: "cancel-scheduled",
|
|
10326
|
+
symbol,
|
|
10327
|
+
strategyName: context.strategyName,
|
|
10328
|
+
exchangeName: context.exchangeName,
|
|
10329
|
+
frameName: context.frameName,
|
|
10330
|
+
backtest,
|
|
10331
|
+
cancelId,
|
|
10332
|
+
});
|
|
10333
|
+
}
|
|
10334
|
+
return result;
|
|
10264
10335
|
};
|
|
10265
10336
|
/**
|
|
10266
10337
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -10287,7 +10358,19 @@ class StrategyCoreService {
|
|
|
10287
10358
|
closeId,
|
|
10288
10359
|
});
|
|
10289
10360
|
await this.validate(context);
|
|
10290
|
-
|
|
10361
|
+
const result = await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
|
|
10362
|
+
{
|
|
10363
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10364
|
+
action: "close-pending",
|
|
10365
|
+
symbol,
|
|
10366
|
+
strategyName: context.strategyName,
|
|
10367
|
+
exchangeName: context.exchangeName,
|
|
10368
|
+
frameName: context.frameName,
|
|
10369
|
+
backtest,
|
|
10370
|
+
closeId,
|
|
10371
|
+
});
|
|
10372
|
+
}
|
|
10373
|
+
return result;
|
|
10291
10374
|
};
|
|
10292
10375
|
/**
|
|
10293
10376
|
* Disposes the ClientStrategy instance for the given context.
|
|
@@ -10368,7 +10451,20 @@ class StrategyCoreService {
|
|
|
10368
10451
|
backtest,
|
|
10369
10452
|
});
|
|
10370
10453
|
await this.validate(context);
|
|
10371
|
-
|
|
10454
|
+
const result = await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
|
|
10455
|
+
if (result) {
|
|
10456
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10457
|
+
action: "partial-profit",
|
|
10458
|
+
symbol,
|
|
10459
|
+
strategyName: context.strategyName,
|
|
10460
|
+
exchangeName: context.exchangeName,
|
|
10461
|
+
frameName: context.frameName,
|
|
10462
|
+
backtest,
|
|
10463
|
+
percentToClose,
|
|
10464
|
+
currentPrice,
|
|
10465
|
+
});
|
|
10466
|
+
}
|
|
10467
|
+
return result;
|
|
10372
10468
|
};
|
|
10373
10469
|
/**
|
|
10374
10470
|
* Executes partial close at loss level (moving toward SL).
|
|
@@ -10409,7 +10505,20 @@ class StrategyCoreService {
|
|
|
10409
10505
|
backtest,
|
|
10410
10506
|
});
|
|
10411
10507
|
await this.validate(context);
|
|
10412
|
-
|
|
10508
|
+
const result = await this.strategyConnectionService.partialLoss(backtest, symbol, percentToClose, currentPrice, context);
|
|
10509
|
+
if (result) {
|
|
10510
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10511
|
+
action: "partial-loss",
|
|
10512
|
+
symbol,
|
|
10513
|
+
strategyName: context.strategyName,
|
|
10514
|
+
exchangeName: context.exchangeName,
|
|
10515
|
+
frameName: context.frameName,
|
|
10516
|
+
backtest,
|
|
10517
|
+
percentToClose,
|
|
10518
|
+
currentPrice,
|
|
10519
|
+
});
|
|
10520
|
+
}
|
|
10521
|
+
return result;
|
|
10413
10522
|
};
|
|
10414
10523
|
/**
|
|
10415
10524
|
* Adjusts the trailing stop-loss distance for an active pending signal.
|
|
@@ -10448,7 +10557,20 @@ class StrategyCoreService {
|
|
|
10448
10557
|
backtest,
|
|
10449
10558
|
});
|
|
10450
10559
|
await this.validate(context);
|
|
10451
|
-
|
|
10560
|
+
const result = await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
|
|
10561
|
+
if (result) {
|
|
10562
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10563
|
+
action: "trailing-stop",
|
|
10564
|
+
symbol,
|
|
10565
|
+
strategyName: context.strategyName,
|
|
10566
|
+
exchangeName: context.exchangeName,
|
|
10567
|
+
frameName: context.frameName,
|
|
10568
|
+
backtest,
|
|
10569
|
+
percentShift,
|
|
10570
|
+
currentPrice,
|
|
10571
|
+
});
|
|
10572
|
+
}
|
|
10573
|
+
return result;
|
|
10452
10574
|
};
|
|
10453
10575
|
/**
|
|
10454
10576
|
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
@@ -10483,7 +10605,20 @@ class StrategyCoreService {
|
|
|
10483
10605
|
backtest,
|
|
10484
10606
|
});
|
|
10485
10607
|
await this.validate(context);
|
|
10486
|
-
|
|
10608
|
+
const result = await this.strategyConnectionService.trailingTake(backtest, symbol, percentShift, currentPrice, context);
|
|
10609
|
+
if (result) {
|
|
10610
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10611
|
+
action: "trailing-take",
|
|
10612
|
+
symbol,
|
|
10613
|
+
strategyName: context.strategyName,
|
|
10614
|
+
exchangeName: context.exchangeName,
|
|
10615
|
+
frameName: context.frameName,
|
|
10616
|
+
backtest,
|
|
10617
|
+
percentShift,
|
|
10618
|
+
currentPrice,
|
|
10619
|
+
});
|
|
10620
|
+
}
|
|
10621
|
+
return result;
|
|
10487
10622
|
};
|
|
10488
10623
|
/**
|
|
10489
10624
|
* Moves stop-loss to breakeven when price reaches threshold.
|
|
@@ -10513,7 +10648,19 @@ class StrategyCoreService {
|
|
|
10513
10648
|
backtest,
|
|
10514
10649
|
});
|
|
10515
10650
|
await this.validate(context);
|
|
10516
|
-
|
|
10651
|
+
const result = await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
|
|
10652
|
+
if (result) {
|
|
10653
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10654
|
+
action: "breakeven",
|
|
10655
|
+
symbol,
|
|
10656
|
+
strategyName: context.strategyName,
|
|
10657
|
+
exchangeName: context.exchangeName,
|
|
10658
|
+
frameName: context.frameName,
|
|
10659
|
+
backtest,
|
|
10660
|
+
currentPrice,
|
|
10661
|
+
});
|
|
10662
|
+
}
|
|
10663
|
+
return result;
|
|
10517
10664
|
};
|
|
10518
10665
|
}
|
|
10519
10666
|
}
|
|
@@ -10587,7 +10734,7 @@ class SizingGlobalService {
|
|
|
10587
10734
|
* @param context - Context with riskName, exchangeName, frameName
|
|
10588
10735
|
* @returns Unique string key for memoization
|
|
10589
10736
|
*/
|
|
10590
|
-
const CREATE_KEY_FN$
|
|
10737
|
+
const CREATE_KEY_FN$g = (context) => {
|
|
10591
10738
|
const parts = [context.riskName, context.exchangeName];
|
|
10592
10739
|
if (context.frameName)
|
|
10593
10740
|
parts.push(context.frameName);
|
|
@@ -10613,7 +10760,7 @@ class RiskGlobalService {
|
|
|
10613
10760
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
10614
10761
|
* @returns Promise that resolves when validation is complete
|
|
10615
10762
|
*/
|
|
10616
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
10763
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
|
|
10617
10764
|
this.loggerService.log("riskGlobalService validate", {
|
|
10618
10765
|
context,
|
|
10619
10766
|
});
|
|
@@ -10691,7 +10838,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
10691
10838
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10692
10839
|
* @returns Unique string key for memoization
|
|
10693
10840
|
*/
|
|
10694
|
-
const CREATE_KEY_FN$
|
|
10841
|
+
const CREATE_KEY_FN$f = (context) => {
|
|
10695
10842
|
const parts = [context.strategyName, context.exchangeName];
|
|
10696
10843
|
if (context.frameName)
|
|
10697
10844
|
parts.push(context.frameName);
|
|
@@ -10735,7 +10882,7 @@ class ActionCoreService {
|
|
|
10735
10882
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
10736
10883
|
* @returns Promise that resolves when all validations complete
|
|
10737
10884
|
*/
|
|
10738
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
10885
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), async (context) => {
|
|
10739
10886
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
10740
10887
|
context,
|
|
10741
10888
|
});
|
|
@@ -13985,6 +14132,116 @@ const schedule_columns = [
|
|
|
13985
14132
|
},
|
|
13986
14133
|
];
|
|
13987
14134
|
|
|
14135
|
+
/**
|
|
14136
|
+
* Column configuration for strategy markdown reports.
|
|
14137
|
+
*
|
|
14138
|
+
* Defines the table structure for displaying strategy management events in trading reports.
|
|
14139
|
+
* Each column specifies how to format and display strategy actions like partial profit/loss,
|
|
14140
|
+
* trailing stop/take, breakeven, cancel scheduled, and close pending.
|
|
14141
|
+
*
|
|
14142
|
+
* Used by {@link StrategyMarkdownService} to generate markdown tables showing:
|
|
14143
|
+
* - Signal identification (symbol, strategy name, signal ID)
|
|
14144
|
+
* - Action information (action type, percent values, prices)
|
|
14145
|
+
* - Timing information (timestamp, mode: backtest or live)
|
|
14146
|
+
*
|
|
14147
|
+
* @remarks
|
|
14148
|
+
* This configuration tracks all strategy management events - when signals are
|
|
14149
|
+
* modified, cancelled, or closed during their lifecycle.
|
|
14150
|
+
*
|
|
14151
|
+
* @example
|
|
14152
|
+
* ```typescript
|
|
14153
|
+
* import { strategy_columns } from "./assets/strategy.columns";
|
|
14154
|
+
*
|
|
14155
|
+
* // Use with StrategyMarkdownService
|
|
14156
|
+
* const service = new StrategyMarkdownService();
|
|
14157
|
+
* await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, strategy_columns);
|
|
14158
|
+
*
|
|
14159
|
+
* // Or customize to show only key fields
|
|
14160
|
+
* const customColumns = strategy_columns.filter(col =>
|
|
14161
|
+
* ["symbol", "action", "currentPrice", "timestamp"].includes(col.key)
|
|
14162
|
+
* );
|
|
14163
|
+
* await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, customColumns);
|
|
14164
|
+
* ```
|
|
14165
|
+
*
|
|
14166
|
+
* @see {@link StrategyMarkdownService} for usage in report generation
|
|
14167
|
+
* @see {@link ColumnModel} for column interface definition
|
|
14168
|
+
* @see {@link StrategyEvent} for data structure
|
|
14169
|
+
*/
|
|
14170
|
+
const strategy_columns = [
|
|
14171
|
+
{
|
|
14172
|
+
key: "symbol",
|
|
14173
|
+
label: "Symbol",
|
|
14174
|
+
format: (data) => data.symbol,
|
|
14175
|
+
isVisible: () => true,
|
|
14176
|
+
},
|
|
14177
|
+
{
|
|
14178
|
+
key: "strategyName",
|
|
14179
|
+
label: "Strategy",
|
|
14180
|
+
format: (data) => data.strategyName,
|
|
14181
|
+
isVisible: () => true,
|
|
14182
|
+
},
|
|
14183
|
+
{
|
|
14184
|
+
key: "signalId",
|
|
14185
|
+
label: "Signal ID",
|
|
14186
|
+
format: (data) => data.signalId,
|
|
14187
|
+
isVisible: () => true,
|
|
14188
|
+
},
|
|
14189
|
+
{
|
|
14190
|
+
key: "action",
|
|
14191
|
+
label: "Action",
|
|
14192
|
+
format: (data) => data.action,
|
|
14193
|
+
isVisible: () => true,
|
|
14194
|
+
},
|
|
14195
|
+
{
|
|
14196
|
+
key: "currentPrice",
|
|
14197
|
+
label: "Price",
|
|
14198
|
+
format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(8)} USD` : "N/A"),
|
|
14199
|
+
isVisible: () => true,
|
|
14200
|
+
},
|
|
14201
|
+
{
|
|
14202
|
+
key: "percentToClose",
|
|
14203
|
+
label: "% To Close",
|
|
14204
|
+
format: (data) => (data.percentToClose !== undefined ? `${data.percentToClose.toFixed(2)}%` : "N/A"),
|
|
14205
|
+
isVisible: () => true,
|
|
14206
|
+
},
|
|
14207
|
+
{
|
|
14208
|
+
key: "percentShift",
|
|
14209
|
+
label: "% Shift",
|
|
14210
|
+
format: (data) => (data.percentShift !== undefined ? `${data.percentShift.toFixed(2)}%` : "N/A"),
|
|
14211
|
+
isVisible: () => true,
|
|
14212
|
+
},
|
|
14213
|
+
{
|
|
14214
|
+
key: "cancelId",
|
|
14215
|
+
label: "Cancel ID",
|
|
14216
|
+
format: (data) => data.cancelId || "N/A",
|
|
14217
|
+
isVisible: () => true,
|
|
14218
|
+
},
|
|
14219
|
+
{
|
|
14220
|
+
key: "closeId",
|
|
14221
|
+
label: "Close ID",
|
|
14222
|
+
format: (data) => data.closeId || "N/A",
|
|
14223
|
+
isVisible: () => true,
|
|
14224
|
+
},
|
|
14225
|
+
{
|
|
14226
|
+
key: "createdAt",
|
|
14227
|
+
label: "Created At",
|
|
14228
|
+
format: (data) => data.createdAt || "N/A",
|
|
14229
|
+
isVisible: () => true,
|
|
14230
|
+
},
|
|
14231
|
+
{
|
|
14232
|
+
key: "timestamp",
|
|
14233
|
+
label: "Timestamp",
|
|
14234
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
14235
|
+
isVisible: () => true,
|
|
14236
|
+
},
|
|
14237
|
+
{
|
|
14238
|
+
key: "mode",
|
|
14239
|
+
label: "Mode",
|
|
14240
|
+
format: (data) => (data.backtest ? "Backtest" : "Live"),
|
|
14241
|
+
isVisible: () => true,
|
|
14242
|
+
},
|
|
14243
|
+
];
|
|
14244
|
+
|
|
13988
14245
|
/**
|
|
13989
14246
|
* Column configuration for walker strategy comparison table in markdown reports.
|
|
13990
14247
|
*
|
|
@@ -14198,6 +14455,8 @@ const COLUMN_CONFIG = {
|
|
|
14198
14455
|
risk_columns,
|
|
14199
14456
|
/** Columns for scheduled report output */
|
|
14200
14457
|
schedule_columns,
|
|
14458
|
+
/** Columns for strategy management events */
|
|
14459
|
+
strategy_columns,
|
|
14201
14460
|
/** Walker: PnL summary columns */
|
|
14202
14461
|
walker_pnl_columns,
|
|
14203
14462
|
/** Walker: strategy-level summary columns */
|
|
@@ -14234,6 +14493,7 @@ const WILDCARD_TARGET$1 = {
|
|
|
14234
14493
|
partial: true,
|
|
14235
14494
|
performance: true,
|
|
14236
14495
|
risk: true,
|
|
14496
|
+
strategy: true,
|
|
14237
14497
|
schedule: true,
|
|
14238
14498
|
walker: true,
|
|
14239
14499
|
};
|
|
@@ -14463,7 +14723,7 @@ class MarkdownUtils {
|
|
|
14463
14723
|
*
|
|
14464
14724
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
14465
14725
|
*/
|
|
14466
|
-
this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET$1) => {
|
|
14726
|
+
this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, strategy = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET$1) => {
|
|
14467
14727
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
|
|
14468
14728
|
backtest: bt$1,
|
|
14469
14729
|
breakeven,
|
|
@@ -14472,6 +14732,7 @@ class MarkdownUtils {
|
|
|
14472
14732
|
partial,
|
|
14473
14733
|
performance,
|
|
14474
14734
|
risk,
|
|
14735
|
+
strategy,
|
|
14475
14736
|
schedule,
|
|
14476
14737
|
walker,
|
|
14477
14738
|
});
|
|
@@ -14497,6 +14758,9 @@ class MarkdownUtils {
|
|
|
14497
14758
|
if (risk) {
|
|
14498
14759
|
unList.push(bt.riskMarkdownService.subscribe());
|
|
14499
14760
|
}
|
|
14761
|
+
if (strategy) {
|
|
14762
|
+
unList.push(bt.strategyMarkdownService.subscribe());
|
|
14763
|
+
}
|
|
14500
14764
|
if (schedule) {
|
|
14501
14765
|
unList.push(bt.scheduleMarkdownService.subscribe());
|
|
14502
14766
|
}
|
|
@@ -14542,7 +14806,7 @@ class MarkdownUtils {
|
|
|
14542
14806
|
* Markdown.disable();
|
|
14543
14807
|
* ```
|
|
14544
14808
|
*/
|
|
14545
|
-
this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET$1) => {
|
|
14809
|
+
this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, } = WILDCARD_TARGET$1) => {
|
|
14546
14810
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
|
|
14547
14811
|
backtest: bt$1,
|
|
14548
14812
|
breakeven,
|
|
@@ -14551,6 +14815,7 @@ class MarkdownUtils {
|
|
|
14551
14815
|
partial,
|
|
14552
14816
|
performance,
|
|
14553
14817
|
risk,
|
|
14818
|
+
strategy,
|
|
14554
14819
|
schedule,
|
|
14555
14820
|
walker,
|
|
14556
14821
|
});
|
|
@@ -14575,6 +14840,9 @@ class MarkdownUtils {
|
|
|
14575
14840
|
if (risk) {
|
|
14576
14841
|
bt.riskMarkdownService.unsubscribe();
|
|
14577
14842
|
}
|
|
14843
|
+
if (strategy) {
|
|
14844
|
+
bt.strategyMarkdownService.unsubscribe();
|
|
14845
|
+
}
|
|
14578
14846
|
if (schedule) {
|
|
14579
14847
|
bt.scheduleMarkdownService.unsubscribe();
|
|
14580
14848
|
}
|
|
@@ -14687,7 +14955,7 @@ const Markdown = new MarkdownAdapter();
|
|
|
14687
14955
|
* @param backtest - Whether running in backtest mode
|
|
14688
14956
|
* @returns Unique string key for memoization
|
|
14689
14957
|
*/
|
|
14690
|
-
const CREATE_KEY_FN$
|
|
14958
|
+
const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
14691
14959
|
const parts = [symbol, strategyName, exchangeName];
|
|
14692
14960
|
if (frameName)
|
|
14693
14961
|
parts.push(frameName);
|
|
@@ -14704,7 +14972,7 @@ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
14704
14972
|
* @param timestamp - Unix timestamp in milliseconds
|
|
14705
14973
|
* @returns Filename string
|
|
14706
14974
|
*/
|
|
14707
|
-
const CREATE_FILE_NAME_FN$
|
|
14975
|
+
const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
14708
14976
|
const parts = [symbol, strategyName, exchangeName];
|
|
14709
14977
|
if (frameName) {
|
|
14710
14978
|
parts.push(frameName);
|
|
@@ -14733,12 +15001,12 @@ function isUnsafe$3(value) {
|
|
|
14733
15001
|
return false;
|
|
14734
15002
|
}
|
|
14735
15003
|
/** Maximum number of signals to store in backtest reports */
|
|
14736
|
-
const MAX_EVENTS$
|
|
15004
|
+
const MAX_EVENTS$8 = 250;
|
|
14737
15005
|
/**
|
|
14738
15006
|
* Storage class for accumulating closed signals per strategy.
|
|
14739
15007
|
* Maintains a list of all closed signals and provides methods to generate reports.
|
|
14740
15008
|
*/
|
|
14741
|
-
let ReportStorage$
|
|
15009
|
+
let ReportStorage$7 = class ReportStorage {
|
|
14742
15010
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
14743
15011
|
this.symbol = symbol;
|
|
14744
15012
|
this.strategyName = strategyName;
|
|
@@ -14755,7 +15023,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
14755
15023
|
addSignal(data) {
|
|
14756
15024
|
this._signalList.unshift(data);
|
|
14757
15025
|
// Trim queue if exceeded MAX_EVENTS
|
|
14758
|
-
if (this._signalList.length > MAX_EVENTS$
|
|
15026
|
+
if (this._signalList.length > MAX_EVENTS$8) {
|
|
14759
15027
|
this._signalList.pop();
|
|
14760
15028
|
}
|
|
14761
15029
|
}
|
|
@@ -14879,7 +15147,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
14879
15147
|
async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
|
|
14880
15148
|
const markdown = await this.getReport(strategyName, columns);
|
|
14881
15149
|
const timestamp = Date.now();
|
|
14882
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
15150
|
+
const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
14883
15151
|
await Markdown.writeData("backtest", markdown, {
|
|
14884
15152
|
path,
|
|
14885
15153
|
file: filename,
|
|
@@ -14926,7 +15194,7 @@ class BacktestMarkdownService {
|
|
|
14926
15194
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
14927
15195
|
* Each combination gets its own isolated storage instance.
|
|
14928
15196
|
*/
|
|
14929
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
15197
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$7(symbol, strategyName, exchangeName, frameName));
|
|
14930
15198
|
/**
|
|
14931
15199
|
* Processes tick events and accumulates closed signals.
|
|
14932
15200
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -15083,7 +15351,7 @@ class BacktestMarkdownService {
|
|
|
15083
15351
|
payload,
|
|
15084
15352
|
});
|
|
15085
15353
|
if (payload) {
|
|
15086
|
-
const key = CREATE_KEY_FN$
|
|
15354
|
+
const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
15087
15355
|
this.getStorage.clear(key);
|
|
15088
15356
|
}
|
|
15089
15357
|
else {
|
|
@@ -15145,7 +15413,7 @@ class BacktestMarkdownService {
|
|
|
15145
15413
|
* @param backtest - Whether running in backtest mode
|
|
15146
15414
|
* @returns Unique string key for memoization
|
|
15147
15415
|
*/
|
|
15148
|
-
const CREATE_KEY_FN$
|
|
15416
|
+
const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
15149
15417
|
const parts = [symbol, strategyName, exchangeName];
|
|
15150
15418
|
if (frameName)
|
|
15151
15419
|
parts.push(frameName);
|
|
@@ -15162,7 +15430,7 @@ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
15162
15430
|
* @param timestamp - Unix timestamp in milliseconds
|
|
15163
15431
|
* @returns Filename string
|
|
15164
15432
|
*/
|
|
15165
|
-
const CREATE_FILE_NAME_FN$
|
|
15433
|
+
const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
15166
15434
|
const parts = [symbol, strategyName, exchangeName];
|
|
15167
15435
|
if (frameName) {
|
|
15168
15436
|
parts.push(frameName);
|
|
@@ -15191,12 +15459,12 @@ function isUnsafe$2(value) {
|
|
|
15191
15459
|
return false;
|
|
15192
15460
|
}
|
|
15193
15461
|
/** Maximum number of events to store in live trading reports */
|
|
15194
|
-
const MAX_EVENTS$
|
|
15462
|
+
const MAX_EVENTS$7 = 250;
|
|
15195
15463
|
/**
|
|
15196
15464
|
* Storage class for accumulating all tick events per strategy.
|
|
15197
15465
|
* Maintains a chronological list of all events (idle, opened, active, closed).
|
|
15198
15466
|
*/
|
|
15199
|
-
let ReportStorage$
|
|
15467
|
+
let ReportStorage$6 = class ReportStorage {
|
|
15200
15468
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
15201
15469
|
this.symbol = symbol;
|
|
15202
15470
|
this.strategyName = strategyName;
|
|
@@ -15228,7 +15496,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15228
15496
|
}
|
|
15229
15497
|
{
|
|
15230
15498
|
this._eventList.unshift(newEvent);
|
|
15231
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15499
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15232
15500
|
this._eventList.pop();
|
|
15233
15501
|
}
|
|
15234
15502
|
}
|
|
@@ -15255,7 +15523,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15255
15523
|
totalExecuted: data.signal.totalExecuted,
|
|
15256
15524
|
});
|
|
15257
15525
|
// Trim queue if exceeded MAX_EVENTS
|
|
15258
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15526
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15259
15527
|
this._eventList.pop();
|
|
15260
15528
|
}
|
|
15261
15529
|
}
|
|
@@ -15294,7 +15562,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15294
15562
|
// If no previous active event found, add new event
|
|
15295
15563
|
this._eventList.unshift(newEvent);
|
|
15296
15564
|
// Trim queue if exceeded MAX_EVENTS
|
|
15297
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15565
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15298
15566
|
this._eventList.pop();
|
|
15299
15567
|
}
|
|
15300
15568
|
}
|
|
@@ -15326,7 +15594,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15326
15594
|
};
|
|
15327
15595
|
this._eventList.unshift(newEvent);
|
|
15328
15596
|
// Trim queue if exceeded MAX_EVENTS
|
|
15329
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15597
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15330
15598
|
this._eventList.pop();
|
|
15331
15599
|
}
|
|
15332
15600
|
}
|
|
@@ -15352,7 +15620,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15352
15620
|
totalExecuted: data.signal.totalExecuted,
|
|
15353
15621
|
});
|
|
15354
15622
|
// Trim queue if exceeded MAX_EVENTS
|
|
15355
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15623
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15356
15624
|
this._eventList.pop();
|
|
15357
15625
|
}
|
|
15358
15626
|
}
|
|
@@ -15391,7 +15659,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15391
15659
|
// If no previous waiting event found, add new event
|
|
15392
15660
|
this._eventList.unshift(newEvent);
|
|
15393
15661
|
// Trim queue if exceeded MAX_EVENTS
|
|
15394
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15662
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15395
15663
|
this._eventList.pop();
|
|
15396
15664
|
}
|
|
15397
15665
|
}
|
|
@@ -15418,7 +15686,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15418
15686
|
cancelReason: data.reason,
|
|
15419
15687
|
});
|
|
15420
15688
|
// Trim queue if exceeded MAX_EVENTS
|
|
15421
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15689
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15422
15690
|
this._eventList.pop();
|
|
15423
15691
|
}
|
|
15424
15692
|
}
|
|
@@ -15557,7 +15825,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15557
15825
|
async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
|
|
15558
15826
|
const markdown = await this.getReport(strategyName, columns);
|
|
15559
15827
|
const timestamp = Date.now();
|
|
15560
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
15828
|
+
const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
15561
15829
|
await Markdown.writeData("live", markdown, {
|
|
15562
15830
|
path,
|
|
15563
15831
|
signalId: "",
|
|
@@ -15607,7 +15875,7 @@ class LiveMarkdownService {
|
|
|
15607
15875
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
15608
15876
|
* Each combination gets its own isolated storage instance.
|
|
15609
15877
|
*/
|
|
15610
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
15878
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
|
|
15611
15879
|
/**
|
|
15612
15880
|
* Subscribes to live signal emitter to receive tick events.
|
|
15613
15881
|
* Protected against multiple subscriptions.
|
|
@@ -15825,7 +16093,7 @@ class LiveMarkdownService {
|
|
|
15825
16093
|
payload,
|
|
15826
16094
|
});
|
|
15827
16095
|
if (payload) {
|
|
15828
|
-
const key = CREATE_KEY_FN$
|
|
16096
|
+
const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
15829
16097
|
this.getStorage.clear(key);
|
|
15830
16098
|
}
|
|
15831
16099
|
else {
|
|
@@ -15845,7 +16113,7 @@ class LiveMarkdownService {
|
|
|
15845
16113
|
* @param backtest - Whether running in backtest mode
|
|
15846
16114
|
* @returns Unique string key for memoization
|
|
15847
16115
|
*/
|
|
15848
|
-
const CREATE_KEY_FN$
|
|
16116
|
+
const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
15849
16117
|
const parts = [symbol, strategyName, exchangeName];
|
|
15850
16118
|
if (frameName)
|
|
15851
16119
|
parts.push(frameName);
|
|
@@ -15862,7 +16130,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
15862
16130
|
* @param timestamp - Unix timestamp in milliseconds
|
|
15863
16131
|
* @returns Filename string
|
|
15864
16132
|
*/
|
|
15865
|
-
const CREATE_FILE_NAME_FN$
|
|
16133
|
+
const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
15866
16134
|
const parts = [symbol, strategyName, exchangeName];
|
|
15867
16135
|
if (frameName) {
|
|
15868
16136
|
parts.push(frameName);
|
|
@@ -15873,12 +16141,12 @@ const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
15873
16141
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
15874
16142
|
};
|
|
15875
16143
|
/** Maximum number of events to store in schedule reports */
|
|
15876
|
-
const MAX_EVENTS$
|
|
16144
|
+
const MAX_EVENTS$6 = 250;
|
|
15877
16145
|
/**
|
|
15878
16146
|
* Storage class for accumulating scheduled signal events per strategy.
|
|
15879
16147
|
* Maintains a chronological list of scheduled and cancelled events.
|
|
15880
16148
|
*/
|
|
15881
|
-
let ReportStorage$
|
|
16149
|
+
let ReportStorage$5 = class ReportStorage {
|
|
15882
16150
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
15883
16151
|
this.symbol = symbol;
|
|
15884
16152
|
this.strategyName = strategyName;
|
|
@@ -15909,7 +16177,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
15909
16177
|
totalExecuted: data.signal.totalExecuted,
|
|
15910
16178
|
});
|
|
15911
16179
|
// Trim queue if exceeded MAX_EVENTS
|
|
15912
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
16180
|
+
if (this._eventList.length > MAX_EVENTS$6) {
|
|
15913
16181
|
this._eventList.pop();
|
|
15914
16182
|
}
|
|
15915
16183
|
}
|
|
@@ -15939,7 +16207,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
15939
16207
|
};
|
|
15940
16208
|
this._eventList.unshift(newEvent);
|
|
15941
16209
|
// Trim queue if exceeded MAX_EVENTS
|
|
15942
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
16210
|
+
if (this._eventList.length > MAX_EVENTS$6) {
|
|
15943
16211
|
this._eventList.pop();
|
|
15944
16212
|
}
|
|
15945
16213
|
}
|
|
@@ -15972,7 +16240,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
15972
16240
|
};
|
|
15973
16241
|
this._eventList.unshift(newEvent);
|
|
15974
16242
|
// Trim queue if exceeded MAX_EVENTS
|
|
15975
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
16243
|
+
if (this._eventList.length > MAX_EVENTS$6) {
|
|
15976
16244
|
this._eventList.pop();
|
|
15977
16245
|
}
|
|
15978
16246
|
}
|
|
@@ -16079,7 +16347,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
16079
16347
|
async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
|
|
16080
16348
|
const markdown = await this.getReport(strategyName, columns);
|
|
16081
16349
|
const timestamp = Date.now();
|
|
16082
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
16350
|
+
const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
16083
16351
|
await Markdown.writeData("schedule", markdown, {
|
|
16084
16352
|
path,
|
|
16085
16353
|
file: filename,
|
|
@@ -16120,7 +16388,7 @@ class ScheduleMarkdownService {
|
|
|
16120
16388
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
16121
16389
|
* Each combination gets its own isolated storage instance.
|
|
16122
16390
|
*/
|
|
16123
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
16391
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
|
|
16124
16392
|
/**
|
|
16125
16393
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
16126
16394
|
* Protected against multiple subscriptions.
|
|
@@ -16323,7 +16591,7 @@ class ScheduleMarkdownService {
|
|
|
16323
16591
|
payload,
|
|
16324
16592
|
});
|
|
16325
16593
|
if (payload) {
|
|
16326
|
-
const key = CREATE_KEY_FN$
|
|
16594
|
+
const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
16327
16595
|
this.getStorage.clear(key);
|
|
16328
16596
|
}
|
|
16329
16597
|
else {
|
|
@@ -16343,7 +16611,7 @@ class ScheduleMarkdownService {
|
|
|
16343
16611
|
* @param backtest - Whether running in backtest mode
|
|
16344
16612
|
* @returns Unique string key for memoization
|
|
16345
16613
|
*/
|
|
16346
|
-
const CREATE_KEY_FN$
|
|
16614
|
+
const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
16347
16615
|
const parts = [symbol, strategyName, exchangeName];
|
|
16348
16616
|
if (frameName)
|
|
16349
16617
|
parts.push(frameName);
|
|
@@ -16360,7 +16628,7 @@ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
16360
16628
|
* @param timestamp - Unix timestamp in milliseconds
|
|
16361
16629
|
* @returns Filename string
|
|
16362
16630
|
*/
|
|
16363
|
-
const CREATE_FILE_NAME_FN$
|
|
16631
|
+
const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
16364
16632
|
const parts = [symbol, strategyName, exchangeName];
|
|
16365
16633
|
if (frameName) {
|
|
16366
16634
|
parts.push(frameName);
|
|
@@ -16380,7 +16648,7 @@ function percentile(sortedArray, p) {
|
|
|
16380
16648
|
return sortedArray[Math.max(0, index)];
|
|
16381
16649
|
}
|
|
16382
16650
|
/** Maximum number of performance events to store per strategy */
|
|
16383
|
-
const MAX_EVENTS$
|
|
16651
|
+
const MAX_EVENTS$5 = 10000;
|
|
16384
16652
|
/**
|
|
16385
16653
|
* Storage class for accumulating performance metrics per strategy.
|
|
16386
16654
|
* Maintains a list of all performance events and provides aggregated statistics.
|
|
@@ -16402,7 +16670,7 @@ class PerformanceStorage {
|
|
|
16402
16670
|
addEvent(event) {
|
|
16403
16671
|
this._events.unshift(event);
|
|
16404
16672
|
// Trim queue if exceeded MAX_EVENTS (keep most recent)
|
|
16405
|
-
if (this._events.length > MAX_EVENTS$
|
|
16673
|
+
if (this._events.length > MAX_EVENTS$5) {
|
|
16406
16674
|
this._events.pop();
|
|
16407
16675
|
}
|
|
16408
16676
|
}
|
|
@@ -16544,7 +16812,7 @@ class PerformanceStorage {
|
|
|
16544
16812
|
async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
|
|
16545
16813
|
const markdown = await this.getReport(strategyName, columns);
|
|
16546
16814
|
const timestamp = Date.now();
|
|
16547
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
16815
|
+
const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
16548
16816
|
await Markdown.writeData("performance", markdown, {
|
|
16549
16817
|
path,
|
|
16550
16818
|
file: filename,
|
|
@@ -16591,7 +16859,7 @@ class PerformanceMarkdownService {
|
|
|
16591
16859
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
16592
16860
|
* Each combination gets its own isolated storage instance.
|
|
16593
16861
|
*/
|
|
16594
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
16862
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
|
|
16595
16863
|
/**
|
|
16596
16864
|
* Subscribes to performance emitter to receive performance events.
|
|
16597
16865
|
* Protected against multiple subscriptions.
|
|
@@ -16758,7 +17026,7 @@ class PerformanceMarkdownService {
|
|
|
16758
17026
|
payload,
|
|
16759
17027
|
});
|
|
16760
17028
|
if (payload) {
|
|
16761
|
-
const key = CREATE_KEY_FN$
|
|
17029
|
+
const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
16762
17030
|
this.getStorage.clear(key);
|
|
16763
17031
|
}
|
|
16764
17032
|
else {
|
|
@@ -16772,7 +17040,7 @@ class PerformanceMarkdownService {
|
|
|
16772
17040
|
* Creates a filename for markdown report based on walker name.
|
|
16773
17041
|
* Filename format: "walkerName-timestamp.md"
|
|
16774
17042
|
*/
|
|
16775
|
-
const CREATE_FILE_NAME_FN$
|
|
17043
|
+
const CREATE_FILE_NAME_FN$5 = (walkerName, timestamp) => {
|
|
16776
17044
|
return `${walkerName}-${timestamp}.md`;
|
|
16777
17045
|
};
|
|
16778
17046
|
/**
|
|
@@ -16807,7 +17075,7 @@ function formatMetric(value) {
|
|
|
16807
17075
|
* Storage class for accumulating walker results.
|
|
16808
17076
|
* Maintains a list of all strategy results and provides methods to generate reports.
|
|
16809
17077
|
*/
|
|
16810
|
-
let ReportStorage$
|
|
17078
|
+
let ReportStorage$4 = class ReportStorage {
|
|
16811
17079
|
constructor(walkerName) {
|
|
16812
17080
|
this.walkerName = walkerName;
|
|
16813
17081
|
/** Walker metadata (set from first addResult call) */
|
|
@@ -16995,7 +17263,7 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
16995
17263
|
async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
16996
17264
|
const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
16997
17265
|
const timestamp = Date.now();
|
|
16998
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
17266
|
+
const filename = CREATE_FILE_NAME_FN$5(this.walkerName, timestamp);
|
|
16999
17267
|
await Markdown.writeData("walker", markdown, {
|
|
17000
17268
|
path,
|
|
17001
17269
|
file: filename,
|
|
@@ -17031,7 +17299,7 @@ class WalkerMarkdownService {
|
|
|
17031
17299
|
* Memoized function to get or create ReportStorage for a walker.
|
|
17032
17300
|
* Each walker gets its own isolated storage instance.
|
|
17033
17301
|
*/
|
|
17034
|
-
this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$
|
|
17302
|
+
this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$4(walkerName));
|
|
17035
17303
|
/**
|
|
17036
17304
|
* Subscribes to walker emitter to receive walker progress events.
|
|
17037
17305
|
* Protected against multiple subscriptions.
|
|
@@ -17228,7 +17496,7 @@ class WalkerMarkdownService {
|
|
|
17228
17496
|
* @param backtest - Whether running in backtest mode
|
|
17229
17497
|
* @returns Unique string key for memoization
|
|
17230
17498
|
*/
|
|
17231
|
-
const CREATE_KEY_FN$
|
|
17499
|
+
const CREATE_KEY_FN$a = (exchangeName, frameName, backtest) => {
|
|
17232
17500
|
const parts = [exchangeName];
|
|
17233
17501
|
if (frameName)
|
|
17234
17502
|
parts.push(frameName);
|
|
@@ -17239,7 +17507,7 @@ const CREATE_KEY_FN$9 = (exchangeName, frameName, backtest) => {
|
|
|
17239
17507
|
* Creates a filename for markdown report based on memoization key components.
|
|
17240
17508
|
* Filename format: "strategyName_exchangeName_frameName-timestamp.md"
|
|
17241
17509
|
*/
|
|
17242
|
-
const CREATE_FILE_NAME_FN$
|
|
17510
|
+
const CREATE_FILE_NAME_FN$4 = (strategyName, exchangeName, frameName, timestamp) => {
|
|
17243
17511
|
const parts = [strategyName, exchangeName];
|
|
17244
17512
|
if (frameName) {
|
|
17245
17513
|
parts.push(frameName);
|
|
@@ -17272,7 +17540,7 @@ function isUnsafe(value) {
|
|
|
17272
17540
|
return false;
|
|
17273
17541
|
}
|
|
17274
17542
|
/** Maximum number of signals to store per symbol in heatmap reports */
|
|
17275
|
-
const MAX_EVENTS$
|
|
17543
|
+
const MAX_EVENTS$4 = 250;
|
|
17276
17544
|
/**
|
|
17277
17545
|
* Storage class for accumulating closed signals per strategy and generating heatmap.
|
|
17278
17546
|
* Maintains symbol-level statistics and provides portfolio-wide metrics.
|
|
@@ -17298,7 +17566,7 @@ class HeatmapStorage {
|
|
|
17298
17566
|
const signals = this.symbolData.get(symbol);
|
|
17299
17567
|
signals.unshift(data);
|
|
17300
17568
|
// Trim queue if exceeded MAX_EVENTS per symbol
|
|
17301
|
-
if (signals.length > MAX_EVENTS$
|
|
17569
|
+
if (signals.length > MAX_EVENTS$4) {
|
|
17302
17570
|
signals.pop();
|
|
17303
17571
|
}
|
|
17304
17572
|
}
|
|
@@ -17549,7 +17817,7 @@ class HeatmapStorage {
|
|
|
17549
17817
|
async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
|
|
17550
17818
|
const markdown = await this.getReport(strategyName, columns);
|
|
17551
17819
|
const timestamp = Date.now();
|
|
17552
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
17820
|
+
const filename = CREATE_FILE_NAME_FN$4(strategyName, this.exchangeName, this.frameName, timestamp);
|
|
17553
17821
|
await Markdown.writeData("heat", markdown, {
|
|
17554
17822
|
path,
|
|
17555
17823
|
file: filename,
|
|
@@ -17595,7 +17863,7 @@ class HeatMarkdownService {
|
|
|
17595
17863
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
17596
17864
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
17597
17865
|
*/
|
|
17598
|
-
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
17866
|
+
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
17599
17867
|
/**
|
|
17600
17868
|
* Subscribes to signal emitter to receive tick events.
|
|
17601
17869
|
* Protected against multiple subscriptions.
|
|
@@ -17790,7 +18058,7 @@ class HeatMarkdownService {
|
|
|
17790
18058
|
payload,
|
|
17791
18059
|
});
|
|
17792
18060
|
if (payload) {
|
|
17793
|
-
const key = CREATE_KEY_FN$
|
|
18061
|
+
const key = CREATE_KEY_FN$a(payload.exchangeName, payload.frameName, payload.backtest);
|
|
17794
18062
|
this.getStorage.clear(key);
|
|
17795
18063
|
}
|
|
17796
18064
|
else {
|
|
@@ -18821,7 +19089,7 @@ class ClientPartial {
|
|
|
18821
19089
|
* @param backtest - Whether running in backtest mode
|
|
18822
19090
|
* @returns Unique string key for memoization
|
|
18823
19091
|
*/
|
|
18824
|
-
const CREATE_KEY_FN$
|
|
19092
|
+
const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
18825
19093
|
/**
|
|
18826
19094
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
18827
19095
|
*
|
|
@@ -18943,7 +19211,7 @@ class PartialConnectionService {
|
|
|
18943
19211
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
18944
19212
|
* Value: ClientPartial instance with logger and event emitters
|
|
18945
19213
|
*/
|
|
18946
|
-
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
19214
|
+
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
|
|
18947
19215
|
return new ClientPartial({
|
|
18948
19216
|
signalId,
|
|
18949
19217
|
logger: this.loggerService,
|
|
@@ -19033,7 +19301,7 @@ class PartialConnectionService {
|
|
|
19033
19301
|
const partial = this.getPartial(data.id, backtest);
|
|
19034
19302
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
19035
19303
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
19036
|
-
const key = CREATE_KEY_FN$
|
|
19304
|
+
const key = CREATE_KEY_FN$9(data.id, backtest);
|
|
19037
19305
|
this.getPartial.clear(key);
|
|
19038
19306
|
};
|
|
19039
19307
|
}
|
|
@@ -19049,7 +19317,7 @@ class PartialConnectionService {
|
|
|
19049
19317
|
* @param backtest - Whether running in backtest mode
|
|
19050
19318
|
* @returns Unique string key for memoization
|
|
19051
19319
|
*/
|
|
19052
|
-
const CREATE_KEY_FN$
|
|
19320
|
+
const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
19053
19321
|
const parts = [symbol, strategyName, exchangeName];
|
|
19054
19322
|
if (frameName)
|
|
19055
19323
|
parts.push(frameName);
|
|
@@ -19060,7 +19328,7 @@ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
19060
19328
|
* Creates a filename for markdown report based on memoization key components.
|
|
19061
19329
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
19062
19330
|
*/
|
|
19063
|
-
const CREATE_FILE_NAME_FN$
|
|
19331
|
+
const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
19064
19332
|
const parts = [symbol, strategyName, exchangeName];
|
|
19065
19333
|
if (frameName) {
|
|
19066
19334
|
parts.push(frameName);
|
|
@@ -19071,12 +19339,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
19071
19339
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
19072
19340
|
};
|
|
19073
19341
|
/** Maximum number of events to store in partial reports */
|
|
19074
|
-
const MAX_EVENTS$
|
|
19342
|
+
const MAX_EVENTS$3 = 250;
|
|
19075
19343
|
/**
|
|
19076
19344
|
* Storage class for accumulating partial profit/loss events per symbol-strategy pair.
|
|
19077
19345
|
* Maintains a chronological list of profit and loss level events.
|
|
19078
19346
|
*/
|
|
19079
|
-
let ReportStorage$
|
|
19347
|
+
let ReportStorage$3 = class ReportStorage {
|
|
19080
19348
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
19081
19349
|
this.symbol = symbol;
|
|
19082
19350
|
this.strategyName = strategyName;
|
|
@@ -19113,7 +19381,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
19113
19381
|
backtest,
|
|
19114
19382
|
});
|
|
19115
19383
|
// Trim queue if exceeded MAX_EVENTS
|
|
19116
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19384
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
19117
19385
|
this._eventList.pop();
|
|
19118
19386
|
}
|
|
19119
19387
|
}
|
|
@@ -19145,7 +19413,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
19145
19413
|
backtest,
|
|
19146
19414
|
});
|
|
19147
19415
|
// Trim queue if exceeded MAX_EVENTS
|
|
19148
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19416
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
19149
19417
|
this._eventList.pop();
|
|
19150
19418
|
}
|
|
19151
19419
|
}
|
|
@@ -19221,7 +19489,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
19221
19489
|
async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
|
|
19222
19490
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
19223
19491
|
const timestamp = Date.now();
|
|
19224
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
19492
|
+
const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
19225
19493
|
await Markdown.writeData("partial", markdown, {
|
|
19226
19494
|
path,
|
|
19227
19495
|
file: filename,
|
|
@@ -19262,7 +19530,7 @@ class PartialMarkdownService {
|
|
|
19262
19530
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
19263
19531
|
* Each combination gets its own isolated storage instance.
|
|
19264
19532
|
*/
|
|
19265
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
19533
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
|
|
19266
19534
|
/**
|
|
19267
19535
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
19268
19536
|
* Protected against multiple subscriptions.
|
|
@@ -19472,7 +19740,7 @@ class PartialMarkdownService {
|
|
|
19472
19740
|
payload,
|
|
19473
19741
|
});
|
|
19474
19742
|
if (payload) {
|
|
19475
|
-
const key = CREATE_KEY_FN$
|
|
19743
|
+
const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
19476
19744
|
this.getStorage.clear(key);
|
|
19477
19745
|
}
|
|
19478
19746
|
else {
|
|
@@ -19488,7 +19756,7 @@ class PartialMarkdownService {
|
|
|
19488
19756
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
19489
19757
|
* @returns Unique string key for memoization
|
|
19490
19758
|
*/
|
|
19491
|
-
const CREATE_KEY_FN$
|
|
19759
|
+
const CREATE_KEY_FN$7 = (context) => {
|
|
19492
19760
|
const parts = [context.strategyName, context.exchangeName];
|
|
19493
19761
|
if (context.frameName)
|
|
19494
19762
|
parts.push(context.frameName);
|
|
@@ -19562,7 +19830,7 @@ class PartialGlobalService {
|
|
|
19562
19830
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
19563
19831
|
* @param methodName - Name of the calling method for error tracking
|
|
19564
19832
|
*/
|
|
19565
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
19833
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
|
|
19566
19834
|
this.loggerService.log("partialGlobalService validate", {
|
|
19567
19835
|
context,
|
|
19568
19836
|
methodName,
|
|
@@ -20017,7 +20285,7 @@ class ClientBreakeven {
|
|
|
20017
20285
|
* @param backtest - Whether running in backtest mode
|
|
20018
20286
|
* @returns Unique string key for memoization
|
|
20019
20287
|
*/
|
|
20020
|
-
const CREATE_KEY_FN$
|
|
20288
|
+
const CREATE_KEY_FN$6 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
20021
20289
|
/**
|
|
20022
20290
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
20023
20291
|
*
|
|
@@ -20103,7 +20371,7 @@ class BreakevenConnectionService {
|
|
|
20103
20371
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
20104
20372
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
20105
20373
|
*/
|
|
20106
|
-
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
20374
|
+
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$6(signalId, backtest), (signalId, backtest) => {
|
|
20107
20375
|
return new ClientBreakeven({
|
|
20108
20376
|
signalId,
|
|
20109
20377
|
logger: this.loggerService,
|
|
@@ -20164,7 +20432,7 @@ class BreakevenConnectionService {
|
|
|
20164
20432
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
20165
20433
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
20166
20434
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
20167
|
-
const key = CREATE_KEY_FN$
|
|
20435
|
+
const key = CREATE_KEY_FN$6(data.id, backtest);
|
|
20168
20436
|
this.getBreakeven.clear(key);
|
|
20169
20437
|
};
|
|
20170
20438
|
}
|
|
@@ -20180,7 +20448,7 @@ class BreakevenConnectionService {
|
|
|
20180
20448
|
* @param backtest - Whether running in backtest mode
|
|
20181
20449
|
* @returns Unique string key for memoization
|
|
20182
20450
|
*/
|
|
20183
|
-
const CREATE_KEY_FN$
|
|
20451
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20184
20452
|
const parts = [symbol, strategyName, exchangeName];
|
|
20185
20453
|
if (frameName)
|
|
20186
20454
|
parts.push(frameName);
|
|
@@ -20191,7 +20459,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
20191
20459
|
* Creates a filename for markdown report based on memoization key components.
|
|
20192
20460
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
20193
20461
|
*/
|
|
20194
|
-
const CREATE_FILE_NAME_FN$
|
|
20462
|
+
const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
20195
20463
|
const parts = [symbol, strategyName, exchangeName];
|
|
20196
20464
|
if (frameName) {
|
|
20197
20465
|
parts.push(frameName);
|
|
@@ -20202,12 +20470,12 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
20202
20470
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
20203
20471
|
};
|
|
20204
20472
|
/** Maximum number of events to store in breakeven reports */
|
|
20205
|
-
const MAX_EVENTS$
|
|
20473
|
+
const MAX_EVENTS$2 = 250;
|
|
20206
20474
|
/**
|
|
20207
20475
|
* Storage class for accumulating breakeven events per symbol-strategy pair.
|
|
20208
20476
|
* Maintains a chronological list of breakeven events.
|
|
20209
20477
|
*/
|
|
20210
|
-
let ReportStorage$
|
|
20478
|
+
let ReportStorage$2 = class ReportStorage {
|
|
20211
20479
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
20212
20480
|
this.symbol = symbol;
|
|
20213
20481
|
this.strategyName = strategyName;
|
|
@@ -20242,7 +20510,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
20242
20510
|
backtest,
|
|
20243
20511
|
});
|
|
20244
20512
|
// Trim queue if exceeded MAX_EVENTS
|
|
20245
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20513
|
+
if (this._eventList.length > MAX_EVENTS$2) {
|
|
20246
20514
|
this._eventList.pop();
|
|
20247
20515
|
}
|
|
20248
20516
|
}
|
|
@@ -20310,7 +20578,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
20310
20578
|
async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
|
|
20311
20579
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
20312
20580
|
const timestamp = Date.now();
|
|
20313
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
20581
|
+
const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
20314
20582
|
await Markdown.writeData("breakeven", markdown, {
|
|
20315
20583
|
path,
|
|
20316
20584
|
file: filename,
|
|
@@ -20351,7 +20619,7 @@ class BreakevenMarkdownService {
|
|
|
20351
20619
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20352
20620
|
* Each combination gets its own isolated storage instance.
|
|
20353
20621
|
*/
|
|
20354
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20622
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName));
|
|
20355
20623
|
/**
|
|
20356
20624
|
* Subscribes to breakeven signal emitter to receive events.
|
|
20357
20625
|
* Protected against multiple subscriptions.
|
|
@@ -20540,7 +20808,7 @@ class BreakevenMarkdownService {
|
|
|
20540
20808
|
payload,
|
|
20541
20809
|
});
|
|
20542
20810
|
if (payload) {
|
|
20543
|
-
const key = CREATE_KEY_FN$
|
|
20811
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20544
20812
|
this.getStorage.clear(key);
|
|
20545
20813
|
}
|
|
20546
20814
|
else {
|
|
@@ -20556,7 +20824,7 @@ class BreakevenMarkdownService {
|
|
|
20556
20824
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
20557
20825
|
* @returns Unique string key for memoization
|
|
20558
20826
|
*/
|
|
20559
|
-
const CREATE_KEY_FN$
|
|
20827
|
+
const CREATE_KEY_FN$4 = (context) => {
|
|
20560
20828
|
const parts = [context.strategyName, context.exchangeName];
|
|
20561
20829
|
if (context.frameName)
|
|
20562
20830
|
parts.push(context.frameName);
|
|
@@ -20630,7 +20898,7 @@ class BreakevenGlobalService {
|
|
|
20630
20898
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
20631
20899
|
* @param methodName - Name of the calling method for error tracking
|
|
20632
20900
|
*/
|
|
20633
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
20901
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$4(context), (context, methodName) => {
|
|
20634
20902
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
20635
20903
|
context,
|
|
20636
20904
|
methodName,
|
|
@@ -20843,7 +21111,7 @@ class ConfigValidationService {
|
|
|
20843
21111
|
* @param backtest - Whether running in backtest mode
|
|
20844
21112
|
* @returns Unique string key for memoization
|
|
20845
21113
|
*/
|
|
20846
|
-
const CREATE_KEY_FN$
|
|
21114
|
+
const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20847
21115
|
const parts = [symbol, strategyName, exchangeName];
|
|
20848
21116
|
if (frameName)
|
|
20849
21117
|
parts.push(frameName);
|
|
@@ -20854,7 +21122,7 @@ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
20854
21122
|
* Creates a filename for markdown report based on memoization key components.
|
|
20855
21123
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
20856
21124
|
*/
|
|
20857
|
-
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
21125
|
+
const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
20858
21126
|
const parts = [symbol, strategyName, exchangeName];
|
|
20859
21127
|
if (frameName) {
|
|
20860
21128
|
parts.push(frameName);
|
|
@@ -20865,12 +21133,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
|
|
|
20865
21133
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
20866
21134
|
};
|
|
20867
21135
|
/** Maximum number of events to store in risk reports */
|
|
20868
|
-
const MAX_EVENTS = 250;
|
|
21136
|
+
const MAX_EVENTS$1 = 250;
|
|
20869
21137
|
/**
|
|
20870
21138
|
* Storage class for accumulating risk rejection events per symbol-strategy pair.
|
|
20871
21139
|
* Maintains a chronological list of rejected signals due to risk limits.
|
|
20872
21140
|
*/
|
|
20873
|
-
class ReportStorage {
|
|
21141
|
+
let ReportStorage$1 = class ReportStorage {
|
|
20874
21142
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
20875
21143
|
this.symbol = symbol;
|
|
20876
21144
|
this.strategyName = strategyName;
|
|
@@ -20887,7 +21155,7 @@ class ReportStorage {
|
|
|
20887
21155
|
addRejectionEvent(event) {
|
|
20888
21156
|
this._eventList.unshift(event);
|
|
20889
21157
|
// Trim queue if exceeded MAX_EVENTS
|
|
20890
|
-
if (this._eventList.length > MAX_EVENTS) {
|
|
21158
|
+
if (this._eventList.length > MAX_EVENTS$1) {
|
|
20891
21159
|
this._eventList.pop();
|
|
20892
21160
|
}
|
|
20893
21161
|
}
|
|
@@ -20971,7 +21239,7 @@ class ReportStorage {
|
|
|
20971
21239
|
async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
|
|
20972
21240
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
20973
21241
|
const timestamp = Date.now();
|
|
20974
|
-
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
21242
|
+
const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
20975
21243
|
await Markdown.writeData("risk", markdown, {
|
|
20976
21244
|
path,
|
|
20977
21245
|
file: filename,
|
|
@@ -20982,7 +21250,7 @@ class ReportStorage {
|
|
|
20982
21250
|
frameName: this.frameName
|
|
20983
21251
|
});
|
|
20984
21252
|
}
|
|
20985
|
-
}
|
|
21253
|
+
};
|
|
20986
21254
|
/**
|
|
20987
21255
|
* Service for generating and saving risk rejection markdown reports.
|
|
20988
21256
|
*
|
|
@@ -21012,7 +21280,7 @@ class RiskMarkdownService {
|
|
|
21012
21280
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21013
21281
|
* Each combination gets its own isolated storage instance.
|
|
21014
21282
|
*/
|
|
21015
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21283
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$3(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
|
|
21016
21284
|
/**
|
|
21017
21285
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
21018
21286
|
* Protected against multiple subscriptions.
|
|
@@ -21201,7 +21469,7 @@ class RiskMarkdownService {
|
|
|
21201
21469
|
payload,
|
|
21202
21470
|
});
|
|
21203
21471
|
if (payload) {
|
|
21204
|
-
const key = CREATE_KEY_FN$
|
|
21472
|
+
const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21205
21473
|
this.getStorage.clear(key);
|
|
21206
21474
|
}
|
|
21207
21475
|
else {
|
|
@@ -21485,6 +21753,7 @@ class ReportDummy {
|
|
|
21485
21753
|
*/
|
|
21486
21754
|
const WILDCARD_TARGET = {
|
|
21487
21755
|
backtest: true,
|
|
21756
|
+
strategy: true,
|
|
21488
21757
|
breakeven: true,
|
|
21489
21758
|
heat: true,
|
|
21490
21759
|
live: true,
|
|
@@ -21530,7 +21799,7 @@ class ReportUtils {
|
|
|
21530
21799
|
*
|
|
21531
21800
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
21532
21801
|
*/
|
|
21533
|
-
this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET) => {
|
|
21802
|
+
this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, } = WILDCARD_TARGET) => {
|
|
21534
21803
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
|
|
21535
21804
|
backtest: bt$1,
|
|
21536
21805
|
breakeven,
|
|
@@ -21541,6 +21810,7 @@ class ReportUtils {
|
|
|
21541
21810
|
risk,
|
|
21542
21811
|
schedule,
|
|
21543
21812
|
walker,
|
|
21813
|
+
strategy,
|
|
21544
21814
|
});
|
|
21545
21815
|
const unList = [];
|
|
21546
21816
|
if (bt$1) {
|
|
@@ -21570,6 +21840,9 @@ class ReportUtils {
|
|
|
21570
21840
|
if (walker) {
|
|
21571
21841
|
unList.push(bt.walkerReportService.subscribe());
|
|
21572
21842
|
}
|
|
21843
|
+
if (strategy) {
|
|
21844
|
+
unList.push(bt.scheduleReportService.subscribe());
|
|
21845
|
+
}
|
|
21573
21846
|
return functoolsKit.compose(...unList.map((un) => () => void un()));
|
|
21574
21847
|
};
|
|
21575
21848
|
/**
|
|
@@ -21608,7 +21881,7 @@ class ReportUtils {
|
|
|
21608
21881
|
* Report.disable();
|
|
21609
21882
|
* ```
|
|
21610
21883
|
*/
|
|
21611
|
-
this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET) => {
|
|
21884
|
+
this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, } = WILDCARD_TARGET) => {
|
|
21612
21885
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
|
|
21613
21886
|
backtest: bt$1,
|
|
21614
21887
|
breakeven,
|
|
@@ -21619,6 +21892,7 @@ class ReportUtils {
|
|
|
21619
21892
|
risk,
|
|
21620
21893
|
schedule,
|
|
21621
21894
|
walker,
|
|
21895
|
+
strategy,
|
|
21622
21896
|
});
|
|
21623
21897
|
if (bt$1) {
|
|
21624
21898
|
bt.backtestReportService.unsubscribe();
|
|
@@ -21647,6 +21921,9 @@ class ReportUtils {
|
|
|
21647
21921
|
if (walker) {
|
|
21648
21922
|
bt.walkerReportService.unsubscribe();
|
|
21649
21923
|
}
|
|
21924
|
+
if (strategy) {
|
|
21925
|
+
bt.strategyReportService.unsubscribe();
|
|
21926
|
+
}
|
|
21650
21927
|
};
|
|
21651
21928
|
}
|
|
21652
21929
|
}
|
|
@@ -23082,22 +23359,1243 @@ class RiskReportService {
|
|
|
23082
23359
|
}
|
|
23083
23360
|
}
|
|
23084
23361
|
|
|
23085
|
-
|
|
23086
|
-
|
|
23087
|
-
|
|
23088
|
-
|
|
23089
|
-
|
|
23090
|
-
|
|
23091
|
-
|
|
23092
|
-
{
|
|
23093
|
-
|
|
23094
|
-
|
|
23095
|
-
|
|
23096
|
-
|
|
23097
|
-
|
|
23098
|
-
|
|
23099
|
-
|
|
23100
|
-
|
|
23362
|
+
/**
|
|
23363
|
+
* Extracts execution context timestamp for strategy event logging.
|
|
23364
|
+
*
|
|
23365
|
+
* @param self - The StrategyReportService instance to extract context from
|
|
23366
|
+
* @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
|
|
23367
|
+
* @internal
|
|
23368
|
+
*/
|
|
23369
|
+
const GET_EXECUTION_CONTEXT_FN$1 = (self) => {
|
|
23370
|
+
if (ExecutionContextService.hasContext()) {
|
|
23371
|
+
const { when } = self.executionContextService.context;
|
|
23372
|
+
return { when: when.toISOString() };
|
|
23373
|
+
}
|
|
23374
|
+
return {
|
|
23375
|
+
when: "",
|
|
23376
|
+
};
|
|
23377
|
+
};
|
|
23378
|
+
/**
|
|
23379
|
+
* Service for persisting strategy management events to JSON report files.
|
|
23380
|
+
*
|
|
23381
|
+
* Handles logging of strategy actions (cancel-scheduled, close-pending, partial-profit,
|
|
23382
|
+
* partial-loss, trailing-stop, trailing-take, breakeven) to persistent storage via
|
|
23383
|
+
* the Report class. Each event is written as a separate JSON record.
|
|
23384
|
+
*
|
|
23385
|
+
* Unlike StrategyMarkdownService which accumulates events in memory for markdown reports,
|
|
23386
|
+
* this service writes each event immediately to disk for audit trail purposes.
|
|
23387
|
+
*
|
|
23388
|
+
* Lifecycle:
|
|
23389
|
+
* - Call subscribe() to enable event logging
|
|
23390
|
+
* - Events are written via Report.writeData() with "strategy" category
|
|
23391
|
+
* - Call unsubscribe() to disable event logging
|
|
23392
|
+
*
|
|
23393
|
+
* @example
|
|
23394
|
+
* ```typescript
|
|
23395
|
+
* // Service is typically used internally by strategy management classes
|
|
23396
|
+
* strategyReportService.subscribe();
|
|
23397
|
+
*
|
|
23398
|
+
* // Events are logged automatically when strategy actions occur
|
|
23399
|
+
* await strategyReportService.partialProfit("BTCUSDT", 50, 50100, false, {
|
|
23400
|
+
* strategyName: "my-strategy",
|
|
23401
|
+
* exchangeName: "binance",
|
|
23402
|
+
* frameName: "1h"
|
|
23403
|
+
* });
|
|
23404
|
+
*
|
|
23405
|
+
* strategyReportService.unsubscribe();
|
|
23406
|
+
* ```
|
|
23407
|
+
*
|
|
23408
|
+
* @see StrategyMarkdownService for in-memory event accumulation and markdown report generation
|
|
23409
|
+
* @see Report for the underlying persistence mechanism
|
|
23410
|
+
*/
|
|
23411
|
+
class StrategyReportService {
|
|
23412
|
+
constructor() {
|
|
23413
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
23414
|
+
this.executionContextService = inject(TYPES.executionContextService);
|
|
23415
|
+
this.strategyCoreService = inject(TYPES.strategyCoreService);
|
|
23416
|
+
/**
|
|
23417
|
+
* Logs a cancel-scheduled event when a scheduled signal is cancelled.
|
|
23418
|
+
*
|
|
23419
|
+
* Retrieves the scheduled signal from StrategyCoreService and writes
|
|
23420
|
+
* the cancellation event to the report file.
|
|
23421
|
+
*
|
|
23422
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23423
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23424
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23425
|
+
* @param cancelId - Optional identifier for the cancellation reason
|
|
23426
|
+
*/
|
|
23427
|
+
this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
|
|
23428
|
+
this.loggerService.log("strategyReportService cancelScheduled", {
|
|
23429
|
+
symbol,
|
|
23430
|
+
isBacktest,
|
|
23431
|
+
cancelId,
|
|
23432
|
+
});
|
|
23433
|
+
if (!this.subscribe.hasValue()) {
|
|
23434
|
+
return;
|
|
23435
|
+
}
|
|
23436
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23437
|
+
const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
|
|
23438
|
+
exchangeName: context.exchangeName,
|
|
23439
|
+
strategyName: context.strategyName,
|
|
23440
|
+
frameName: context.frameName,
|
|
23441
|
+
});
|
|
23442
|
+
if (!scheduledRow) {
|
|
23443
|
+
return;
|
|
23444
|
+
}
|
|
23445
|
+
await Report.writeData("strategy", {
|
|
23446
|
+
action: "cancel-scheduled",
|
|
23447
|
+
cancelId,
|
|
23448
|
+
symbol,
|
|
23449
|
+
createdAt,
|
|
23450
|
+
}, {
|
|
23451
|
+
signalId: scheduledRow.id,
|
|
23452
|
+
exchangeName: context.exchangeName,
|
|
23453
|
+
frameName: context.frameName,
|
|
23454
|
+
strategyName: context.strategyName,
|
|
23455
|
+
symbol,
|
|
23456
|
+
walkerName: "",
|
|
23457
|
+
});
|
|
23458
|
+
};
|
|
23459
|
+
/**
|
|
23460
|
+
* Logs a close-pending event when a pending signal is closed.
|
|
23461
|
+
*
|
|
23462
|
+
* Retrieves the pending signal from StrategyCoreService and writes
|
|
23463
|
+
* the close event to the report file.
|
|
23464
|
+
*
|
|
23465
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23466
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23467
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23468
|
+
* @param closeId - Optional identifier for the close reason
|
|
23469
|
+
*/
|
|
23470
|
+
this.closePending = async (symbol, isBacktest, context, closeId) => {
|
|
23471
|
+
this.loggerService.log("strategyReportService closePending", {
|
|
23472
|
+
symbol,
|
|
23473
|
+
isBacktest,
|
|
23474
|
+
closeId,
|
|
23475
|
+
});
|
|
23476
|
+
if (!this.subscribe.hasValue()) {
|
|
23477
|
+
return;
|
|
23478
|
+
}
|
|
23479
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23480
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23481
|
+
exchangeName: context.exchangeName,
|
|
23482
|
+
strategyName: context.strategyName,
|
|
23483
|
+
frameName: context.frameName,
|
|
23484
|
+
});
|
|
23485
|
+
if (!pendingRow) {
|
|
23486
|
+
return;
|
|
23487
|
+
}
|
|
23488
|
+
await Report.writeData("strategy", {
|
|
23489
|
+
action: "close-pending",
|
|
23490
|
+
closeId,
|
|
23491
|
+
symbol,
|
|
23492
|
+
createdAt,
|
|
23493
|
+
}, {
|
|
23494
|
+
signalId: pendingRow.id,
|
|
23495
|
+
exchangeName: context.exchangeName,
|
|
23496
|
+
frameName: context.frameName,
|
|
23497
|
+
strategyName: context.strategyName,
|
|
23498
|
+
symbol,
|
|
23499
|
+
walkerName: "",
|
|
23500
|
+
});
|
|
23501
|
+
};
|
|
23502
|
+
/**
|
|
23503
|
+
* Logs a partial-profit event when a portion of the position is closed at profit.
|
|
23504
|
+
*
|
|
23505
|
+
* Records the percentage closed and current price when partial profit-taking occurs.
|
|
23506
|
+
*
|
|
23507
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23508
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
23509
|
+
* @param currentPrice - Current market price at time of partial close
|
|
23510
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23511
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23512
|
+
*/
|
|
23513
|
+
this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
|
|
23514
|
+
this.loggerService.log("strategyReportService partialProfit", {
|
|
23515
|
+
symbol,
|
|
23516
|
+
percentToClose,
|
|
23517
|
+
currentPrice,
|
|
23518
|
+
isBacktest,
|
|
23519
|
+
});
|
|
23520
|
+
if (!this.subscribe.hasValue()) {
|
|
23521
|
+
return;
|
|
23522
|
+
}
|
|
23523
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23524
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23525
|
+
exchangeName: context.exchangeName,
|
|
23526
|
+
strategyName: context.strategyName,
|
|
23527
|
+
frameName: context.frameName,
|
|
23528
|
+
});
|
|
23529
|
+
if (!pendingRow) {
|
|
23530
|
+
return;
|
|
23531
|
+
}
|
|
23532
|
+
await Report.writeData("strategy", {
|
|
23533
|
+
action: "partial-profit",
|
|
23534
|
+
percentToClose,
|
|
23535
|
+
currentPrice,
|
|
23536
|
+
symbol,
|
|
23537
|
+
createdAt,
|
|
23538
|
+
}, {
|
|
23539
|
+
signalId: pendingRow.id,
|
|
23540
|
+
exchangeName: context.exchangeName,
|
|
23541
|
+
frameName: context.frameName,
|
|
23542
|
+
strategyName: context.strategyName,
|
|
23543
|
+
symbol,
|
|
23544
|
+
walkerName: "",
|
|
23545
|
+
});
|
|
23546
|
+
};
|
|
23547
|
+
/**
|
|
23548
|
+
* Logs a partial-loss event when a portion of the position is closed at loss.
|
|
23549
|
+
*
|
|
23550
|
+
* Records the percentage closed and current price when partial loss-cutting occurs.
|
|
23551
|
+
*
|
|
23552
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23553
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
23554
|
+
* @param currentPrice - Current market price at time of partial close
|
|
23555
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23556
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23557
|
+
*/
|
|
23558
|
+
this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
|
|
23559
|
+
this.loggerService.log("strategyReportService partialLoss", {
|
|
23560
|
+
symbol,
|
|
23561
|
+
percentToClose,
|
|
23562
|
+
currentPrice,
|
|
23563
|
+
isBacktest,
|
|
23564
|
+
});
|
|
23565
|
+
if (!this.subscribe.hasValue()) {
|
|
23566
|
+
return;
|
|
23567
|
+
}
|
|
23568
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23569
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23570
|
+
exchangeName: context.exchangeName,
|
|
23571
|
+
strategyName: context.strategyName,
|
|
23572
|
+
frameName: context.frameName,
|
|
23573
|
+
});
|
|
23574
|
+
if (!pendingRow) {
|
|
23575
|
+
return;
|
|
23576
|
+
}
|
|
23577
|
+
await Report.writeData("strategy", {
|
|
23578
|
+
action: "partial-loss",
|
|
23579
|
+
percentToClose,
|
|
23580
|
+
currentPrice,
|
|
23581
|
+
symbol,
|
|
23582
|
+
createdAt,
|
|
23583
|
+
}, {
|
|
23584
|
+
signalId: pendingRow.id,
|
|
23585
|
+
exchangeName: context.exchangeName,
|
|
23586
|
+
frameName: context.frameName,
|
|
23587
|
+
strategyName: context.strategyName,
|
|
23588
|
+
symbol,
|
|
23589
|
+
walkerName: "",
|
|
23590
|
+
});
|
|
23591
|
+
};
|
|
23592
|
+
/**
|
|
23593
|
+
* Logs a trailing-stop event when the stop-loss is adjusted.
|
|
23594
|
+
*
|
|
23595
|
+
* Records the percentage shift and current price when trailing stop moves.
|
|
23596
|
+
*
|
|
23597
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23598
|
+
* @param percentShift - Percentage the stop-loss was shifted
|
|
23599
|
+
* @param currentPrice - Current market price at time of adjustment
|
|
23600
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23601
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23602
|
+
*/
|
|
23603
|
+
this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
|
|
23604
|
+
this.loggerService.log("strategyReportService trailingStop", {
|
|
23605
|
+
symbol,
|
|
23606
|
+
percentShift,
|
|
23607
|
+
currentPrice,
|
|
23608
|
+
isBacktest,
|
|
23609
|
+
});
|
|
23610
|
+
if (!this.subscribe.hasValue()) {
|
|
23611
|
+
return;
|
|
23612
|
+
}
|
|
23613
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23614
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23615
|
+
exchangeName: context.exchangeName,
|
|
23616
|
+
strategyName: context.strategyName,
|
|
23617
|
+
frameName: context.frameName,
|
|
23618
|
+
});
|
|
23619
|
+
if (!pendingRow) {
|
|
23620
|
+
return;
|
|
23621
|
+
}
|
|
23622
|
+
await Report.writeData("strategy", {
|
|
23623
|
+
action: "trailing-stop",
|
|
23624
|
+
percentShift,
|
|
23625
|
+
currentPrice,
|
|
23626
|
+
symbol,
|
|
23627
|
+
createdAt,
|
|
23628
|
+
}, {
|
|
23629
|
+
signalId: pendingRow.id,
|
|
23630
|
+
exchangeName: context.exchangeName,
|
|
23631
|
+
frameName: context.frameName,
|
|
23632
|
+
strategyName: context.strategyName,
|
|
23633
|
+
symbol,
|
|
23634
|
+
walkerName: "",
|
|
23635
|
+
});
|
|
23636
|
+
};
|
|
23637
|
+
/**
|
|
23638
|
+
* Logs a trailing-take event when the take-profit is adjusted.
|
|
23639
|
+
*
|
|
23640
|
+
* Records the percentage shift and current price when trailing take-profit moves.
|
|
23641
|
+
*
|
|
23642
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23643
|
+
* @param percentShift - Percentage the take-profit was shifted
|
|
23644
|
+
* @param currentPrice - Current market price at time of adjustment
|
|
23645
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23646
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23647
|
+
*/
|
|
23648
|
+
this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
|
|
23649
|
+
this.loggerService.log("strategyReportService trailingTake", {
|
|
23650
|
+
symbol,
|
|
23651
|
+
percentShift,
|
|
23652
|
+
currentPrice,
|
|
23653
|
+
isBacktest,
|
|
23654
|
+
});
|
|
23655
|
+
if (!this.subscribe.hasValue()) {
|
|
23656
|
+
return;
|
|
23657
|
+
}
|
|
23658
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23659
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23660
|
+
exchangeName: context.exchangeName,
|
|
23661
|
+
strategyName: context.strategyName,
|
|
23662
|
+
frameName: context.frameName,
|
|
23663
|
+
});
|
|
23664
|
+
if (!pendingRow) {
|
|
23665
|
+
return;
|
|
23666
|
+
}
|
|
23667
|
+
await Report.writeData("strategy", {
|
|
23668
|
+
action: "trailing-take",
|
|
23669
|
+
percentShift,
|
|
23670
|
+
currentPrice,
|
|
23671
|
+
symbol,
|
|
23672
|
+
createdAt,
|
|
23673
|
+
}, {
|
|
23674
|
+
signalId: pendingRow.id,
|
|
23675
|
+
exchangeName: context.exchangeName,
|
|
23676
|
+
frameName: context.frameName,
|
|
23677
|
+
strategyName: context.strategyName,
|
|
23678
|
+
symbol,
|
|
23679
|
+
walkerName: "",
|
|
23680
|
+
});
|
|
23681
|
+
};
|
|
23682
|
+
/**
|
|
23683
|
+
* Logs a breakeven event when the stop-loss is moved to entry price.
|
|
23684
|
+
*
|
|
23685
|
+
* Records the current price when breakeven protection is activated.
|
|
23686
|
+
*
|
|
23687
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23688
|
+
* @param currentPrice - Current market price at time of breakeven activation
|
|
23689
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23690
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23691
|
+
*/
|
|
23692
|
+
this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
|
|
23693
|
+
this.loggerService.log("strategyReportService breakeven", {
|
|
23694
|
+
symbol,
|
|
23695
|
+
currentPrice,
|
|
23696
|
+
isBacktest,
|
|
23697
|
+
});
|
|
23698
|
+
if (!this.subscribe.hasValue()) {
|
|
23699
|
+
return;
|
|
23700
|
+
}
|
|
23701
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23702
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23703
|
+
exchangeName: context.exchangeName,
|
|
23704
|
+
strategyName: context.strategyName,
|
|
23705
|
+
frameName: context.frameName,
|
|
23706
|
+
});
|
|
23707
|
+
if (!pendingRow) {
|
|
23708
|
+
return;
|
|
23709
|
+
}
|
|
23710
|
+
await Report.writeData("strategy", {
|
|
23711
|
+
action: "breakeven",
|
|
23712
|
+
currentPrice,
|
|
23713
|
+
symbol,
|
|
23714
|
+
createdAt,
|
|
23715
|
+
}, {
|
|
23716
|
+
signalId: pendingRow.id,
|
|
23717
|
+
exchangeName: context.exchangeName,
|
|
23718
|
+
frameName: context.frameName,
|
|
23719
|
+
strategyName: context.strategyName,
|
|
23720
|
+
symbol,
|
|
23721
|
+
walkerName: "",
|
|
23722
|
+
});
|
|
23723
|
+
};
|
|
23724
|
+
/**
|
|
23725
|
+
* Initializes the service for event logging.
|
|
23726
|
+
*
|
|
23727
|
+
* Must be called before any events can be logged. Uses singleshot pattern
|
|
23728
|
+
* to ensure only one subscription exists at a time.
|
|
23729
|
+
*
|
|
23730
|
+
* @returns Cleanup function that clears the subscription when called
|
|
23731
|
+
*/
|
|
23732
|
+
this.subscribe = functoolsKit.singleshot(() => {
|
|
23733
|
+
this.loggerService.log("strategyReportService subscribe");
|
|
23734
|
+
const unCancelSchedule = strategyCommitSubject
|
|
23735
|
+
.filter(({ action }) => action === "cancel-scheduled")
|
|
23736
|
+
.connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
|
|
23737
|
+
exchangeName: event.exchangeName,
|
|
23738
|
+
frameName: event.frameName,
|
|
23739
|
+
strategyName: event.strategyName,
|
|
23740
|
+
}, event.cancelId));
|
|
23741
|
+
const unClosePending = strategyCommitSubject
|
|
23742
|
+
.filter(({ action }) => action === "close-pending")
|
|
23743
|
+
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
23744
|
+
exchangeName: event.exchangeName,
|
|
23745
|
+
frameName: event.frameName,
|
|
23746
|
+
strategyName: event.strategyName,
|
|
23747
|
+
}, event.closeId));
|
|
23748
|
+
const unPartialProfit = strategyCommitSubject
|
|
23749
|
+
.filter(({ action }) => action === "partial-profit")
|
|
23750
|
+
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
23751
|
+
exchangeName: event.exchangeName,
|
|
23752
|
+
frameName: event.frameName,
|
|
23753
|
+
strategyName: event.strategyName,
|
|
23754
|
+
}));
|
|
23755
|
+
const unPartialLoss = strategyCommitSubject
|
|
23756
|
+
.filter(({ action }) => action === "partial-loss")
|
|
23757
|
+
.connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
23758
|
+
exchangeName: event.exchangeName,
|
|
23759
|
+
frameName: event.frameName,
|
|
23760
|
+
strategyName: event.strategyName,
|
|
23761
|
+
}));
|
|
23762
|
+
const unTrailingStop = strategyCommitSubject
|
|
23763
|
+
.filter(({ action }) => action === "trailing-stop")
|
|
23764
|
+
.connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
23765
|
+
exchangeName: event.exchangeName,
|
|
23766
|
+
frameName: event.frameName,
|
|
23767
|
+
strategyName: event.strategyName,
|
|
23768
|
+
}));
|
|
23769
|
+
const unTrailingTake = strategyCommitSubject
|
|
23770
|
+
.filter(({ action }) => action === "trailing-take")
|
|
23771
|
+
.connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
23772
|
+
exchangeName: event.exchangeName,
|
|
23773
|
+
frameName: event.frameName,
|
|
23774
|
+
strategyName: event.strategyName,
|
|
23775
|
+
}));
|
|
23776
|
+
const unBreakeven = strategyCommitSubject
|
|
23777
|
+
.filter(({ action }) => action === "breakeven")
|
|
23778
|
+
.connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
|
|
23779
|
+
exchangeName: event.exchangeName,
|
|
23780
|
+
frameName: event.frameName,
|
|
23781
|
+
strategyName: event.strategyName,
|
|
23782
|
+
}));
|
|
23783
|
+
const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
|
|
23784
|
+
return () => {
|
|
23785
|
+
disposeFn();
|
|
23786
|
+
this.subscribe.clear();
|
|
23787
|
+
};
|
|
23788
|
+
});
|
|
23789
|
+
/**
|
|
23790
|
+
* Stops event logging and cleans up the subscription.
|
|
23791
|
+
*
|
|
23792
|
+
* Safe to call multiple times - only clears if subscription exists.
|
|
23793
|
+
*/
|
|
23794
|
+
this.unsubscribe = async () => {
|
|
23795
|
+
this.loggerService.log("strategyReportService unsubscribe");
|
|
23796
|
+
if (this.subscribe.hasValue()) {
|
|
23797
|
+
const lastSubscription = this.subscribe();
|
|
23798
|
+
lastSubscription();
|
|
23799
|
+
}
|
|
23800
|
+
};
|
|
23801
|
+
}
|
|
23802
|
+
}
|
|
23803
|
+
|
|
23804
|
+
/**
|
|
23805
|
+
* Extracts execution context timestamp for strategy event logging.
|
|
23806
|
+
*
|
|
23807
|
+
* @param self - The StrategyMarkdownService instance to extract context from
|
|
23808
|
+
* @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
|
|
23809
|
+
* @internal
|
|
23810
|
+
*/
|
|
23811
|
+
const GET_EXECUTION_CONTEXT_FN = (self) => {
|
|
23812
|
+
if (ExecutionContextService.hasContext()) {
|
|
23813
|
+
const { when } = self.executionContextService.context;
|
|
23814
|
+
return { when: when.toISOString() };
|
|
23815
|
+
}
|
|
23816
|
+
return {
|
|
23817
|
+
when: "",
|
|
23818
|
+
};
|
|
23819
|
+
};
|
|
23820
|
+
/**
|
|
23821
|
+
* Creates a unique key for memoizing ReportStorage instances.
|
|
23822
|
+
*
|
|
23823
|
+
* Key format: `{symbol}:{strategyName}:{exchangeName}[:{frameName}]:{backtest|live}`
|
|
23824
|
+
*
|
|
23825
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23826
|
+
* @param strategyName - Name of the trading strategy
|
|
23827
|
+
* @param exchangeName - Name of the exchange
|
|
23828
|
+
* @param frameName - Timeframe name (optional, included if present)
|
|
23829
|
+
* @param backtest - Whether this is backtest or live mode
|
|
23830
|
+
* @returns Colon-separated key string for memoization
|
|
23831
|
+
* @internal
|
|
23832
|
+
*/
|
|
23833
|
+
const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
23834
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
23835
|
+
if (frameName)
|
|
23836
|
+
parts.push(frameName);
|
|
23837
|
+
parts.push(backtest ? "backtest" : "live");
|
|
23838
|
+
return parts.join(":");
|
|
23839
|
+
};
|
|
23840
|
+
/**
|
|
23841
|
+
* Creates a filename for markdown report output.
|
|
23842
|
+
*
|
|
23843
|
+
* Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
|
|
23844
|
+
*
|
|
23845
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23846
|
+
* @param strategyName - Name of the trading strategy
|
|
23847
|
+
* @param exchangeName - Name of the exchange
|
|
23848
|
+
* @param frameName - Timeframe name (indicates backtest mode if present)
|
|
23849
|
+
* @param timestamp - Unix timestamp in milliseconds for uniqueness
|
|
23850
|
+
* @returns Underscore-separated filename with .md extension
|
|
23851
|
+
* @internal
|
|
23852
|
+
*/
|
|
23853
|
+
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
23854
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
23855
|
+
if (frameName) {
|
|
23856
|
+
parts.push(frameName);
|
|
23857
|
+
parts.push("backtest");
|
|
23858
|
+
}
|
|
23859
|
+
else
|
|
23860
|
+
parts.push("live");
|
|
23861
|
+
return `${parts.join("_")}-${timestamp}.md`;
|
|
23862
|
+
};
|
|
23863
|
+
/**
|
|
23864
|
+
* Maximum number of events to store per symbol-strategy pair.
|
|
23865
|
+
* Older events are discarded when this limit is exceeded.
|
|
23866
|
+
* @internal
|
|
23867
|
+
*/
|
|
23868
|
+
const MAX_EVENTS = 250;
|
|
23869
|
+
/**
|
|
23870
|
+
* In-memory storage for accumulating strategy events per symbol-strategy pair.
|
|
23871
|
+
*
|
|
23872
|
+
* Maintains a rolling window of the most recent events (up to MAX_EVENTS),
|
|
23873
|
+
* with newer events added to the front of the list. Provides methods to:
|
|
23874
|
+
* - Add new events (FIFO queue with max size)
|
|
23875
|
+
* - Retrieve aggregated statistics
|
|
23876
|
+
* - Generate markdown reports
|
|
23877
|
+
* - Dump reports to disk
|
|
23878
|
+
*
|
|
23879
|
+
* @internal
|
|
23880
|
+
*/
|
|
23881
|
+
class ReportStorage {
|
|
23882
|
+
/**
|
|
23883
|
+
* Creates a new ReportStorage instance.
|
|
23884
|
+
*
|
|
23885
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23886
|
+
* @param strategyName - Name of the trading strategy
|
|
23887
|
+
* @param exchangeName - Name of the exchange
|
|
23888
|
+
* @param frameName - Timeframe name for backtest identification
|
|
23889
|
+
*/
|
|
23890
|
+
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
23891
|
+
this.symbol = symbol;
|
|
23892
|
+
this.strategyName = strategyName;
|
|
23893
|
+
this.exchangeName = exchangeName;
|
|
23894
|
+
this.frameName = frameName;
|
|
23895
|
+
this._eventList = [];
|
|
23896
|
+
}
|
|
23897
|
+
/**
|
|
23898
|
+
* Adds a new event to the storage.
|
|
23899
|
+
*
|
|
23900
|
+
* Events are added to the front of the list (most recent first).
|
|
23901
|
+
* If the list exceeds MAX_EVENTS, the oldest event is removed.
|
|
23902
|
+
*
|
|
23903
|
+
* @param event - The strategy event to store
|
|
23904
|
+
*/
|
|
23905
|
+
addEvent(event) {
|
|
23906
|
+
this._eventList.unshift(event);
|
|
23907
|
+
if (this._eventList.length > MAX_EVENTS) {
|
|
23908
|
+
this._eventList.pop();
|
|
23909
|
+
}
|
|
23910
|
+
}
|
|
23911
|
+
/**
|
|
23912
|
+
* Retrieves aggregated statistics from stored events.
|
|
23913
|
+
*
|
|
23914
|
+
* Calculates counts for each action type from the event list.
|
|
23915
|
+
*
|
|
23916
|
+
* @returns Promise resolving to StrategyStatisticsModel with event list and counts
|
|
23917
|
+
*/
|
|
23918
|
+
async getData() {
|
|
23919
|
+
if (this._eventList.length === 0) {
|
|
23920
|
+
return {
|
|
23921
|
+
eventList: [],
|
|
23922
|
+
totalEvents: 0,
|
|
23923
|
+
cancelScheduledCount: 0,
|
|
23924
|
+
closePendingCount: 0,
|
|
23925
|
+
partialProfitCount: 0,
|
|
23926
|
+
partialLossCount: 0,
|
|
23927
|
+
trailingStopCount: 0,
|
|
23928
|
+
trailingTakeCount: 0,
|
|
23929
|
+
breakevenCount: 0,
|
|
23930
|
+
};
|
|
23931
|
+
}
|
|
23932
|
+
return {
|
|
23933
|
+
eventList: this._eventList,
|
|
23934
|
+
totalEvents: this._eventList.length,
|
|
23935
|
+
cancelScheduledCount: this._eventList.filter(e => e.action === "cancel-scheduled").length,
|
|
23936
|
+
closePendingCount: this._eventList.filter(e => e.action === "close-pending").length,
|
|
23937
|
+
partialProfitCount: this._eventList.filter(e => e.action === "partial-profit").length,
|
|
23938
|
+
partialLossCount: this._eventList.filter(e => e.action === "partial-loss").length,
|
|
23939
|
+
trailingStopCount: this._eventList.filter(e => e.action === "trailing-stop").length,
|
|
23940
|
+
trailingTakeCount: this._eventList.filter(e => e.action === "trailing-take").length,
|
|
23941
|
+
breakevenCount: this._eventList.filter(e => e.action === "breakeven").length,
|
|
23942
|
+
};
|
|
23943
|
+
}
|
|
23944
|
+
/**
|
|
23945
|
+
* Generates a markdown report from stored events.
|
|
23946
|
+
*
|
|
23947
|
+
* Creates a formatted markdown document containing:
|
|
23948
|
+
* - Header with symbol and strategy name
|
|
23949
|
+
* - Table of all events with configurable columns
|
|
23950
|
+
* - Summary statistics with counts by action type
|
|
23951
|
+
*
|
|
23952
|
+
* @param symbol - Trading pair symbol for report header
|
|
23953
|
+
* @param strategyName - Strategy name for report header
|
|
23954
|
+
* @param columns - Column configuration for the event table
|
|
23955
|
+
* @returns Promise resolving to formatted markdown string
|
|
23956
|
+
*/
|
|
23957
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.strategy_columns) {
|
|
23958
|
+
const stats = await this.getData();
|
|
23959
|
+
if (stats.totalEvents === 0) {
|
|
23960
|
+
return [
|
|
23961
|
+
`# Strategy Report: ${symbol}:${strategyName}`,
|
|
23962
|
+
"",
|
|
23963
|
+
"No strategy events recorded yet."
|
|
23964
|
+
].join("\n");
|
|
23965
|
+
}
|
|
23966
|
+
const visibleColumns = [];
|
|
23967
|
+
for (const col of columns) {
|
|
23968
|
+
if (await col.isVisible()) {
|
|
23969
|
+
visibleColumns.push(col);
|
|
23970
|
+
}
|
|
23971
|
+
}
|
|
23972
|
+
const header = visibleColumns.map((col) => col.label);
|
|
23973
|
+
const separator = visibleColumns.map(() => "---");
|
|
23974
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
23975
|
+
const tableData = [header, separator, ...rows];
|
|
23976
|
+
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
23977
|
+
return [
|
|
23978
|
+
`# Strategy Report: ${symbol}:${strategyName}`,
|
|
23979
|
+
"",
|
|
23980
|
+
table,
|
|
23981
|
+
"",
|
|
23982
|
+
`**Total events:** ${stats.totalEvents}`,
|
|
23983
|
+
`- Cancel scheduled: ${stats.cancelScheduledCount}`,
|
|
23984
|
+
`- Close pending: ${stats.closePendingCount}`,
|
|
23985
|
+
`- Partial profit: ${stats.partialProfitCount}`,
|
|
23986
|
+
`- Partial loss: ${stats.partialLossCount}`,
|
|
23987
|
+
`- Trailing stop: ${stats.trailingStopCount}`,
|
|
23988
|
+
`- Trailing take: ${stats.trailingTakeCount}`,
|
|
23989
|
+
`- Breakeven: ${stats.breakevenCount}`,
|
|
23990
|
+
].join("\n");
|
|
23991
|
+
}
|
|
23992
|
+
/**
|
|
23993
|
+
* Generates and saves a markdown report to disk.
|
|
23994
|
+
*
|
|
23995
|
+
* Creates the output directory if it doesn't exist and writes
|
|
23996
|
+
* the report with a timestamped filename.
|
|
23997
|
+
*
|
|
23998
|
+
* @param symbol - Trading pair symbol for report
|
|
23999
|
+
* @param strategyName - Strategy name for report
|
|
24000
|
+
* @param path - Output directory path (default: "./dump/strategy")
|
|
24001
|
+
* @param columns - Column configuration for the event table
|
|
24002
|
+
*/
|
|
24003
|
+
async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
|
|
24004
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
24005
|
+
const timestamp = Date.now();
|
|
24006
|
+
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
24007
|
+
await Markdown.writeData("strategy", markdown, {
|
|
24008
|
+
path,
|
|
24009
|
+
file: filename,
|
|
24010
|
+
symbol: this.symbol,
|
|
24011
|
+
strategyName: this.strategyName,
|
|
24012
|
+
exchangeName: this.exchangeName,
|
|
24013
|
+
signalId: "",
|
|
24014
|
+
frameName: this.frameName
|
|
24015
|
+
});
|
|
24016
|
+
}
|
|
24017
|
+
}
|
|
24018
|
+
/**
|
|
24019
|
+
* Service for accumulating strategy management events and generating markdown reports.
|
|
24020
|
+
*
|
|
24021
|
+
* Collects strategy actions (cancel-scheduled, close-pending, partial-profit,
|
|
24022
|
+
* partial-loss, trailing-stop, trailing-take, breakeven) in memory and provides
|
|
24023
|
+
* methods to retrieve statistics, generate reports, and export to files.
|
|
24024
|
+
*
|
|
24025
|
+
* Unlike StrategyReportService which writes each event to disk immediately,
|
|
24026
|
+
* this service accumulates events in ReportStorage instances (max 250 per
|
|
24027
|
+
* symbol-strategy pair) for batch reporting.
|
|
24028
|
+
*
|
|
24029
|
+
* Features:
|
|
24030
|
+
* - In-memory event accumulation with memoized storage per symbol-strategy pair
|
|
24031
|
+
* - Statistical data extraction (event counts by action type)
|
|
24032
|
+
* - Markdown report generation with configurable columns
|
|
24033
|
+
* - File export with timestamped filenames
|
|
24034
|
+
* - Selective or full cache clearing
|
|
24035
|
+
*
|
|
24036
|
+
* Lifecycle:
|
|
24037
|
+
* - Call subscribe() to enable event collection
|
|
24038
|
+
* - Events are collected automatically via cancelScheduled, closePending, etc.
|
|
24039
|
+
* - Use getData(), getReport(), or dump() to retrieve accumulated data
|
|
24040
|
+
* - Call unsubscribe() to disable collection and clear all data
|
|
24041
|
+
*
|
|
24042
|
+
* @example
|
|
24043
|
+
* ```typescript
|
|
24044
|
+
* strategyMarkdownService.subscribe();
|
|
24045
|
+
*
|
|
24046
|
+
* // Events are collected automatically during strategy execution
|
|
24047
|
+
* // ...
|
|
24048
|
+
*
|
|
24049
|
+
* // Get statistics
|
|
24050
|
+
* const stats = await strategyMarkdownService.getData("BTCUSDT", "my-strategy", "binance", "1h", true);
|
|
24051
|
+
*
|
|
24052
|
+
* // Generate markdown report
|
|
24053
|
+
* const report = await strategyMarkdownService.getReport("BTCUSDT", "my-strategy", "binance", "1h", true);
|
|
24054
|
+
*
|
|
24055
|
+
* // Export to file
|
|
24056
|
+
* await strategyMarkdownService.dump("BTCUSDT", "my-strategy", "binance", "1h", true);
|
|
24057
|
+
*
|
|
24058
|
+
* strategyMarkdownService.unsubscribe();
|
|
24059
|
+
* ```
|
|
24060
|
+
*
|
|
24061
|
+
* @see StrategyReportService for immediate event persistence to JSON files
|
|
24062
|
+
* @see Strategy for the high-level utility class that wraps this service
|
|
24063
|
+
*/
|
|
24064
|
+
class StrategyMarkdownService {
|
|
24065
|
+
constructor() {
|
|
24066
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
24067
|
+
this.executionContextService = inject(TYPES.executionContextService);
|
|
24068
|
+
this.strategyCoreService = inject(TYPES.strategyCoreService);
|
|
24069
|
+
/**
|
|
24070
|
+
* Memoized factory for ReportStorage instances.
|
|
24071
|
+
*
|
|
24072
|
+
* Creates and caches ReportStorage per unique symbol-strategy-exchange-frame-backtest combination.
|
|
24073
|
+
* Uses CREATE_KEY_FN for cache key generation.
|
|
24074
|
+
*
|
|
24075
|
+
* @internal
|
|
24076
|
+
*/
|
|
24077
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$2(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
|
|
24078
|
+
/**
|
|
24079
|
+
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
24080
|
+
*
|
|
24081
|
+
* Retrieves the scheduled signal from StrategyCoreService and stores
|
|
24082
|
+
* the cancellation event in the appropriate ReportStorage.
|
|
24083
|
+
*
|
|
24084
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24085
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24086
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24087
|
+
* @param cancelId - Optional identifier for the cancellation reason
|
|
24088
|
+
*/
|
|
24089
|
+
this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
|
|
24090
|
+
this.loggerService.log("strategyMarkdownService cancelScheduled", {
|
|
24091
|
+
symbol,
|
|
24092
|
+
isBacktest,
|
|
24093
|
+
cancelId,
|
|
24094
|
+
});
|
|
24095
|
+
if (!this.subscribe.hasValue()) {
|
|
24096
|
+
return;
|
|
24097
|
+
}
|
|
24098
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24099
|
+
const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
|
|
24100
|
+
exchangeName: context.exchangeName,
|
|
24101
|
+
strategyName: context.strategyName,
|
|
24102
|
+
frameName: context.frameName,
|
|
24103
|
+
});
|
|
24104
|
+
if (!scheduledRow) {
|
|
24105
|
+
return;
|
|
24106
|
+
}
|
|
24107
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24108
|
+
storage.addEvent({
|
|
24109
|
+
timestamp: Date.now(),
|
|
24110
|
+
symbol,
|
|
24111
|
+
strategyName: context.strategyName,
|
|
24112
|
+
exchangeName: context.exchangeName,
|
|
24113
|
+
frameName: context.frameName,
|
|
24114
|
+
signalId: scheduledRow.id,
|
|
24115
|
+
action: "cancel-scheduled",
|
|
24116
|
+
cancelId,
|
|
24117
|
+
createdAt,
|
|
24118
|
+
backtest: isBacktest,
|
|
24119
|
+
});
|
|
24120
|
+
};
|
|
24121
|
+
/**
|
|
24122
|
+
* Records a close-pending event when a pending signal is closed.
|
|
24123
|
+
*
|
|
24124
|
+
* Retrieves the pending signal from StrategyCoreService and stores
|
|
24125
|
+
* the close event in the appropriate ReportStorage.
|
|
24126
|
+
*
|
|
24127
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24128
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24129
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24130
|
+
* @param closeId - Optional identifier for the close reason
|
|
24131
|
+
*/
|
|
24132
|
+
this.closePending = async (symbol, isBacktest, context, closeId) => {
|
|
24133
|
+
this.loggerService.log("strategyMarkdownService closePending", {
|
|
24134
|
+
symbol,
|
|
24135
|
+
isBacktest,
|
|
24136
|
+
closeId,
|
|
24137
|
+
});
|
|
24138
|
+
if (!this.subscribe.hasValue()) {
|
|
24139
|
+
return;
|
|
24140
|
+
}
|
|
24141
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24142
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24143
|
+
exchangeName: context.exchangeName,
|
|
24144
|
+
strategyName: context.strategyName,
|
|
24145
|
+
frameName: context.frameName,
|
|
24146
|
+
});
|
|
24147
|
+
if (!pendingRow) {
|
|
24148
|
+
return;
|
|
24149
|
+
}
|
|
24150
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24151
|
+
storage.addEvent({
|
|
24152
|
+
timestamp: Date.now(),
|
|
24153
|
+
symbol,
|
|
24154
|
+
strategyName: context.strategyName,
|
|
24155
|
+
exchangeName: context.exchangeName,
|
|
24156
|
+
frameName: context.frameName,
|
|
24157
|
+
signalId: pendingRow.id,
|
|
24158
|
+
action: "close-pending",
|
|
24159
|
+
closeId,
|
|
24160
|
+
createdAt,
|
|
24161
|
+
backtest: isBacktest,
|
|
24162
|
+
});
|
|
24163
|
+
};
|
|
24164
|
+
/**
|
|
24165
|
+
* Records a partial-profit event when a portion of the position is closed at profit.
|
|
24166
|
+
*
|
|
24167
|
+
* Stores the percentage closed and current price when partial profit-taking occurs.
|
|
24168
|
+
*
|
|
24169
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24170
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
24171
|
+
* @param currentPrice - Current market price at time of partial close
|
|
24172
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24173
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24174
|
+
*/
|
|
24175
|
+
this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
|
|
24176
|
+
this.loggerService.log("strategyMarkdownService partialProfit", {
|
|
24177
|
+
symbol,
|
|
24178
|
+
percentToClose,
|
|
24179
|
+
currentPrice,
|
|
24180
|
+
isBacktest,
|
|
24181
|
+
});
|
|
24182
|
+
if (!this.subscribe.hasValue()) {
|
|
24183
|
+
return;
|
|
24184
|
+
}
|
|
24185
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24186
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24187
|
+
exchangeName: context.exchangeName,
|
|
24188
|
+
strategyName: context.strategyName,
|
|
24189
|
+
frameName: context.frameName,
|
|
24190
|
+
});
|
|
24191
|
+
if (!pendingRow) {
|
|
24192
|
+
return;
|
|
24193
|
+
}
|
|
24194
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24195
|
+
storage.addEvent({
|
|
24196
|
+
timestamp: Date.now(),
|
|
24197
|
+
symbol,
|
|
24198
|
+
strategyName: context.strategyName,
|
|
24199
|
+
exchangeName: context.exchangeName,
|
|
24200
|
+
frameName: context.frameName,
|
|
24201
|
+
signalId: pendingRow.id,
|
|
24202
|
+
action: "partial-profit",
|
|
24203
|
+
percentToClose,
|
|
24204
|
+
currentPrice,
|
|
24205
|
+
createdAt,
|
|
24206
|
+
backtest: isBacktest,
|
|
24207
|
+
});
|
|
24208
|
+
};
|
|
24209
|
+
/**
|
|
24210
|
+
* Records a partial-loss event when a portion of the position is closed at loss.
|
|
24211
|
+
*
|
|
24212
|
+
* Stores the percentage closed and current price when partial loss-cutting occurs.
|
|
24213
|
+
*
|
|
24214
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24215
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
24216
|
+
* @param currentPrice - Current market price at time of partial close
|
|
24217
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24218
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24219
|
+
*/
|
|
24220
|
+
this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
|
|
24221
|
+
this.loggerService.log("strategyMarkdownService partialLoss", {
|
|
24222
|
+
symbol,
|
|
24223
|
+
percentToClose,
|
|
24224
|
+
currentPrice,
|
|
24225
|
+
isBacktest,
|
|
24226
|
+
});
|
|
24227
|
+
if (!this.subscribe.hasValue()) {
|
|
24228
|
+
return;
|
|
24229
|
+
}
|
|
24230
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24231
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24232
|
+
exchangeName: context.exchangeName,
|
|
24233
|
+
strategyName: context.strategyName,
|
|
24234
|
+
frameName: context.frameName,
|
|
24235
|
+
});
|
|
24236
|
+
if (!pendingRow) {
|
|
24237
|
+
return;
|
|
24238
|
+
}
|
|
24239
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24240
|
+
storage.addEvent({
|
|
24241
|
+
timestamp: Date.now(),
|
|
24242
|
+
symbol,
|
|
24243
|
+
strategyName: context.strategyName,
|
|
24244
|
+
exchangeName: context.exchangeName,
|
|
24245
|
+
frameName: context.frameName,
|
|
24246
|
+
signalId: pendingRow.id,
|
|
24247
|
+
action: "partial-loss",
|
|
24248
|
+
percentToClose,
|
|
24249
|
+
currentPrice,
|
|
24250
|
+
createdAt,
|
|
24251
|
+
backtest: isBacktest,
|
|
24252
|
+
});
|
|
24253
|
+
};
|
|
24254
|
+
/**
|
|
24255
|
+
* Records a trailing-stop event when the stop-loss is adjusted.
|
|
24256
|
+
*
|
|
24257
|
+
* Stores the percentage shift and current price when trailing stop moves.
|
|
24258
|
+
*
|
|
24259
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24260
|
+
* @param percentShift - Percentage the stop-loss was shifted
|
|
24261
|
+
* @param currentPrice - Current market price at time of adjustment
|
|
24262
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24263
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24264
|
+
*/
|
|
24265
|
+
this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
|
|
24266
|
+
this.loggerService.log("strategyMarkdownService trailingStop", {
|
|
24267
|
+
symbol,
|
|
24268
|
+
percentShift,
|
|
24269
|
+
currentPrice,
|
|
24270
|
+
isBacktest,
|
|
24271
|
+
});
|
|
24272
|
+
if (!this.subscribe.hasValue()) {
|
|
24273
|
+
return;
|
|
24274
|
+
}
|
|
24275
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24276
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24277
|
+
exchangeName: context.exchangeName,
|
|
24278
|
+
strategyName: context.strategyName,
|
|
24279
|
+
frameName: context.frameName,
|
|
24280
|
+
});
|
|
24281
|
+
if (!pendingRow) {
|
|
24282
|
+
return;
|
|
24283
|
+
}
|
|
24284
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24285
|
+
storage.addEvent({
|
|
24286
|
+
timestamp: Date.now(),
|
|
24287
|
+
symbol,
|
|
24288
|
+
strategyName: context.strategyName,
|
|
24289
|
+
exchangeName: context.exchangeName,
|
|
24290
|
+
frameName: context.frameName,
|
|
24291
|
+
signalId: pendingRow.id,
|
|
24292
|
+
action: "trailing-stop",
|
|
24293
|
+
percentShift,
|
|
24294
|
+
currentPrice,
|
|
24295
|
+
createdAt,
|
|
24296
|
+
backtest: isBacktest,
|
|
24297
|
+
});
|
|
24298
|
+
};
|
|
24299
|
+
/**
|
|
24300
|
+
* Records a trailing-take event when the take-profit is adjusted.
|
|
24301
|
+
*
|
|
24302
|
+
* Stores the percentage shift and current price when trailing take-profit moves.
|
|
24303
|
+
*
|
|
24304
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24305
|
+
* @param percentShift - Percentage the take-profit was shifted
|
|
24306
|
+
* @param currentPrice - Current market price at time of adjustment
|
|
24307
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24308
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24309
|
+
*/
|
|
24310
|
+
this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
|
|
24311
|
+
this.loggerService.log("strategyMarkdownService trailingTake", {
|
|
24312
|
+
symbol,
|
|
24313
|
+
percentShift,
|
|
24314
|
+
currentPrice,
|
|
24315
|
+
isBacktest,
|
|
24316
|
+
});
|
|
24317
|
+
if (!this.subscribe.hasValue()) {
|
|
24318
|
+
return;
|
|
24319
|
+
}
|
|
24320
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24321
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24322
|
+
exchangeName: context.exchangeName,
|
|
24323
|
+
strategyName: context.strategyName,
|
|
24324
|
+
frameName: context.frameName,
|
|
24325
|
+
});
|
|
24326
|
+
if (!pendingRow) {
|
|
24327
|
+
return;
|
|
24328
|
+
}
|
|
24329
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24330
|
+
storage.addEvent({
|
|
24331
|
+
timestamp: Date.now(),
|
|
24332
|
+
symbol,
|
|
24333
|
+
strategyName: context.strategyName,
|
|
24334
|
+
exchangeName: context.exchangeName,
|
|
24335
|
+
frameName: context.frameName,
|
|
24336
|
+
signalId: pendingRow.id,
|
|
24337
|
+
action: "trailing-take",
|
|
24338
|
+
percentShift,
|
|
24339
|
+
currentPrice,
|
|
24340
|
+
createdAt,
|
|
24341
|
+
backtest: isBacktest,
|
|
24342
|
+
});
|
|
24343
|
+
};
|
|
24344
|
+
/**
|
|
24345
|
+
* Records a breakeven event when the stop-loss is moved to entry price.
|
|
24346
|
+
*
|
|
24347
|
+
* Stores the current price when breakeven protection is activated.
|
|
24348
|
+
*
|
|
24349
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24350
|
+
* @param currentPrice - Current market price at time of breakeven activation
|
|
24351
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24352
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24353
|
+
*/
|
|
24354
|
+
this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
|
|
24355
|
+
this.loggerService.log("strategyMarkdownService breakeven", {
|
|
24356
|
+
symbol,
|
|
24357
|
+
currentPrice,
|
|
24358
|
+
isBacktest,
|
|
24359
|
+
});
|
|
24360
|
+
if (!this.subscribe.hasValue()) {
|
|
24361
|
+
return;
|
|
24362
|
+
}
|
|
24363
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24364
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24365
|
+
exchangeName: context.exchangeName,
|
|
24366
|
+
strategyName: context.strategyName,
|
|
24367
|
+
frameName: context.frameName,
|
|
24368
|
+
});
|
|
24369
|
+
if (!pendingRow) {
|
|
24370
|
+
return;
|
|
24371
|
+
}
|
|
24372
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24373
|
+
storage.addEvent({
|
|
24374
|
+
timestamp: Date.now(),
|
|
24375
|
+
symbol,
|
|
24376
|
+
strategyName: context.strategyName,
|
|
24377
|
+
exchangeName: context.exchangeName,
|
|
24378
|
+
frameName: context.frameName,
|
|
24379
|
+
signalId: pendingRow.id,
|
|
24380
|
+
action: "breakeven",
|
|
24381
|
+
currentPrice,
|
|
24382
|
+
createdAt,
|
|
24383
|
+
backtest: isBacktest,
|
|
24384
|
+
});
|
|
24385
|
+
};
|
|
24386
|
+
/**
|
|
24387
|
+
* Retrieves aggregated statistics from accumulated strategy events.
|
|
24388
|
+
*
|
|
24389
|
+
* Returns counts for each action type and the full event list from the
|
|
24390
|
+
* ReportStorage for the specified symbol-strategy pair.
|
|
24391
|
+
*
|
|
24392
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24393
|
+
* @param strategyName - Name of the trading strategy
|
|
24394
|
+
* @param exchangeName - Name of the exchange
|
|
24395
|
+
* @param frameName - Timeframe name for backtest identification
|
|
24396
|
+
* @param backtest - Whether to get backtest or live data
|
|
24397
|
+
* @returns Promise resolving to StrategyStatisticsModel with event list and counts
|
|
24398
|
+
* @throws Error if service not initialized (subscribe() not called)
|
|
24399
|
+
*/
|
|
24400
|
+
this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24401
|
+
this.loggerService.log("strategyMarkdownService getData", {
|
|
24402
|
+
symbol,
|
|
24403
|
+
strategyName,
|
|
24404
|
+
exchangeName,
|
|
24405
|
+
frameName,
|
|
24406
|
+
backtest,
|
|
24407
|
+
});
|
|
24408
|
+
if (!this.subscribe.hasValue()) {
|
|
24409
|
+
throw new Error("StrategyMarkdownService not initialized. Call subscribe() before getting data.");
|
|
24410
|
+
}
|
|
24411
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
24412
|
+
return storage.getData();
|
|
24413
|
+
};
|
|
24414
|
+
/**
|
|
24415
|
+
* Generates a markdown report from accumulated strategy events.
|
|
24416
|
+
*
|
|
24417
|
+
* Creates a formatted markdown document containing:
|
|
24418
|
+
* - Header with symbol and strategy name
|
|
24419
|
+
* - Table of all events with configurable columns
|
|
24420
|
+
* - Summary statistics with counts by action type
|
|
24421
|
+
*
|
|
24422
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24423
|
+
* @param strategyName - Name of the trading strategy
|
|
24424
|
+
* @param exchangeName - Name of the exchange
|
|
24425
|
+
* @param frameName - Timeframe name for backtest identification
|
|
24426
|
+
* @param backtest - Whether to get backtest or live data
|
|
24427
|
+
* @param columns - Column configuration for the event table
|
|
24428
|
+
* @returns Promise resolving to formatted markdown string
|
|
24429
|
+
* @throws Error if service not initialized (subscribe() not called)
|
|
24430
|
+
*/
|
|
24431
|
+
this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.strategy_columns) => {
|
|
24432
|
+
this.loggerService.log("strategyMarkdownService getReport", {
|
|
24433
|
+
symbol,
|
|
24434
|
+
strategyName,
|
|
24435
|
+
exchangeName,
|
|
24436
|
+
frameName,
|
|
24437
|
+
backtest,
|
|
24438
|
+
});
|
|
24439
|
+
if (!this.subscribe.hasValue()) {
|
|
24440
|
+
throw new Error("StrategyMarkdownService not initialized. Call subscribe() before generating reports.");
|
|
24441
|
+
}
|
|
24442
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
24443
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
24444
|
+
};
|
|
24445
|
+
/**
|
|
24446
|
+
* Generates and saves a markdown report to disk.
|
|
24447
|
+
*
|
|
24448
|
+
* Creates the output directory if it doesn't exist and writes
|
|
24449
|
+
* the report with a timestamped filename via Markdown.writeData().
|
|
24450
|
+
*
|
|
24451
|
+
* Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
|
|
24452
|
+
*
|
|
24453
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24454
|
+
* @param strategyName - Name of the trading strategy
|
|
24455
|
+
* @param exchangeName - Name of the exchange
|
|
24456
|
+
* @param frameName - Timeframe name for backtest identification
|
|
24457
|
+
* @param backtest - Whether to dump backtest or live data
|
|
24458
|
+
* @param path - Output directory path (default: "./dump/strategy")
|
|
24459
|
+
* @param columns - Column configuration for the event table
|
|
24460
|
+
* @returns Promise that resolves when file is written
|
|
24461
|
+
* @throws Error if service not initialized (subscribe() not called)
|
|
24462
|
+
*/
|
|
24463
|
+
this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) => {
|
|
24464
|
+
this.loggerService.log("strategyMarkdownService dump", {
|
|
24465
|
+
symbol,
|
|
24466
|
+
strategyName,
|
|
24467
|
+
exchangeName,
|
|
24468
|
+
frameName,
|
|
24469
|
+
backtest,
|
|
24470
|
+
path,
|
|
24471
|
+
});
|
|
24472
|
+
if (!this.subscribe.hasValue()) {
|
|
24473
|
+
throw new Error("StrategyMarkdownService not initialized. Call subscribe() before dumping reports.");
|
|
24474
|
+
}
|
|
24475
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
24476
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
24477
|
+
};
|
|
24478
|
+
/**
|
|
24479
|
+
* Clears accumulated events from storage.
|
|
24480
|
+
*
|
|
24481
|
+
* Can clear either a specific symbol-strategy pair or all stored data.
|
|
24482
|
+
*
|
|
24483
|
+
* @param payload - Optional filter to clear specific storage. If omitted, clears all.
|
|
24484
|
+
* @param payload.symbol - Trading pair symbol
|
|
24485
|
+
* @param payload.strategyName - Strategy name
|
|
24486
|
+
* @param payload.exchangeName - Exchange name
|
|
24487
|
+
* @param payload.frameName - Frame name
|
|
24488
|
+
* @param payload.backtest - Backtest mode flag
|
|
24489
|
+
*/
|
|
24490
|
+
this.clear = async (payload) => {
|
|
24491
|
+
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
24492
|
+
if (payload) {
|
|
24493
|
+
const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
24494
|
+
this.getStorage.clear(key);
|
|
24495
|
+
}
|
|
24496
|
+
else {
|
|
24497
|
+
this.getStorage.clear();
|
|
24498
|
+
}
|
|
24499
|
+
};
|
|
24500
|
+
/**
|
|
24501
|
+
* Initializes the service for event collection.
|
|
24502
|
+
*
|
|
24503
|
+
* Must be called before any events can be collected or reports generated.
|
|
24504
|
+
* Uses singleshot pattern to ensure only one subscription exists at a time.
|
|
24505
|
+
*
|
|
24506
|
+
* @returns Cleanup function that clears the subscription and all accumulated data
|
|
24507
|
+
*/
|
|
24508
|
+
this.subscribe = functoolsKit.singleshot(() => {
|
|
24509
|
+
this.loggerService.log("strategyMarkdownService subscribe");
|
|
24510
|
+
const unCancelSchedule = strategyCommitSubject
|
|
24511
|
+
.filter(({ action }) => action === "cancel-scheduled")
|
|
24512
|
+
.connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
|
|
24513
|
+
exchangeName: event.exchangeName,
|
|
24514
|
+
frameName: event.frameName,
|
|
24515
|
+
strategyName: event.strategyName,
|
|
24516
|
+
}, event.cancelId));
|
|
24517
|
+
const unClosePending = strategyCommitSubject
|
|
24518
|
+
.filter(({ action }) => action === "close-pending")
|
|
24519
|
+
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
24520
|
+
exchangeName: event.exchangeName,
|
|
24521
|
+
frameName: event.frameName,
|
|
24522
|
+
strategyName: event.strategyName,
|
|
24523
|
+
}, event.closeId));
|
|
24524
|
+
const unPartialProfit = strategyCommitSubject
|
|
24525
|
+
.filter(({ action }) => action === "partial-profit")
|
|
24526
|
+
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
24527
|
+
exchangeName: event.exchangeName,
|
|
24528
|
+
frameName: event.frameName,
|
|
24529
|
+
strategyName: event.strategyName,
|
|
24530
|
+
}));
|
|
24531
|
+
const unPartialLoss = strategyCommitSubject
|
|
24532
|
+
.filter(({ action }) => action === "partial-loss")
|
|
24533
|
+
.connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
24534
|
+
exchangeName: event.exchangeName,
|
|
24535
|
+
frameName: event.frameName,
|
|
24536
|
+
strategyName: event.strategyName,
|
|
24537
|
+
}));
|
|
24538
|
+
const unTrailingStop = strategyCommitSubject
|
|
24539
|
+
.filter(({ action }) => action === "trailing-stop")
|
|
24540
|
+
.connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
24541
|
+
exchangeName: event.exchangeName,
|
|
24542
|
+
frameName: event.frameName,
|
|
24543
|
+
strategyName: event.strategyName,
|
|
24544
|
+
}));
|
|
24545
|
+
const unTrailingTake = strategyCommitSubject
|
|
24546
|
+
.filter(({ action }) => action === "trailing-take")
|
|
24547
|
+
.connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
24548
|
+
exchangeName: event.exchangeName,
|
|
24549
|
+
frameName: event.frameName,
|
|
24550
|
+
strategyName: event.strategyName,
|
|
24551
|
+
}));
|
|
24552
|
+
const unBreakeven = strategyCommitSubject
|
|
24553
|
+
.filter(({ action }) => action === "breakeven")
|
|
24554
|
+
.connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
|
|
24555
|
+
exchangeName: event.exchangeName,
|
|
24556
|
+
frameName: event.frameName,
|
|
24557
|
+
strategyName: event.strategyName,
|
|
24558
|
+
}));
|
|
24559
|
+
const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
|
|
24560
|
+
return () => {
|
|
24561
|
+
disposeFn();
|
|
24562
|
+
this.subscribe.clear();
|
|
24563
|
+
this.clear();
|
|
24564
|
+
};
|
|
24565
|
+
});
|
|
24566
|
+
/**
|
|
24567
|
+
* Stops event collection and clears all accumulated data.
|
|
24568
|
+
*
|
|
24569
|
+
* Invokes the cleanup function returned by subscribe(), which clears
|
|
24570
|
+
* both the subscription and all ReportStorage instances.
|
|
24571
|
+
* Safe to call multiple times - only acts if subscription exists.
|
|
24572
|
+
*/
|
|
24573
|
+
this.unsubscribe = async () => {
|
|
24574
|
+
this.loggerService.log("strategyMarkdownService unsubscribe");
|
|
24575
|
+
if (this.subscribe.hasValue()) {
|
|
24576
|
+
const lastSubscription = this.subscribe();
|
|
24577
|
+
lastSubscription();
|
|
24578
|
+
}
|
|
24579
|
+
};
|
|
24580
|
+
}
|
|
24581
|
+
}
|
|
24582
|
+
|
|
24583
|
+
{
|
|
24584
|
+
provide(TYPES.loggerService, () => new LoggerService());
|
|
24585
|
+
}
|
|
24586
|
+
{
|
|
24587
|
+
provide(TYPES.executionContextService, () => new ExecutionContextService());
|
|
24588
|
+
provide(TYPES.methodContextService, () => new MethodContextService());
|
|
24589
|
+
}
|
|
24590
|
+
{
|
|
24591
|
+
provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
|
|
24592
|
+
provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
|
|
24593
|
+
provide(TYPES.frameConnectionService, () => new FrameConnectionService());
|
|
24594
|
+
provide(TYPES.sizingConnectionService, () => new SizingConnectionService());
|
|
24595
|
+
provide(TYPES.riskConnectionService, () => new RiskConnectionService());
|
|
24596
|
+
provide(TYPES.actionConnectionService, () => new ActionConnectionService());
|
|
24597
|
+
provide(TYPES.partialConnectionService, () => new PartialConnectionService());
|
|
24598
|
+
provide(TYPES.breakevenConnectionService, () => new BreakevenConnectionService());
|
|
23101
24599
|
}
|
|
23102
24600
|
{
|
|
23103
24601
|
provide(TYPES.exchangeSchemaService, () => new ExchangeSchemaService());
|
|
@@ -23145,6 +24643,7 @@ class RiskReportService {
|
|
|
23145
24643
|
provide(TYPES.partialMarkdownService, () => new PartialMarkdownService());
|
|
23146
24644
|
provide(TYPES.breakevenMarkdownService, () => new BreakevenMarkdownService());
|
|
23147
24645
|
provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
|
|
24646
|
+
provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
|
|
23148
24647
|
}
|
|
23149
24648
|
{
|
|
23150
24649
|
provide(TYPES.backtestReportService, () => new BacktestReportService());
|
|
@@ -23156,6 +24655,7 @@ class RiskReportService {
|
|
|
23156
24655
|
provide(TYPES.partialReportService, () => new PartialReportService());
|
|
23157
24656
|
provide(TYPES.breakevenReportService, () => new BreakevenReportService());
|
|
23158
24657
|
provide(TYPES.riskReportService, () => new RiskReportService());
|
|
24658
|
+
provide(TYPES.strategyReportService, () => new StrategyReportService());
|
|
23159
24659
|
}
|
|
23160
24660
|
{
|
|
23161
24661
|
provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
|
|
@@ -23232,6 +24732,7 @@ const markdownServices = {
|
|
|
23232
24732
|
partialMarkdownService: inject(TYPES.partialMarkdownService),
|
|
23233
24733
|
breakevenMarkdownService: inject(TYPES.breakevenMarkdownService),
|
|
23234
24734
|
riskMarkdownService: inject(TYPES.riskMarkdownService),
|
|
24735
|
+
strategyMarkdownService: inject(TYPES.strategyMarkdownService),
|
|
23235
24736
|
};
|
|
23236
24737
|
const reportServices = {
|
|
23237
24738
|
backtestReportService: inject(TYPES.backtestReportService),
|
|
@@ -23243,6 +24744,7 @@ const reportServices = {
|
|
|
23243
24744
|
partialReportService: inject(TYPES.partialReportService),
|
|
23244
24745
|
breakevenReportService: inject(TYPES.breakevenReportService),
|
|
23245
24746
|
riskReportService: inject(TYPES.riskReportService),
|
|
24747
|
+
strategyReportService: inject(TYPES.strategyReportService),
|
|
23246
24748
|
};
|
|
23247
24749
|
const validationServices = {
|
|
23248
24750
|
exchangeValidationService: inject(TYPES.exchangeValidationService),
|
|
@@ -25321,6 +26823,8 @@ const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
|
|
|
25321
26823
|
const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
|
|
25322
26824
|
const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
|
|
25323
26825
|
const LISTEN_ACTIVE_PING_ONCE_METHOD_NAME = "event.listenActivePingOnce";
|
|
26826
|
+
const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
|
|
26827
|
+
const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
|
|
25324
26828
|
/**
|
|
25325
26829
|
* Subscribes to all signal events with queued async processing.
|
|
25326
26830
|
*
|
|
@@ -26350,6 +27854,78 @@ function listenActivePingOnce(filterFn, fn) {
|
|
|
26350
27854
|
bt.loggerService.log(LISTEN_ACTIVE_PING_ONCE_METHOD_NAME);
|
|
26351
27855
|
return activePingSubject.filter(filterFn).once(fn);
|
|
26352
27856
|
}
|
|
27857
|
+
/**
|
|
27858
|
+
* Subscribes to strategy management events with queued async processing.
|
|
27859
|
+
*
|
|
27860
|
+
* Emits when strategy management actions are executed:
|
|
27861
|
+
* - cancel-scheduled: Scheduled signal cancelled
|
|
27862
|
+
* - close-pending: Pending signal closed
|
|
27863
|
+
* - partial-profit: Partial close at profit level
|
|
27864
|
+
* - partial-loss: Partial close at loss level
|
|
27865
|
+
* - trailing-stop: Stop-loss adjusted
|
|
27866
|
+
* - trailing-take: Take-profit adjusted
|
|
27867
|
+
* - breakeven: Stop-loss moved to entry price
|
|
27868
|
+
*
|
|
27869
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
27870
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
27871
|
+
*
|
|
27872
|
+
* @param fn - Callback function to handle strategy commit events
|
|
27873
|
+
* @returns Unsubscribe function to stop listening
|
|
27874
|
+
*
|
|
27875
|
+
* @example
|
|
27876
|
+
* ```typescript
|
|
27877
|
+
* import { listenStrategyCommit } from "./function/event";
|
|
27878
|
+
*
|
|
27879
|
+
* const unsubscribe = listenStrategyCommit((event) => {
|
|
27880
|
+
* console.log(`[${event.action}] ${event.symbol}`);
|
|
27881
|
+
* console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
|
|
27882
|
+
* if (event.action === "partial-profit") {
|
|
27883
|
+
* console.log(`Closed ${event.percentToClose}% at ${event.currentPrice}`);
|
|
27884
|
+
* }
|
|
27885
|
+
* });
|
|
27886
|
+
*
|
|
27887
|
+
* // Later: stop listening
|
|
27888
|
+
* unsubscribe();
|
|
27889
|
+
* ```
|
|
27890
|
+
*/
|
|
27891
|
+
function listenStrategyCommit(fn) {
|
|
27892
|
+
bt.loggerService.log(LISTEN_STRATEGY_COMMIT_METHOD_NAME);
|
|
27893
|
+
return strategyCommitSubject.subscribe(functoolsKit.queued(async (event) => fn(event)));
|
|
27894
|
+
}
|
|
27895
|
+
/**
|
|
27896
|
+
* Subscribes to filtered strategy management events with one-time execution.
|
|
27897
|
+
*
|
|
27898
|
+
* Listens for events matching the filter predicate, then executes callback once
|
|
27899
|
+
* and automatically unsubscribes. Useful for waiting for specific strategy actions.
|
|
27900
|
+
*
|
|
27901
|
+
* @param filterFn - Predicate to filter which events trigger the callback
|
|
27902
|
+
* @param fn - Callback function to handle the filtered event (called only once)
|
|
27903
|
+
* @returns Unsubscribe function to cancel the listener before it fires
|
|
27904
|
+
*
|
|
27905
|
+
* @example
|
|
27906
|
+
* ```typescript
|
|
27907
|
+
* import { listenStrategyCommitOnce } from "./function/event";
|
|
27908
|
+
*
|
|
27909
|
+
* // Wait for first trailing stop adjustment
|
|
27910
|
+
* listenStrategyCommitOnce(
|
|
27911
|
+
* (event) => event.action === "trailing-stop",
|
|
27912
|
+
* (event) => console.log("Trailing stop adjusted:", event.symbol)
|
|
27913
|
+
* );
|
|
27914
|
+
*
|
|
27915
|
+
* // Wait for breakeven on BTCUSDT
|
|
27916
|
+
* const cancel = listenStrategyCommitOnce(
|
|
27917
|
+
* (event) => event.action === "breakeven" && event.symbol === "BTCUSDT",
|
|
27918
|
+
* (event) => console.log("BTCUSDT moved to breakeven at", event.currentPrice)
|
|
27919
|
+
* );
|
|
27920
|
+
*
|
|
27921
|
+
* // Cancel if needed before event fires
|
|
27922
|
+
* cancel();
|
|
27923
|
+
* ```
|
|
27924
|
+
*/
|
|
27925
|
+
function listenStrategyCommitOnce(filterFn, fn) {
|
|
27926
|
+
bt.loggerService.log(LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME);
|
|
27927
|
+
return strategyCommitSubject.filter(filterFn).once(fn);
|
|
27928
|
+
}
|
|
26353
27929
|
|
|
26354
27930
|
const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
|
|
26355
27931
|
const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
|
|
@@ -30795,6 +32371,7 @@ class NotificationInstance {
|
|
|
30795
32371
|
priceTakeProfit: data.signal.priceTakeProfit,
|
|
30796
32372
|
priceStopLoss: data.signal.priceStopLoss,
|
|
30797
32373
|
note: data.signal.note,
|
|
32374
|
+
createdAt: data.createdAt,
|
|
30798
32375
|
});
|
|
30799
32376
|
}
|
|
30800
32377
|
else if (data.action === "closed") {
|
|
@@ -30816,6 +32393,7 @@ class NotificationInstance {
|
|
|
30816
32393
|
closeReason: data.closeReason,
|
|
30817
32394
|
duration: durationMin,
|
|
30818
32395
|
note: data.signal.note,
|
|
32396
|
+
createdAt: data.createdAt,
|
|
30819
32397
|
});
|
|
30820
32398
|
}
|
|
30821
32399
|
else if (data.action === "scheduled") {
|
|
@@ -30832,6 +32410,7 @@ class NotificationInstance {
|
|
|
30832
32410
|
priceOpen: data.signal.priceOpen,
|
|
30833
32411
|
scheduledAt: data.signal.scheduledAt,
|
|
30834
32412
|
currentPrice: data.currentPrice,
|
|
32413
|
+
createdAt: data.createdAt,
|
|
30835
32414
|
});
|
|
30836
32415
|
}
|
|
30837
32416
|
else if (data.action === "cancelled") {
|
|
@@ -30850,6 +32429,7 @@ class NotificationInstance {
|
|
|
30850
32429
|
cancelReason: data.reason,
|
|
30851
32430
|
cancelId: data.cancelId,
|
|
30852
32431
|
duration: durationMin,
|
|
32432
|
+
createdAt: data.createdAt,
|
|
30853
32433
|
});
|
|
30854
32434
|
}
|
|
30855
32435
|
};
|
|
@@ -30858,7 +32438,7 @@ class NotificationInstance {
|
|
|
30858
32438
|
*/
|
|
30859
32439
|
this._handlePartialProfit = async (data) => {
|
|
30860
32440
|
this._addNotification({
|
|
30861
|
-
type: "
|
|
32441
|
+
type: "partial_profit.available",
|
|
30862
32442
|
id: CREATE_KEY_FN(),
|
|
30863
32443
|
timestamp: data.timestamp,
|
|
30864
32444
|
backtest: data.backtest,
|
|
@@ -30877,7 +32457,7 @@ class NotificationInstance {
|
|
|
30877
32457
|
*/
|
|
30878
32458
|
this._handlePartialLoss = async (data) => {
|
|
30879
32459
|
this._addNotification({
|
|
30880
|
-
type: "
|
|
32460
|
+
type: "partial_loss.available",
|
|
30881
32461
|
id: CREATE_KEY_FN(),
|
|
30882
32462
|
timestamp: data.timestamp,
|
|
30883
32463
|
backtest: data.backtest,
|
|
@@ -30892,50 +32472,109 @@ class NotificationInstance {
|
|
|
30892
32472
|
});
|
|
30893
32473
|
};
|
|
30894
32474
|
/**
|
|
30895
|
-
* Processes
|
|
32475
|
+
* Processes breakeven events.
|
|
30896
32476
|
*/
|
|
30897
|
-
this.
|
|
32477
|
+
this._handleBreakeven = async (data) => {
|
|
30898
32478
|
this._addNotification({
|
|
30899
|
-
type: "
|
|
32479
|
+
type: "breakeven.available",
|
|
30900
32480
|
id: CREATE_KEY_FN(),
|
|
30901
32481
|
timestamp: data.timestamp,
|
|
30902
32482
|
backtest: data.backtest,
|
|
30903
32483
|
symbol: data.symbol,
|
|
30904
32484
|
strategyName: data.strategyName,
|
|
30905
32485
|
exchangeName: data.exchangeName,
|
|
30906
|
-
|
|
30907
|
-
rejectionId: data.rejectionId,
|
|
30908
|
-
activePositionCount: data.activePositionCount,
|
|
32486
|
+
signalId: data.data.id,
|
|
30909
32487
|
currentPrice: data.currentPrice,
|
|
30910
|
-
|
|
32488
|
+
priceOpen: data.data.priceOpen,
|
|
32489
|
+
position: data.data.position,
|
|
30911
32490
|
});
|
|
30912
32491
|
};
|
|
30913
32492
|
/**
|
|
30914
|
-
* Processes
|
|
32493
|
+
* Processes strategy commit events.
|
|
30915
32494
|
*/
|
|
30916
|
-
this.
|
|
30917
|
-
|
|
30918
|
-
|
|
30919
|
-
|
|
30920
|
-
|
|
30921
|
-
|
|
30922
|
-
|
|
30923
|
-
|
|
30924
|
-
|
|
30925
|
-
|
|
32495
|
+
this._handleStrategyCommit = async (data) => {
|
|
32496
|
+
if (data.action === "partial-profit") {
|
|
32497
|
+
this._addNotification({
|
|
32498
|
+
type: "partial_profit.commit",
|
|
32499
|
+
id: CREATE_KEY_FN(),
|
|
32500
|
+
timestamp: Date.now(),
|
|
32501
|
+
backtest: data.backtest,
|
|
32502
|
+
symbol: data.symbol,
|
|
32503
|
+
strategyName: data.strategyName,
|
|
32504
|
+
exchangeName: data.exchangeName,
|
|
32505
|
+
percentToClose: data.percentToClose,
|
|
32506
|
+
currentPrice: data.currentPrice,
|
|
32507
|
+
});
|
|
32508
|
+
}
|
|
32509
|
+
else if (data.action === "partial-loss") {
|
|
32510
|
+
this._addNotification({
|
|
32511
|
+
type: "partial_loss.commit",
|
|
32512
|
+
id: CREATE_KEY_FN(),
|
|
32513
|
+
timestamp: Date.now(),
|
|
32514
|
+
backtest: data.backtest,
|
|
32515
|
+
symbol: data.symbol,
|
|
32516
|
+
strategyName: data.strategyName,
|
|
32517
|
+
exchangeName: data.exchangeName,
|
|
32518
|
+
percentToClose: data.percentToClose,
|
|
32519
|
+
currentPrice: data.currentPrice,
|
|
32520
|
+
});
|
|
32521
|
+
}
|
|
32522
|
+
else if (data.action === "breakeven") {
|
|
32523
|
+
this._addNotification({
|
|
32524
|
+
type: "breakeven.commit",
|
|
32525
|
+
id: CREATE_KEY_FN(),
|
|
32526
|
+
timestamp: Date.now(),
|
|
32527
|
+
backtest: data.backtest,
|
|
32528
|
+
symbol: data.symbol,
|
|
32529
|
+
strategyName: data.strategyName,
|
|
32530
|
+
exchangeName: data.exchangeName,
|
|
32531
|
+
currentPrice: data.currentPrice,
|
|
32532
|
+
});
|
|
32533
|
+
}
|
|
32534
|
+
else if (data.action === "trailing-stop") {
|
|
32535
|
+
this._addNotification({
|
|
32536
|
+
type: "trailing_stop.commit",
|
|
32537
|
+
id: CREATE_KEY_FN(),
|
|
32538
|
+
timestamp: Date.now(),
|
|
32539
|
+
backtest: data.backtest,
|
|
32540
|
+
symbol: data.symbol,
|
|
32541
|
+
strategyName: data.strategyName,
|
|
32542
|
+
exchangeName: data.exchangeName,
|
|
32543
|
+
percentShift: data.percentShift,
|
|
32544
|
+
currentPrice: data.currentPrice,
|
|
32545
|
+
});
|
|
32546
|
+
}
|
|
32547
|
+
else if (data.action === "trailing-take") {
|
|
32548
|
+
this._addNotification({
|
|
32549
|
+
type: "trailing_take.commit",
|
|
32550
|
+
id: CREATE_KEY_FN(),
|
|
32551
|
+
timestamp: Date.now(),
|
|
32552
|
+
backtest: data.backtest,
|
|
32553
|
+
symbol: data.symbol,
|
|
32554
|
+
strategyName: data.strategyName,
|
|
32555
|
+
exchangeName: data.exchangeName,
|
|
32556
|
+
percentShift: data.percentShift,
|
|
32557
|
+
currentPrice: data.currentPrice,
|
|
32558
|
+
});
|
|
32559
|
+
}
|
|
30926
32560
|
};
|
|
30927
32561
|
/**
|
|
30928
|
-
* Processes
|
|
32562
|
+
* Processes risk rejection events.
|
|
30929
32563
|
*/
|
|
30930
|
-
this.
|
|
32564
|
+
this._handleRisk = async (data) => {
|
|
30931
32565
|
this._addNotification({
|
|
30932
|
-
type: "
|
|
32566
|
+
type: "risk.rejection",
|
|
30933
32567
|
id: CREATE_KEY_FN(),
|
|
30934
|
-
timestamp:
|
|
30935
|
-
backtest:
|
|
32568
|
+
timestamp: data.timestamp,
|
|
32569
|
+
backtest: data.backtest,
|
|
30936
32570
|
symbol: data.symbol,
|
|
30937
32571
|
strategyName: data.strategyName,
|
|
30938
32572
|
exchangeName: data.exchangeName,
|
|
32573
|
+
rejectionNote: data.rejectionNote,
|
|
32574
|
+
rejectionId: data.rejectionId,
|
|
32575
|
+
activePositionCount: data.activePositionCount,
|
|
32576
|
+
currentPrice: data.currentPrice,
|
|
32577
|
+
pendingSignal: data.pendingSignal,
|
|
30939
32578
|
});
|
|
30940
32579
|
};
|
|
30941
32580
|
/**
|
|
@@ -30977,23 +32616,6 @@ class NotificationInstance {
|
|
|
30977
32616
|
backtest: false,
|
|
30978
32617
|
});
|
|
30979
32618
|
};
|
|
30980
|
-
/**
|
|
30981
|
-
* Processes progress events.
|
|
30982
|
-
*/
|
|
30983
|
-
this._handleProgressBacktest = async (data) => {
|
|
30984
|
-
this._addNotification({
|
|
30985
|
-
type: "progress.backtest",
|
|
30986
|
-
id: CREATE_KEY_FN(),
|
|
30987
|
-
timestamp: Date.now(),
|
|
30988
|
-
backtest: true,
|
|
30989
|
-
exchangeName: data.exchangeName,
|
|
30990
|
-
strategyName: data.strategyName,
|
|
30991
|
-
symbol: data.symbol,
|
|
30992
|
-
totalFrames: data.totalFrames,
|
|
30993
|
-
processedFrames: data.processedFrames,
|
|
30994
|
-
progress: data.progress,
|
|
30995
|
-
});
|
|
30996
|
-
};
|
|
30997
32619
|
/**
|
|
30998
32620
|
* Subscribes to all notification emitters and returns an unsubscribe function.
|
|
30999
32621
|
* Protected against multiple subscriptions using singleshot.
|
|
@@ -31012,14 +32634,13 @@ class NotificationInstance {
|
|
|
31012
32634
|
const unSignal = signalEmitter.subscribe(this._handleSignal);
|
|
31013
32635
|
const unProfit = partialProfitSubject.subscribe(this._handlePartialProfit);
|
|
31014
32636
|
const unLoss = partialLossSubject.subscribe(this._handlePartialLoss);
|
|
32637
|
+
const unBreakeven = breakevenSubject.subscribe(this._handleBreakeven);
|
|
32638
|
+
const unStrategyCommit = strategyCommitSubject.subscribe(this._handleStrategyCommit);
|
|
31015
32639
|
const unRisk = riskSubject.subscribe(this._handleRisk);
|
|
31016
|
-
const unDoneLine = doneLiveSubject.subscribe(this._handleDoneLive);
|
|
31017
|
-
const unDoneBacktest = doneBacktestSubject.subscribe(this._handleDoneBacktest);
|
|
31018
32640
|
const unError = errorEmitter.subscribe(this._handleError);
|
|
31019
32641
|
const unExit = exitEmitter.subscribe(this._handleCriticalError);
|
|
31020
32642
|
const unValidation = validationSubject.subscribe(this._handleValidationError);
|
|
31021
|
-
const
|
|
31022
|
-
const disposeFn = functoolsKit.compose(() => unSignal(), () => unProfit(), () => unLoss(), () => unRisk(), () => unDoneLine(), () => unDoneBacktest(), () => unError(), () => unExit(), () => unValidation(), () => unProgressBacktest());
|
|
32643
|
+
const disposeFn = functoolsKit.compose(() => unSignal(), () => unProfit(), () => unLoss(), () => unBreakeven(), () => unStrategyCommit(), () => unRisk(), () => unError(), () => unExit(), () => unValidation());
|
|
31023
32644
|
return () => {
|
|
31024
32645
|
disposeFn();
|
|
31025
32646
|
this.enable.clear();
|
|
@@ -31409,6 +33030,201 @@ class BreakevenUtils {
|
|
|
31409
33030
|
*/
|
|
31410
33031
|
const Breakeven = new BreakevenUtils();
|
|
31411
33032
|
|
|
33033
|
+
const STRATEGY_METHOD_NAME_GET_DATA = "StrategyUtils.getData";
|
|
33034
|
+
const STRATEGY_METHOD_NAME_GET_REPORT = "StrategyUtils.getReport";
|
|
33035
|
+
const STRATEGY_METHOD_NAME_DUMP = "StrategyUtils.dump";
|
|
33036
|
+
/**
|
|
33037
|
+
* Utility class for accessing strategy management reports and statistics.
|
|
33038
|
+
*
|
|
33039
|
+
* Provides static-like methods (via singleton instance) to retrieve data
|
|
33040
|
+
* accumulated by StrategyMarkdownService from strategy management events.
|
|
33041
|
+
*
|
|
33042
|
+
* Features:
|
|
33043
|
+
* - Statistical data extraction (event counts by action type)
|
|
33044
|
+
* - Markdown report generation with event tables
|
|
33045
|
+
* - File export to disk
|
|
33046
|
+
*
|
|
33047
|
+
* Data source:
|
|
33048
|
+
* - StrategyMarkdownService receives events via direct method calls
|
|
33049
|
+
* - Accumulates events in ReportStorage (max 250 events per symbol-strategy pair)
|
|
33050
|
+
* - Events include: cancel-scheduled, close-pending, partial-profit, partial-loss,
|
|
33051
|
+
* trailing-stop, trailing-take, breakeven
|
|
33052
|
+
*
|
|
33053
|
+
* @example
|
|
33054
|
+
* ```typescript
|
|
33055
|
+
* import { Strategy } from "./classes/Strategy";
|
|
33056
|
+
*
|
|
33057
|
+
* // Get statistical data for BTCUSDT:my-strategy
|
|
33058
|
+
* const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33059
|
+
* console.log(`Total events: ${stats.totalEvents}`);
|
|
33060
|
+
* console.log(`Partial profit events: ${stats.partialProfitCount}`);
|
|
33061
|
+
*
|
|
33062
|
+
* // Generate markdown report
|
|
33063
|
+
* const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33064
|
+
* console.log(markdown); // Formatted table with all events
|
|
33065
|
+
*
|
|
33066
|
+
* // Export report to file
|
|
33067
|
+
* await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }); // Saves to ./dump/strategy/
|
|
33068
|
+
* ```
|
|
33069
|
+
*/
|
|
33070
|
+
class StrategyUtils {
|
|
33071
|
+
constructor() {
|
|
33072
|
+
/**
|
|
33073
|
+
* Retrieves statistical data from accumulated strategy events.
|
|
33074
|
+
*
|
|
33075
|
+
* Delegates to StrategyMarkdownService.getData() which reads from ReportStorage.
|
|
33076
|
+
* Returns aggregated metrics calculated from all strategy events.
|
|
33077
|
+
*
|
|
33078
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
33079
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
33080
|
+
* @param backtest - Whether to get backtest data (default: false)
|
|
33081
|
+
* @returns Promise resolving to StrategyStatisticsModel object with counts and event list
|
|
33082
|
+
*
|
|
33083
|
+
* @example
|
|
33084
|
+
* ```typescript
|
|
33085
|
+
* const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33086
|
+
*
|
|
33087
|
+
* console.log(`Total events: ${stats.totalEvents}`);
|
|
33088
|
+
* console.log(`Partial profit: ${stats.partialProfitCount}`);
|
|
33089
|
+
* console.log(`Trailing stop: ${stats.trailingStopCount}`);
|
|
33090
|
+
*
|
|
33091
|
+
* // Iterate through all events
|
|
33092
|
+
* for (const event of stats.eventList) {
|
|
33093
|
+
* console.log(`Signal ${event.signalId}: ${event.action} at ${event.currentPrice}`);
|
|
33094
|
+
* }
|
|
33095
|
+
* ```
|
|
33096
|
+
*/
|
|
33097
|
+
this.getData = async (symbol, context, backtest = false) => {
|
|
33098
|
+
bt.loggerService.info(STRATEGY_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
|
|
33099
|
+
bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_DATA);
|
|
33100
|
+
bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_DATA);
|
|
33101
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_DATA);
|
|
33102
|
+
{
|
|
33103
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33104
|
+
riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA);
|
|
33105
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA));
|
|
33106
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_DATA));
|
|
33107
|
+
}
|
|
33108
|
+
return await bt.strategyMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
33109
|
+
};
|
|
33110
|
+
/**
|
|
33111
|
+
* Generates markdown report with all strategy events for a symbol-strategy pair.
|
|
33112
|
+
*
|
|
33113
|
+
* Creates formatted table containing:
|
|
33114
|
+
* - Symbol
|
|
33115
|
+
* - Strategy
|
|
33116
|
+
* - Signal ID
|
|
33117
|
+
* - Action (cancel-scheduled, close-pending, partial-profit, etc.)
|
|
33118
|
+
* - Price
|
|
33119
|
+
* - Percent values (% To Close, % Shift)
|
|
33120
|
+
* - Cancel/Close IDs
|
|
33121
|
+
* - Timestamp (ISO 8601)
|
|
33122
|
+
* - Mode (Backtest/Live)
|
|
33123
|
+
*
|
|
33124
|
+
* Also includes summary statistics at the end with counts by action type.
|
|
33125
|
+
*
|
|
33126
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
33127
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
33128
|
+
* @param backtest - Whether to get backtest data (default: false)
|
|
33129
|
+
* @param columns - Optional columns configuration for the report
|
|
33130
|
+
* @returns Promise resolving to markdown formatted report string
|
|
33131
|
+
*
|
|
33132
|
+
* @example
|
|
33133
|
+
* ```typescript
|
|
33134
|
+
* const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33135
|
+
* console.log(markdown);
|
|
33136
|
+
*
|
|
33137
|
+
* // Output:
|
|
33138
|
+
* // # Strategy Report: BTCUSDT:my-strategy
|
|
33139
|
+
* //
|
|
33140
|
+
* // | Symbol | Strategy | Signal ID | Action | Price | ... |
|
|
33141
|
+
* // | --- | --- | --- | --- | --- | ... |
|
|
33142
|
+
* // | BTCUSDT | my-strategy | abc123 | partial-profit | 50100.00000000 USD | ... |
|
|
33143
|
+
* //
|
|
33144
|
+
* // **Total events:** 5
|
|
33145
|
+
* // - Cancel scheduled: 0
|
|
33146
|
+
* // - Close pending: 1
|
|
33147
|
+
* // - Partial profit: 2
|
|
33148
|
+
* // ...
|
|
33149
|
+
* ```
|
|
33150
|
+
*/
|
|
33151
|
+
this.getReport = async (symbol, context, backtest = false, columns) => {
|
|
33152
|
+
bt.loggerService.info(STRATEGY_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
|
|
33153
|
+
bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_REPORT);
|
|
33154
|
+
bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_REPORT);
|
|
33155
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_REPORT);
|
|
33156
|
+
{
|
|
33157
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33158
|
+
riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT);
|
|
33159
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT));
|
|
33160
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_REPORT));
|
|
33161
|
+
}
|
|
33162
|
+
return await bt.strategyMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
|
|
33163
|
+
};
|
|
33164
|
+
/**
|
|
33165
|
+
* Generates and saves markdown report to file.
|
|
33166
|
+
*
|
|
33167
|
+
* Creates directory if it doesn't exist.
|
|
33168
|
+
* Filename format: {symbol}_{strategyName}_{exchangeName}_{frameName|live}-{timestamp}.md
|
|
33169
|
+
*
|
|
33170
|
+
* Delegates to StrategyMarkdownService.dump() which:
|
|
33171
|
+
* 1. Generates markdown report via getReport()
|
|
33172
|
+
* 2. Creates output directory (recursive mkdir)
|
|
33173
|
+
* 3. Writes file with UTF-8 encoding
|
|
33174
|
+
* 4. Logs success/failure to console
|
|
33175
|
+
*
|
|
33176
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
33177
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
33178
|
+
* @param backtest - Whether to dump backtest data (default: false)
|
|
33179
|
+
* @param path - Output directory path (default: "./dump/strategy")
|
|
33180
|
+
* @param columns - Optional columns configuration for the report
|
|
33181
|
+
* @returns Promise that resolves when file is written
|
|
33182
|
+
*
|
|
33183
|
+
* @example
|
|
33184
|
+
* ```typescript
|
|
33185
|
+
* // Save to default path: ./dump/strategy/BTCUSDT_my-strategy_binance_1h_backtest-{timestamp}.md
|
|
33186
|
+
* await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true);
|
|
33187
|
+
*
|
|
33188
|
+
* // Save to custom path
|
|
33189
|
+
* await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./reports/strategy");
|
|
33190
|
+
*
|
|
33191
|
+
* // After multiple symbols backtested, export all reports
|
|
33192
|
+
* for (const symbol of ["BTCUSDT", "ETHUSDT", "BNBUSDT"]) {
|
|
33193
|
+
* await Strategy.dump(symbol, { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./backtest-results");
|
|
33194
|
+
* }
|
|
33195
|
+
* ```
|
|
33196
|
+
*/
|
|
33197
|
+
this.dump = async (symbol, context, backtest = false, path, columns) => {
|
|
33198
|
+
bt.loggerService.info(STRATEGY_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
|
|
33199
|
+
bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_DUMP);
|
|
33200
|
+
bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_DUMP);
|
|
33201
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_DUMP);
|
|
33202
|
+
{
|
|
33203
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33204
|
+
riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP);
|
|
33205
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP));
|
|
33206
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_DUMP));
|
|
33207
|
+
}
|
|
33208
|
+
await bt.strategyMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
|
|
33209
|
+
};
|
|
33210
|
+
}
|
|
33211
|
+
}
|
|
33212
|
+
/**
|
|
33213
|
+
* Global singleton instance of StrategyUtils.
|
|
33214
|
+
* Provides static-like access to strategy management reporting methods.
|
|
33215
|
+
*
|
|
33216
|
+
* @example
|
|
33217
|
+
* ```typescript
|
|
33218
|
+
* import { Strategy } from "backtest-kit";
|
|
33219
|
+
*
|
|
33220
|
+
* // Usage same as StrategyUtils methods
|
|
33221
|
+
* const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33222
|
+
* const report = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33223
|
+
* await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33224
|
+
* ```
|
|
33225
|
+
*/
|
|
33226
|
+
const Strategy = new StrategyUtils();
|
|
33227
|
+
|
|
31412
33228
|
/**
|
|
31413
33229
|
* Rounds a price to the appropriate precision based on the tick size.
|
|
31414
33230
|
*
|
|
@@ -31569,6 +33385,7 @@ exports.Report = Report;
|
|
|
31569
33385
|
exports.ReportBase = ReportBase;
|
|
31570
33386
|
exports.Risk = Risk;
|
|
31571
33387
|
exports.Schedule = Schedule;
|
|
33388
|
+
exports.Strategy = Strategy;
|
|
31572
33389
|
exports.Walker = Walker;
|
|
31573
33390
|
exports.addActionSchema = addActionSchema;
|
|
31574
33391
|
exports.addExchangeSchema = addExchangeSchema;
|
|
@@ -31644,6 +33461,8 @@ exports.listenSignalBacktestOnce = listenSignalBacktestOnce;
|
|
|
31644
33461
|
exports.listenSignalLive = listenSignalLive;
|
|
31645
33462
|
exports.listenSignalLiveOnce = listenSignalLiveOnce;
|
|
31646
33463
|
exports.listenSignalOnce = listenSignalOnce;
|
|
33464
|
+
exports.listenStrategyCommit = listenStrategyCommit;
|
|
33465
|
+
exports.listenStrategyCommitOnce = listenStrategyCommitOnce;
|
|
31647
33466
|
exports.listenValidation = listenValidation;
|
|
31648
33467
|
exports.listenWalker = listenWalker;
|
|
31649
33468
|
exports.listenWalkerComplete = listenWalkerComplete;
|