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.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createActivator } from 'di-kit';
|
|
2
2
|
import { scoped } from 'di-scoped';
|
|
3
|
-
import { Subject, makeExtendable, singleshot, getErrorMessage, memoize, not, trycatch, retry,
|
|
3
|
+
import { Subject, makeExtendable, singleshot, getErrorMessage, memoize, errorData, not, trycatch, retry, queued, sleep, randomString, str, isObject, ToolRegistry, typo, and, resolveDocuments, timeout, TIMEOUT_SYMBOL as TIMEOUT_SYMBOL$1, compose, singlerun } from 'functools-kit';
|
|
4
4
|
import * as fs from 'fs/promises';
|
|
5
5
|
import fs__default from 'fs/promises';
|
|
6
6
|
import path, { join, dirname } from 'path';
|
|
@@ -101,6 +101,7 @@ const markdownServices$1 = {
|
|
|
101
101
|
breakevenMarkdownService: Symbol('breakevenMarkdownService'),
|
|
102
102
|
outlineMarkdownService: Symbol('outlineMarkdownService'),
|
|
103
103
|
riskMarkdownService: Symbol('riskMarkdownService'),
|
|
104
|
+
strategyMarkdownService: Symbol('strategyMarkdownService'),
|
|
104
105
|
};
|
|
105
106
|
const reportServices$1 = {
|
|
106
107
|
backtestReportService: Symbol('backtestReportService'),
|
|
@@ -112,6 +113,7 @@ const reportServices$1 = {
|
|
|
112
113
|
partialReportService: Symbol('partialReportService'),
|
|
113
114
|
breakevenReportService: Symbol('breakevenReportService'),
|
|
114
115
|
riskReportService: Symbol('riskReportService'),
|
|
116
|
+
strategyReportService: Symbol('strategyReportService'),
|
|
115
117
|
};
|
|
116
118
|
const validationServices$1 = {
|
|
117
119
|
exchangeValidationService: Symbol('exchangeValidationService'),
|
|
@@ -520,6 +522,20 @@ const schedulePingSubject = new Subject();
|
|
|
520
522
|
* Allows users to track active signal lifecycle and implement custom dynamic management logic.
|
|
521
523
|
*/
|
|
522
524
|
const activePingSubject = new Subject();
|
|
525
|
+
/**
|
|
526
|
+
* Strategy management signal emitter.
|
|
527
|
+
* Emits when strategy management actions are executed:
|
|
528
|
+
* - cancel-scheduled: Scheduled signal cancelled
|
|
529
|
+
* - close-pending: Pending signal closed
|
|
530
|
+
* - partial-profit: Partial close at profit level
|
|
531
|
+
* - partial-loss: Partial close at loss level
|
|
532
|
+
* - trailing-stop: Stop-loss adjusted
|
|
533
|
+
* - trailing-take: Take-profit adjusted
|
|
534
|
+
* - breakeven: Stop-loss moved to entry price
|
|
535
|
+
*
|
|
536
|
+
* Used by StrategyReportService and StrategyMarkdownService for event logging and reporting.
|
|
537
|
+
*/
|
|
538
|
+
const strategyCommitSubject = new Subject();
|
|
523
539
|
|
|
524
540
|
var emitters = /*#__PURE__*/Object.freeze({
|
|
525
541
|
__proto__: null,
|
|
@@ -540,6 +556,7 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
540
556
|
signalBacktestEmitter: signalBacktestEmitter,
|
|
541
557
|
signalEmitter: signalEmitter,
|
|
542
558
|
signalLiveEmitter: signalLiveEmitter,
|
|
559
|
+
strategyCommitSubject: strategyCommitSubject,
|
|
543
560
|
validationSubject: validationSubject,
|
|
544
561
|
walkerCompleteSubject: walkerCompleteSubject,
|
|
545
562
|
walkerEmitter: walkerEmitter,
|
|
@@ -1545,8 +1562,15 @@ class PersistCandleUtils {
|
|
|
1545
1562
|
const candle = await stateStorage.readValue(timestamp);
|
|
1546
1563
|
cachedCandles.push(candle);
|
|
1547
1564
|
}
|
|
1548
|
-
catch {
|
|
1549
|
-
|
|
1565
|
+
catch (error) {
|
|
1566
|
+
const message = `PersistCandleUtils.readCandlesData found invalid candle symbol=${symbol} interval=${interval} timestamp=${timestamp}`;
|
|
1567
|
+
const payload = {
|
|
1568
|
+
error: errorData(error),
|
|
1569
|
+
message: getErrorMessage(error),
|
|
1570
|
+
};
|
|
1571
|
+
bt.loggerService.warn(message, payload);
|
|
1572
|
+
console.warn(message, payload);
|
|
1573
|
+
errorEmitter.next(error);
|
|
1550
1574
|
continue;
|
|
1551
1575
|
}
|
|
1552
1576
|
}
|
|
@@ -3658,6 +3682,7 @@ const CHECK_SCHEDULED_SIGNAL_TIMEOUT_FN = async (self, scheduled, currentPrice)
|
|
|
3658
3682
|
symbol: self.params.execution.context.symbol,
|
|
3659
3683
|
backtest: self.params.execution.context.backtest,
|
|
3660
3684
|
reason: "timeout",
|
|
3685
|
+
createdAt: currentTime,
|
|
3661
3686
|
};
|
|
3662
3687
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
3663
3688
|
return result;
|
|
@@ -3713,6 +3738,7 @@ const CANCEL_SCHEDULED_SIGNAL_BY_STOPLOSS_FN = async (self, scheduled, currentPr
|
|
|
3713
3738
|
symbol: self.params.execution.context.symbol,
|
|
3714
3739
|
backtest: self.params.execution.context.backtest,
|
|
3715
3740
|
reason: "price_reject",
|
|
3741
|
+
createdAt: currentTime,
|
|
3716
3742
|
};
|
|
3717
3743
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
3718
3744
|
return result;
|
|
@@ -3768,6 +3794,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
|
|
|
3768
3794
|
symbol: self.params.execution.context.symbol,
|
|
3769
3795
|
currentPrice: self._pendingSignal.priceOpen,
|
|
3770
3796
|
backtest: self.params.execution.context.backtest,
|
|
3797
|
+
createdAt: activationTime,
|
|
3771
3798
|
};
|
|
3772
3799
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, activationTime, self.params.execution.context.backtest);
|
|
3773
3800
|
return result;
|
|
@@ -4200,6 +4227,7 @@ const RETURN_SCHEDULED_SIGNAL_ACTIVE_FN = async (self, scheduled, currentPrice)
|
|
|
4200
4227
|
percentSl: 0,
|
|
4201
4228
|
pnl,
|
|
4202
4229
|
backtest: self.params.execution.context.backtest,
|
|
4230
|
+
createdAt: currentTime,
|
|
4203
4231
|
};
|
|
4204
4232
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4205
4233
|
return result;
|
|
@@ -4224,6 +4252,7 @@ const OPEN_NEW_SCHEDULED_SIGNAL_FN = async (self, signal) => {
|
|
|
4224
4252
|
symbol: self.params.execution.context.symbol,
|
|
4225
4253
|
currentPrice: currentPrice,
|
|
4226
4254
|
backtest: self.params.execution.context.backtest,
|
|
4255
|
+
createdAt: currentTime,
|
|
4227
4256
|
};
|
|
4228
4257
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4229
4258
|
return result;
|
|
@@ -4244,6 +4273,7 @@ const OPEN_NEW_PENDING_SIGNAL_FN = async (self, signal) => {
|
|
|
4244
4273
|
symbol: self.params.execution.context.symbol,
|
|
4245
4274
|
currentPrice: signal.priceOpen,
|
|
4246
4275
|
backtest: self.params.execution.context.backtest,
|
|
4276
|
+
createdAt: currentTime,
|
|
4247
4277
|
};
|
|
4248
4278
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4249
4279
|
return result;
|
|
@@ -4308,6 +4338,7 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
|
|
|
4308
4338
|
frameName: self.params.method.context.frameName,
|
|
4309
4339
|
symbol: self.params.execution.context.symbol,
|
|
4310
4340
|
backtest: self.params.execution.context.backtest,
|
|
4341
|
+
createdAt: currentTime,
|
|
4311
4342
|
};
|
|
4312
4343
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4313
4344
|
return result;
|
|
@@ -4381,6 +4412,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
4381
4412
|
percentSl,
|
|
4382
4413
|
pnl,
|
|
4383
4414
|
backtest: self.params.execution.context.backtest,
|
|
4415
|
+
createdAt: currentTime,
|
|
4384
4416
|
};
|
|
4385
4417
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4386
4418
|
return result;
|
|
@@ -4397,6 +4429,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
|
|
|
4397
4429
|
symbol: self.params.execution.context.symbol,
|
|
4398
4430
|
currentPrice: currentPrice,
|
|
4399
4431
|
backtest: self.params.execution.context.backtest,
|
|
4432
|
+
createdAt: currentTime,
|
|
4400
4433
|
};
|
|
4401
4434
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
4402
4435
|
return result;
|
|
@@ -4423,6 +4456,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
|
|
|
4423
4456
|
symbol: self.params.execution.context.symbol,
|
|
4424
4457
|
backtest: self.params.execution.context.backtest,
|
|
4425
4458
|
reason,
|
|
4459
|
+
createdAt: closeTimestamp,
|
|
4426
4460
|
};
|
|
4427
4461
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
|
|
4428
4462
|
return result;
|
|
@@ -4503,6 +4537,7 @@ const CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, signal, averagePrice, c
|
|
|
4503
4537
|
frameName: self.params.method.context.frameName,
|
|
4504
4538
|
symbol: self.params.execution.context.symbol,
|
|
4505
4539
|
backtest: self.params.execution.context.backtest,
|
|
4540
|
+
createdAt: closeTimestamp,
|
|
4506
4541
|
};
|
|
4507
4542
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
|
|
4508
4543
|
return result;
|
|
@@ -5019,6 +5054,7 @@ class ClientStrategy {
|
|
|
5019
5054
|
backtest: this.params.execution.context.backtest,
|
|
5020
5055
|
reason: "user",
|
|
5021
5056
|
cancelId: cancelledSignal.cancelId,
|
|
5057
|
+
createdAt: currentTime,
|
|
5022
5058
|
};
|
|
5023
5059
|
await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
|
|
5024
5060
|
return result;
|
|
@@ -5053,6 +5089,7 @@ class ClientStrategy {
|
|
|
5053
5089
|
symbol: this.params.execution.context.symbol,
|
|
5054
5090
|
backtest: this.params.execution.context.backtest,
|
|
5055
5091
|
closeId: closedSignal.closeId,
|
|
5092
|
+
createdAt: currentTime,
|
|
5056
5093
|
};
|
|
5057
5094
|
await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
|
|
5058
5095
|
return result;
|
|
@@ -5167,6 +5204,7 @@ class ClientStrategy {
|
|
|
5167
5204
|
backtest: true,
|
|
5168
5205
|
reason: "user",
|
|
5169
5206
|
cancelId: cancelledSignal.cancelId,
|
|
5207
|
+
createdAt: closeTimestamp,
|
|
5170
5208
|
};
|
|
5171
5209
|
await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledResult, closeTimestamp, this.params.execution.context.backtest);
|
|
5172
5210
|
return cancelledResult;
|
|
@@ -5198,6 +5236,7 @@ class ClientStrategy {
|
|
|
5198
5236
|
symbol: this.params.execution.context.symbol,
|
|
5199
5237
|
backtest: true,
|
|
5200
5238
|
closeId: closedSignal.closeId,
|
|
5239
|
+
createdAt: closeTimestamp,
|
|
5201
5240
|
};
|
|
5202
5241
|
await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, closedResult, closeTimestamp, this.params.execution.context.backtest);
|
|
5203
5242
|
return closedResult;
|
|
@@ -6513,7 +6552,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
6513
6552
|
* @param backtest - Whether running in backtest mode
|
|
6514
6553
|
* @returns Unique string key for memoization
|
|
6515
6554
|
*/
|
|
6516
|
-
const CREATE_KEY_FN$
|
|
6555
|
+
const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
6517
6556
|
const parts = [symbol, strategyName, exchangeName];
|
|
6518
6557
|
if (frameName)
|
|
6519
6558
|
parts.push(frameName);
|
|
@@ -6678,7 +6717,7 @@ class StrategyConnectionService {
|
|
|
6678
6717
|
* @param backtest - Whether running in backtest mode
|
|
6679
6718
|
* @returns Configured ClientStrategy instance
|
|
6680
6719
|
*/
|
|
6681
|
-
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
6720
|
+
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
6682
6721
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
6683
6722
|
return new ClientStrategy({
|
|
6684
6723
|
symbol,
|
|
@@ -6933,7 +6972,7 @@ class StrategyConnectionService {
|
|
|
6933
6972
|
}
|
|
6934
6973
|
return;
|
|
6935
6974
|
}
|
|
6936
|
-
const key = CREATE_KEY_FN$
|
|
6975
|
+
const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
6937
6976
|
if (!this.getStrategy.has(key)) {
|
|
6938
6977
|
return;
|
|
6939
6978
|
}
|
|
@@ -7902,7 +7941,7 @@ class ClientRisk {
|
|
|
7902
7941
|
* @param backtest - Whether running in backtest mode
|
|
7903
7942
|
* @returns Unique string key for memoization
|
|
7904
7943
|
*/
|
|
7905
|
-
const CREATE_KEY_FN$
|
|
7944
|
+
const CREATE_KEY_FN$k = (riskName, exchangeName, frameName, backtest) => {
|
|
7906
7945
|
const parts = [riskName, exchangeName];
|
|
7907
7946
|
if (frameName)
|
|
7908
7947
|
parts.push(frameName);
|
|
@@ -8001,7 +8040,7 @@ class RiskConnectionService {
|
|
|
8001
8040
|
* @param backtest - True if backtest mode, false if live mode
|
|
8002
8041
|
* @returns Configured ClientRisk instance
|
|
8003
8042
|
*/
|
|
8004
|
-
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
8043
|
+
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
8005
8044
|
const schema = this.riskSchemaService.get(riskName);
|
|
8006
8045
|
return new ClientRisk({
|
|
8007
8046
|
...schema,
|
|
@@ -8069,7 +8108,7 @@ class RiskConnectionService {
|
|
|
8069
8108
|
payload,
|
|
8070
8109
|
});
|
|
8071
8110
|
if (payload) {
|
|
8072
|
-
const key = CREATE_KEY_FN$
|
|
8111
|
+
const key = CREATE_KEY_FN$k(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
8073
8112
|
this.getRisk.clear(key);
|
|
8074
8113
|
}
|
|
8075
8114
|
else {
|
|
@@ -9492,7 +9531,7 @@ class ClientAction {
|
|
|
9492
9531
|
* @param backtest - Whether running in backtest mode
|
|
9493
9532
|
* @returns Unique string key for memoization
|
|
9494
9533
|
*/
|
|
9495
|
-
const CREATE_KEY_FN$
|
|
9534
|
+
const CREATE_KEY_FN$j = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
9496
9535
|
const parts = [actionName, strategyName, exchangeName];
|
|
9497
9536
|
if (frameName)
|
|
9498
9537
|
parts.push(frameName);
|
|
@@ -9543,7 +9582,7 @@ class ActionConnectionService {
|
|
|
9543
9582
|
* @param backtest - True if backtest mode, false if live mode
|
|
9544
9583
|
* @returns Configured ClientAction instance
|
|
9545
9584
|
*/
|
|
9546
|
-
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
9585
|
+
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
9547
9586
|
const schema = this.actionSchemaService.get(actionName);
|
|
9548
9587
|
return new ClientAction({
|
|
9549
9588
|
...schema,
|
|
@@ -9736,7 +9775,7 @@ class ActionConnectionService {
|
|
|
9736
9775
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
9737
9776
|
return;
|
|
9738
9777
|
}
|
|
9739
|
-
const key = CREATE_KEY_FN$
|
|
9778
|
+
const key = CREATE_KEY_FN$j(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
9740
9779
|
if (!this.getAction.has(key)) {
|
|
9741
9780
|
return;
|
|
9742
9781
|
}
|
|
@@ -9754,7 +9793,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
9754
9793
|
* @param exchangeName - Exchange name
|
|
9755
9794
|
* @returns Unique string key for memoization
|
|
9756
9795
|
*/
|
|
9757
|
-
const CREATE_KEY_FN$
|
|
9796
|
+
const CREATE_KEY_FN$i = (exchangeName) => {
|
|
9758
9797
|
return exchangeName;
|
|
9759
9798
|
};
|
|
9760
9799
|
/**
|
|
@@ -9778,7 +9817,7 @@ class ExchangeCoreService {
|
|
|
9778
9817
|
* @param exchangeName - Name of the exchange to validate
|
|
9779
9818
|
* @returns Promise that resolves when validation is complete
|
|
9780
9819
|
*/
|
|
9781
|
-
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
9820
|
+
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$i(exchangeName), async (exchangeName) => {
|
|
9782
9821
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
9783
9822
|
exchangeName,
|
|
9784
9823
|
});
|
|
@@ -10002,12 +10041,32 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
10002
10041
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10003
10042
|
* @returns Unique string key for memoization
|
|
10004
10043
|
*/
|
|
10005
|
-
const CREATE_KEY_FN$
|
|
10044
|
+
const CREATE_KEY_FN$h = (context) => {
|
|
10006
10045
|
const parts = [context.strategyName, context.exchangeName];
|
|
10007
10046
|
if (context.frameName)
|
|
10008
10047
|
parts.push(context.frameName);
|
|
10009
10048
|
return parts.join(":");
|
|
10010
10049
|
};
|
|
10050
|
+
/**
|
|
10051
|
+
* Broadcasts StrategyCommitContract event to strategyCommitSubject.
|
|
10052
|
+
*
|
|
10053
|
+
* @param event - The signal commit event to broadcast
|
|
10054
|
+
*/
|
|
10055
|
+
const CALL_STRATEGY_COMMIT_FN = trycatch(async (event) => {
|
|
10056
|
+
await strategyCommitSubject.next(event);
|
|
10057
|
+
}, {
|
|
10058
|
+
fallback: (error) => {
|
|
10059
|
+
const message = "StrategyCoreService CALL_STRATEGY_COMMIT_FN thrown";
|
|
10060
|
+
const payload = {
|
|
10061
|
+
error: errorData(error),
|
|
10062
|
+
message: getErrorMessage(error),
|
|
10063
|
+
};
|
|
10064
|
+
bt.loggerService.warn(message, payload);
|
|
10065
|
+
console.warn(message, payload);
|
|
10066
|
+
errorEmitter.next(error);
|
|
10067
|
+
},
|
|
10068
|
+
defaultValue: null,
|
|
10069
|
+
});
|
|
10011
10070
|
/**
|
|
10012
10071
|
* Global service for strategy operations with execution context injection.
|
|
10013
10072
|
*
|
|
@@ -10034,7 +10093,7 @@ class StrategyCoreService {
|
|
|
10034
10093
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10035
10094
|
* @returns Promise that resolves when validation is complete
|
|
10036
10095
|
*/
|
|
10037
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
10096
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$h(context), async (context) => {
|
|
10038
10097
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
10039
10098
|
context,
|
|
10040
10099
|
});
|
|
@@ -10240,7 +10299,19 @@ class StrategyCoreService {
|
|
|
10240
10299
|
cancelId,
|
|
10241
10300
|
});
|
|
10242
10301
|
await this.validate(context);
|
|
10243
|
-
|
|
10302
|
+
const result = await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
|
|
10303
|
+
{
|
|
10304
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10305
|
+
action: "cancel-scheduled",
|
|
10306
|
+
symbol,
|
|
10307
|
+
strategyName: context.strategyName,
|
|
10308
|
+
exchangeName: context.exchangeName,
|
|
10309
|
+
frameName: context.frameName,
|
|
10310
|
+
backtest,
|
|
10311
|
+
cancelId,
|
|
10312
|
+
});
|
|
10313
|
+
}
|
|
10314
|
+
return result;
|
|
10244
10315
|
};
|
|
10245
10316
|
/**
|
|
10246
10317
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -10267,7 +10338,19 @@ class StrategyCoreService {
|
|
|
10267
10338
|
closeId,
|
|
10268
10339
|
});
|
|
10269
10340
|
await this.validate(context);
|
|
10270
|
-
|
|
10341
|
+
const result = await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
|
|
10342
|
+
{
|
|
10343
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10344
|
+
action: "close-pending",
|
|
10345
|
+
symbol,
|
|
10346
|
+
strategyName: context.strategyName,
|
|
10347
|
+
exchangeName: context.exchangeName,
|
|
10348
|
+
frameName: context.frameName,
|
|
10349
|
+
backtest,
|
|
10350
|
+
closeId,
|
|
10351
|
+
});
|
|
10352
|
+
}
|
|
10353
|
+
return result;
|
|
10271
10354
|
};
|
|
10272
10355
|
/**
|
|
10273
10356
|
* Disposes the ClientStrategy instance for the given context.
|
|
@@ -10348,7 +10431,20 @@ class StrategyCoreService {
|
|
|
10348
10431
|
backtest,
|
|
10349
10432
|
});
|
|
10350
10433
|
await this.validate(context);
|
|
10351
|
-
|
|
10434
|
+
const result = await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
|
|
10435
|
+
if (result) {
|
|
10436
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10437
|
+
action: "partial-profit",
|
|
10438
|
+
symbol,
|
|
10439
|
+
strategyName: context.strategyName,
|
|
10440
|
+
exchangeName: context.exchangeName,
|
|
10441
|
+
frameName: context.frameName,
|
|
10442
|
+
backtest,
|
|
10443
|
+
percentToClose,
|
|
10444
|
+
currentPrice,
|
|
10445
|
+
});
|
|
10446
|
+
}
|
|
10447
|
+
return result;
|
|
10352
10448
|
};
|
|
10353
10449
|
/**
|
|
10354
10450
|
* Executes partial close at loss level (moving toward SL).
|
|
@@ -10389,7 +10485,20 @@ class StrategyCoreService {
|
|
|
10389
10485
|
backtest,
|
|
10390
10486
|
});
|
|
10391
10487
|
await this.validate(context);
|
|
10392
|
-
|
|
10488
|
+
const result = await this.strategyConnectionService.partialLoss(backtest, symbol, percentToClose, currentPrice, context);
|
|
10489
|
+
if (result) {
|
|
10490
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10491
|
+
action: "partial-loss",
|
|
10492
|
+
symbol,
|
|
10493
|
+
strategyName: context.strategyName,
|
|
10494
|
+
exchangeName: context.exchangeName,
|
|
10495
|
+
frameName: context.frameName,
|
|
10496
|
+
backtest,
|
|
10497
|
+
percentToClose,
|
|
10498
|
+
currentPrice,
|
|
10499
|
+
});
|
|
10500
|
+
}
|
|
10501
|
+
return result;
|
|
10393
10502
|
};
|
|
10394
10503
|
/**
|
|
10395
10504
|
* Adjusts the trailing stop-loss distance for an active pending signal.
|
|
@@ -10428,7 +10537,20 @@ class StrategyCoreService {
|
|
|
10428
10537
|
backtest,
|
|
10429
10538
|
});
|
|
10430
10539
|
await this.validate(context);
|
|
10431
|
-
|
|
10540
|
+
const result = await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
|
|
10541
|
+
if (result) {
|
|
10542
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10543
|
+
action: "trailing-stop",
|
|
10544
|
+
symbol,
|
|
10545
|
+
strategyName: context.strategyName,
|
|
10546
|
+
exchangeName: context.exchangeName,
|
|
10547
|
+
frameName: context.frameName,
|
|
10548
|
+
backtest,
|
|
10549
|
+
percentShift,
|
|
10550
|
+
currentPrice,
|
|
10551
|
+
});
|
|
10552
|
+
}
|
|
10553
|
+
return result;
|
|
10432
10554
|
};
|
|
10433
10555
|
/**
|
|
10434
10556
|
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
@@ -10463,7 +10585,20 @@ class StrategyCoreService {
|
|
|
10463
10585
|
backtest,
|
|
10464
10586
|
});
|
|
10465
10587
|
await this.validate(context);
|
|
10466
|
-
|
|
10588
|
+
const result = await this.strategyConnectionService.trailingTake(backtest, symbol, percentShift, currentPrice, context);
|
|
10589
|
+
if (result) {
|
|
10590
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10591
|
+
action: "trailing-take",
|
|
10592
|
+
symbol,
|
|
10593
|
+
strategyName: context.strategyName,
|
|
10594
|
+
exchangeName: context.exchangeName,
|
|
10595
|
+
frameName: context.frameName,
|
|
10596
|
+
backtest,
|
|
10597
|
+
percentShift,
|
|
10598
|
+
currentPrice,
|
|
10599
|
+
});
|
|
10600
|
+
}
|
|
10601
|
+
return result;
|
|
10467
10602
|
};
|
|
10468
10603
|
/**
|
|
10469
10604
|
* Moves stop-loss to breakeven when price reaches threshold.
|
|
@@ -10493,7 +10628,19 @@ class StrategyCoreService {
|
|
|
10493
10628
|
backtest,
|
|
10494
10629
|
});
|
|
10495
10630
|
await this.validate(context);
|
|
10496
|
-
|
|
10631
|
+
const result = await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
|
|
10632
|
+
if (result) {
|
|
10633
|
+
await CALL_STRATEGY_COMMIT_FN({
|
|
10634
|
+
action: "breakeven",
|
|
10635
|
+
symbol,
|
|
10636
|
+
strategyName: context.strategyName,
|
|
10637
|
+
exchangeName: context.exchangeName,
|
|
10638
|
+
frameName: context.frameName,
|
|
10639
|
+
backtest,
|
|
10640
|
+
currentPrice,
|
|
10641
|
+
});
|
|
10642
|
+
}
|
|
10643
|
+
return result;
|
|
10497
10644
|
};
|
|
10498
10645
|
}
|
|
10499
10646
|
}
|
|
@@ -10567,7 +10714,7 @@ class SizingGlobalService {
|
|
|
10567
10714
|
* @param context - Context with riskName, exchangeName, frameName
|
|
10568
10715
|
* @returns Unique string key for memoization
|
|
10569
10716
|
*/
|
|
10570
|
-
const CREATE_KEY_FN$
|
|
10717
|
+
const CREATE_KEY_FN$g = (context) => {
|
|
10571
10718
|
const parts = [context.riskName, context.exchangeName];
|
|
10572
10719
|
if (context.frameName)
|
|
10573
10720
|
parts.push(context.frameName);
|
|
@@ -10593,7 +10740,7 @@ class RiskGlobalService {
|
|
|
10593
10740
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
10594
10741
|
* @returns Promise that resolves when validation is complete
|
|
10595
10742
|
*/
|
|
10596
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
10743
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
|
|
10597
10744
|
this.loggerService.log("riskGlobalService validate", {
|
|
10598
10745
|
context,
|
|
10599
10746
|
});
|
|
@@ -10671,7 +10818,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
10671
10818
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10672
10819
|
* @returns Unique string key for memoization
|
|
10673
10820
|
*/
|
|
10674
|
-
const CREATE_KEY_FN$
|
|
10821
|
+
const CREATE_KEY_FN$f = (context) => {
|
|
10675
10822
|
const parts = [context.strategyName, context.exchangeName];
|
|
10676
10823
|
if (context.frameName)
|
|
10677
10824
|
parts.push(context.frameName);
|
|
@@ -10715,7 +10862,7 @@ class ActionCoreService {
|
|
|
10715
10862
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
10716
10863
|
* @returns Promise that resolves when all validations complete
|
|
10717
10864
|
*/
|
|
10718
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
10865
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$f(context), async (context) => {
|
|
10719
10866
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
10720
10867
|
context,
|
|
10721
10868
|
});
|
|
@@ -13965,6 +14112,116 @@ const schedule_columns = [
|
|
|
13965
14112
|
},
|
|
13966
14113
|
];
|
|
13967
14114
|
|
|
14115
|
+
/**
|
|
14116
|
+
* Column configuration for strategy markdown reports.
|
|
14117
|
+
*
|
|
14118
|
+
* Defines the table structure for displaying strategy management events in trading reports.
|
|
14119
|
+
* Each column specifies how to format and display strategy actions like partial profit/loss,
|
|
14120
|
+
* trailing stop/take, breakeven, cancel scheduled, and close pending.
|
|
14121
|
+
*
|
|
14122
|
+
* Used by {@link StrategyMarkdownService} to generate markdown tables showing:
|
|
14123
|
+
* - Signal identification (symbol, strategy name, signal ID)
|
|
14124
|
+
* - Action information (action type, percent values, prices)
|
|
14125
|
+
* - Timing information (timestamp, mode: backtest or live)
|
|
14126
|
+
*
|
|
14127
|
+
* @remarks
|
|
14128
|
+
* This configuration tracks all strategy management events - when signals are
|
|
14129
|
+
* modified, cancelled, or closed during their lifecycle.
|
|
14130
|
+
*
|
|
14131
|
+
* @example
|
|
14132
|
+
* ```typescript
|
|
14133
|
+
* import { strategy_columns } from "./assets/strategy.columns";
|
|
14134
|
+
*
|
|
14135
|
+
* // Use with StrategyMarkdownService
|
|
14136
|
+
* const service = new StrategyMarkdownService();
|
|
14137
|
+
* await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, strategy_columns);
|
|
14138
|
+
*
|
|
14139
|
+
* // Or customize to show only key fields
|
|
14140
|
+
* const customColumns = strategy_columns.filter(col =>
|
|
14141
|
+
* ["symbol", "action", "currentPrice", "timestamp"].includes(col.key)
|
|
14142
|
+
* );
|
|
14143
|
+
* await service.getReport("BTCUSDT", "my-strategy", "binance", "1h", false, customColumns);
|
|
14144
|
+
* ```
|
|
14145
|
+
*
|
|
14146
|
+
* @see {@link StrategyMarkdownService} for usage in report generation
|
|
14147
|
+
* @see {@link ColumnModel} for column interface definition
|
|
14148
|
+
* @see {@link StrategyEvent} for data structure
|
|
14149
|
+
*/
|
|
14150
|
+
const strategy_columns = [
|
|
14151
|
+
{
|
|
14152
|
+
key: "symbol",
|
|
14153
|
+
label: "Symbol",
|
|
14154
|
+
format: (data) => data.symbol,
|
|
14155
|
+
isVisible: () => true,
|
|
14156
|
+
},
|
|
14157
|
+
{
|
|
14158
|
+
key: "strategyName",
|
|
14159
|
+
label: "Strategy",
|
|
14160
|
+
format: (data) => data.strategyName,
|
|
14161
|
+
isVisible: () => true,
|
|
14162
|
+
},
|
|
14163
|
+
{
|
|
14164
|
+
key: "signalId",
|
|
14165
|
+
label: "Signal ID",
|
|
14166
|
+
format: (data) => data.signalId,
|
|
14167
|
+
isVisible: () => true,
|
|
14168
|
+
},
|
|
14169
|
+
{
|
|
14170
|
+
key: "action",
|
|
14171
|
+
label: "Action",
|
|
14172
|
+
format: (data) => data.action,
|
|
14173
|
+
isVisible: () => true,
|
|
14174
|
+
},
|
|
14175
|
+
{
|
|
14176
|
+
key: "currentPrice",
|
|
14177
|
+
label: "Price",
|
|
14178
|
+
format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(8)} USD` : "N/A"),
|
|
14179
|
+
isVisible: () => true,
|
|
14180
|
+
},
|
|
14181
|
+
{
|
|
14182
|
+
key: "percentToClose",
|
|
14183
|
+
label: "% To Close",
|
|
14184
|
+
format: (data) => (data.percentToClose !== undefined ? `${data.percentToClose.toFixed(2)}%` : "N/A"),
|
|
14185
|
+
isVisible: () => true,
|
|
14186
|
+
},
|
|
14187
|
+
{
|
|
14188
|
+
key: "percentShift",
|
|
14189
|
+
label: "% Shift",
|
|
14190
|
+
format: (data) => (data.percentShift !== undefined ? `${data.percentShift.toFixed(2)}%` : "N/A"),
|
|
14191
|
+
isVisible: () => true,
|
|
14192
|
+
},
|
|
14193
|
+
{
|
|
14194
|
+
key: "cancelId",
|
|
14195
|
+
label: "Cancel ID",
|
|
14196
|
+
format: (data) => data.cancelId || "N/A",
|
|
14197
|
+
isVisible: () => true,
|
|
14198
|
+
},
|
|
14199
|
+
{
|
|
14200
|
+
key: "closeId",
|
|
14201
|
+
label: "Close ID",
|
|
14202
|
+
format: (data) => data.closeId || "N/A",
|
|
14203
|
+
isVisible: () => true,
|
|
14204
|
+
},
|
|
14205
|
+
{
|
|
14206
|
+
key: "createdAt",
|
|
14207
|
+
label: "Created At",
|
|
14208
|
+
format: (data) => data.createdAt || "N/A",
|
|
14209
|
+
isVisible: () => true,
|
|
14210
|
+
},
|
|
14211
|
+
{
|
|
14212
|
+
key: "timestamp",
|
|
14213
|
+
label: "Timestamp",
|
|
14214
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
14215
|
+
isVisible: () => true,
|
|
14216
|
+
},
|
|
14217
|
+
{
|
|
14218
|
+
key: "mode",
|
|
14219
|
+
label: "Mode",
|
|
14220
|
+
format: (data) => (data.backtest ? "Backtest" : "Live"),
|
|
14221
|
+
isVisible: () => true,
|
|
14222
|
+
},
|
|
14223
|
+
];
|
|
14224
|
+
|
|
13968
14225
|
/**
|
|
13969
14226
|
* Column configuration for walker strategy comparison table in markdown reports.
|
|
13970
14227
|
*
|
|
@@ -14178,6 +14435,8 @@ const COLUMN_CONFIG = {
|
|
|
14178
14435
|
risk_columns,
|
|
14179
14436
|
/** Columns for scheduled report output */
|
|
14180
14437
|
schedule_columns,
|
|
14438
|
+
/** Columns for strategy management events */
|
|
14439
|
+
strategy_columns,
|
|
14181
14440
|
/** Walker: PnL summary columns */
|
|
14182
14441
|
walker_pnl_columns,
|
|
14183
14442
|
/** Walker: strategy-level summary columns */
|
|
@@ -14214,6 +14473,7 @@ const WILDCARD_TARGET$1 = {
|
|
|
14214
14473
|
partial: true,
|
|
14215
14474
|
performance: true,
|
|
14216
14475
|
risk: true,
|
|
14476
|
+
strategy: true,
|
|
14217
14477
|
schedule: true,
|
|
14218
14478
|
walker: true,
|
|
14219
14479
|
};
|
|
@@ -14443,7 +14703,7 @@ class MarkdownUtils {
|
|
|
14443
14703
|
*
|
|
14444
14704
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
14445
14705
|
*/
|
|
14446
|
-
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) => {
|
|
14706
|
+
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) => {
|
|
14447
14707
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
|
|
14448
14708
|
backtest: bt$1,
|
|
14449
14709
|
breakeven,
|
|
@@ -14452,6 +14712,7 @@ class MarkdownUtils {
|
|
|
14452
14712
|
partial,
|
|
14453
14713
|
performance,
|
|
14454
14714
|
risk,
|
|
14715
|
+
strategy,
|
|
14455
14716
|
schedule,
|
|
14456
14717
|
walker,
|
|
14457
14718
|
});
|
|
@@ -14477,6 +14738,9 @@ class MarkdownUtils {
|
|
|
14477
14738
|
if (risk) {
|
|
14478
14739
|
unList.push(bt.riskMarkdownService.subscribe());
|
|
14479
14740
|
}
|
|
14741
|
+
if (strategy) {
|
|
14742
|
+
unList.push(bt.strategyMarkdownService.subscribe());
|
|
14743
|
+
}
|
|
14480
14744
|
if (schedule) {
|
|
14481
14745
|
unList.push(bt.scheduleMarkdownService.subscribe());
|
|
14482
14746
|
}
|
|
@@ -14522,7 +14786,7 @@ class MarkdownUtils {
|
|
|
14522
14786
|
* Markdown.disable();
|
|
14523
14787
|
* ```
|
|
14524
14788
|
*/
|
|
14525
|
-
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) => {
|
|
14789
|
+
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) => {
|
|
14526
14790
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
|
|
14527
14791
|
backtest: bt$1,
|
|
14528
14792
|
breakeven,
|
|
@@ -14531,6 +14795,7 @@ class MarkdownUtils {
|
|
|
14531
14795
|
partial,
|
|
14532
14796
|
performance,
|
|
14533
14797
|
risk,
|
|
14798
|
+
strategy,
|
|
14534
14799
|
schedule,
|
|
14535
14800
|
walker,
|
|
14536
14801
|
});
|
|
@@ -14555,6 +14820,9 @@ class MarkdownUtils {
|
|
|
14555
14820
|
if (risk) {
|
|
14556
14821
|
bt.riskMarkdownService.unsubscribe();
|
|
14557
14822
|
}
|
|
14823
|
+
if (strategy) {
|
|
14824
|
+
bt.strategyMarkdownService.unsubscribe();
|
|
14825
|
+
}
|
|
14558
14826
|
if (schedule) {
|
|
14559
14827
|
bt.scheduleMarkdownService.unsubscribe();
|
|
14560
14828
|
}
|
|
@@ -14667,7 +14935,7 @@ const Markdown = new MarkdownAdapter();
|
|
|
14667
14935
|
* @param backtest - Whether running in backtest mode
|
|
14668
14936
|
* @returns Unique string key for memoization
|
|
14669
14937
|
*/
|
|
14670
|
-
const CREATE_KEY_FN$
|
|
14938
|
+
const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
14671
14939
|
const parts = [symbol, strategyName, exchangeName];
|
|
14672
14940
|
if (frameName)
|
|
14673
14941
|
parts.push(frameName);
|
|
@@ -14684,7 +14952,7 @@ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
14684
14952
|
* @param timestamp - Unix timestamp in milliseconds
|
|
14685
14953
|
* @returns Filename string
|
|
14686
14954
|
*/
|
|
14687
|
-
const CREATE_FILE_NAME_FN$
|
|
14955
|
+
const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
14688
14956
|
const parts = [symbol, strategyName, exchangeName];
|
|
14689
14957
|
if (frameName) {
|
|
14690
14958
|
parts.push(frameName);
|
|
@@ -14713,12 +14981,12 @@ function isUnsafe$3(value) {
|
|
|
14713
14981
|
return false;
|
|
14714
14982
|
}
|
|
14715
14983
|
/** Maximum number of signals to store in backtest reports */
|
|
14716
|
-
const MAX_EVENTS$
|
|
14984
|
+
const MAX_EVENTS$8 = 250;
|
|
14717
14985
|
/**
|
|
14718
14986
|
* Storage class for accumulating closed signals per strategy.
|
|
14719
14987
|
* Maintains a list of all closed signals and provides methods to generate reports.
|
|
14720
14988
|
*/
|
|
14721
|
-
let ReportStorage$
|
|
14989
|
+
let ReportStorage$7 = class ReportStorage {
|
|
14722
14990
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
14723
14991
|
this.symbol = symbol;
|
|
14724
14992
|
this.strategyName = strategyName;
|
|
@@ -14735,7 +15003,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
14735
15003
|
addSignal(data) {
|
|
14736
15004
|
this._signalList.unshift(data);
|
|
14737
15005
|
// Trim queue if exceeded MAX_EVENTS
|
|
14738
|
-
if (this._signalList.length > MAX_EVENTS$
|
|
15006
|
+
if (this._signalList.length > MAX_EVENTS$8) {
|
|
14739
15007
|
this._signalList.pop();
|
|
14740
15008
|
}
|
|
14741
15009
|
}
|
|
@@ -14859,7 +15127,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
14859
15127
|
async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
|
|
14860
15128
|
const markdown = await this.getReport(strategyName, columns);
|
|
14861
15129
|
const timestamp = Date.now();
|
|
14862
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
15130
|
+
const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
14863
15131
|
await Markdown.writeData("backtest", markdown, {
|
|
14864
15132
|
path,
|
|
14865
15133
|
file: filename,
|
|
@@ -14906,7 +15174,7 @@ class BacktestMarkdownService {
|
|
|
14906
15174
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
14907
15175
|
* Each combination gets its own isolated storage instance.
|
|
14908
15176
|
*/
|
|
14909
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
15177
|
+
this.getStorage = 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));
|
|
14910
15178
|
/**
|
|
14911
15179
|
* Processes tick events and accumulates closed signals.
|
|
14912
15180
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -15063,7 +15331,7 @@ class BacktestMarkdownService {
|
|
|
15063
15331
|
payload,
|
|
15064
15332
|
});
|
|
15065
15333
|
if (payload) {
|
|
15066
|
-
const key = CREATE_KEY_FN$
|
|
15334
|
+
const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
15067
15335
|
this.getStorage.clear(key);
|
|
15068
15336
|
}
|
|
15069
15337
|
else {
|
|
@@ -15125,7 +15393,7 @@ class BacktestMarkdownService {
|
|
|
15125
15393
|
* @param backtest - Whether running in backtest mode
|
|
15126
15394
|
* @returns Unique string key for memoization
|
|
15127
15395
|
*/
|
|
15128
|
-
const CREATE_KEY_FN$
|
|
15396
|
+
const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
15129
15397
|
const parts = [symbol, strategyName, exchangeName];
|
|
15130
15398
|
if (frameName)
|
|
15131
15399
|
parts.push(frameName);
|
|
@@ -15142,7 +15410,7 @@ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
15142
15410
|
* @param timestamp - Unix timestamp in milliseconds
|
|
15143
15411
|
* @returns Filename string
|
|
15144
15412
|
*/
|
|
15145
|
-
const CREATE_FILE_NAME_FN$
|
|
15413
|
+
const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
15146
15414
|
const parts = [symbol, strategyName, exchangeName];
|
|
15147
15415
|
if (frameName) {
|
|
15148
15416
|
parts.push(frameName);
|
|
@@ -15171,12 +15439,12 @@ function isUnsafe$2(value) {
|
|
|
15171
15439
|
return false;
|
|
15172
15440
|
}
|
|
15173
15441
|
/** Maximum number of events to store in live trading reports */
|
|
15174
|
-
const MAX_EVENTS$
|
|
15442
|
+
const MAX_EVENTS$7 = 250;
|
|
15175
15443
|
/**
|
|
15176
15444
|
* Storage class for accumulating all tick events per strategy.
|
|
15177
15445
|
* Maintains a chronological list of all events (idle, opened, active, closed).
|
|
15178
15446
|
*/
|
|
15179
|
-
let ReportStorage$
|
|
15447
|
+
let ReportStorage$6 = class ReportStorage {
|
|
15180
15448
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
15181
15449
|
this.symbol = symbol;
|
|
15182
15450
|
this.strategyName = strategyName;
|
|
@@ -15208,7 +15476,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15208
15476
|
}
|
|
15209
15477
|
{
|
|
15210
15478
|
this._eventList.unshift(newEvent);
|
|
15211
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15479
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15212
15480
|
this._eventList.pop();
|
|
15213
15481
|
}
|
|
15214
15482
|
}
|
|
@@ -15235,7 +15503,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15235
15503
|
totalExecuted: data.signal.totalExecuted,
|
|
15236
15504
|
});
|
|
15237
15505
|
// Trim queue if exceeded MAX_EVENTS
|
|
15238
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15506
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15239
15507
|
this._eventList.pop();
|
|
15240
15508
|
}
|
|
15241
15509
|
}
|
|
@@ -15274,7 +15542,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15274
15542
|
// If no previous active event found, add new event
|
|
15275
15543
|
this._eventList.unshift(newEvent);
|
|
15276
15544
|
// Trim queue if exceeded MAX_EVENTS
|
|
15277
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15545
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15278
15546
|
this._eventList.pop();
|
|
15279
15547
|
}
|
|
15280
15548
|
}
|
|
@@ -15306,7 +15574,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15306
15574
|
};
|
|
15307
15575
|
this._eventList.unshift(newEvent);
|
|
15308
15576
|
// Trim queue if exceeded MAX_EVENTS
|
|
15309
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15577
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15310
15578
|
this._eventList.pop();
|
|
15311
15579
|
}
|
|
15312
15580
|
}
|
|
@@ -15332,7 +15600,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15332
15600
|
totalExecuted: data.signal.totalExecuted,
|
|
15333
15601
|
});
|
|
15334
15602
|
// Trim queue if exceeded MAX_EVENTS
|
|
15335
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15603
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15336
15604
|
this._eventList.pop();
|
|
15337
15605
|
}
|
|
15338
15606
|
}
|
|
@@ -15371,7 +15639,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15371
15639
|
// If no previous waiting event found, add new event
|
|
15372
15640
|
this._eventList.unshift(newEvent);
|
|
15373
15641
|
// Trim queue if exceeded MAX_EVENTS
|
|
15374
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15642
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15375
15643
|
this._eventList.pop();
|
|
15376
15644
|
}
|
|
15377
15645
|
}
|
|
@@ -15398,7 +15666,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15398
15666
|
cancelReason: data.reason,
|
|
15399
15667
|
});
|
|
15400
15668
|
// Trim queue if exceeded MAX_EVENTS
|
|
15401
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
15669
|
+
if (this._eventList.length > MAX_EVENTS$7) {
|
|
15402
15670
|
this._eventList.pop();
|
|
15403
15671
|
}
|
|
15404
15672
|
}
|
|
@@ -15537,7 +15805,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
15537
15805
|
async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
|
|
15538
15806
|
const markdown = await this.getReport(strategyName, columns);
|
|
15539
15807
|
const timestamp = Date.now();
|
|
15540
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
15808
|
+
const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
15541
15809
|
await Markdown.writeData("live", markdown, {
|
|
15542
15810
|
path,
|
|
15543
15811
|
signalId: "",
|
|
@@ -15587,7 +15855,7 @@ class LiveMarkdownService {
|
|
|
15587
15855
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
15588
15856
|
* Each combination gets its own isolated storage instance.
|
|
15589
15857
|
*/
|
|
15590
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
15858
|
+
this.getStorage = 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));
|
|
15591
15859
|
/**
|
|
15592
15860
|
* Subscribes to live signal emitter to receive tick events.
|
|
15593
15861
|
* Protected against multiple subscriptions.
|
|
@@ -15805,7 +16073,7 @@ class LiveMarkdownService {
|
|
|
15805
16073
|
payload,
|
|
15806
16074
|
});
|
|
15807
16075
|
if (payload) {
|
|
15808
|
-
const key = CREATE_KEY_FN$
|
|
16076
|
+
const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
15809
16077
|
this.getStorage.clear(key);
|
|
15810
16078
|
}
|
|
15811
16079
|
else {
|
|
@@ -15825,7 +16093,7 @@ class LiveMarkdownService {
|
|
|
15825
16093
|
* @param backtest - Whether running in backtest mode
|
|
15826
16094
|
* @returns Unique string key for memoization
|
|
15827
16095
|
*/
|
|
15828
|
-
const CREATE_KEY_FN$
|
|
16096
|
+
const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
15829
16097
|
const parts = [symbol, strategyName, exchangeName];
|
|
15830
16098
|
if (frameName)
|
|
15831
16099
|
parts.push(frameName);
|
|
@@ -15842,7 +16110,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
15842
16110
|
* @param timestamp - Unix timestamp in milliseconds
|
|
15843
16111
|
* @returns Filename string
|
|
15844
16112
|
*/
|
|
15845
|
-
const CREATE_FILE_NAME_FN$
|
|
16113
|
+
const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
15846
16114
|
const parts = [symbol, strategyName, exchangeName];
|
|
15847
16115
|
if (frameName) {
|
|
15848
16116
|
parts.push(frameName);
|
|
@@ -15853,12 +16121,12 @@ const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
15853
16121
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
15854
16122
|
};
|
|
15855
16123
|
/** Maximum number of events to store in schedule reports */
|
|
15856
|
-
const MAX_EVENTS$
|
|
16124
|
+
const MAX_EVENTS$6 = 250;
|
|
15857
16125
|
/**
|
|
15858
16126
|
* Storage class for accumulating scheduled signal events per strategy.
|
|
15859
16127
|
* Maintains a chronological list of scheduled and cancelled events.
|
|
15860
16128
|
*/
|
|
15861
|
-
let ReportStorage$
|
|
16129
|
+
let ReportStorage$5 = class ReportStorage {
|
|
15862
16130
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
15863
16131
|
this.symbol = symbol;
|
|
15864
16132
|
this.strategyName = strategyName;
|
|
@@ -15889,7 +16157,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
15889
16157
|
totalExecuted: data.signal.totalExecuted,
|
|
15890
16158
|
});
|
|
15891
16159
|
// Trim queue if exceeded MAX_EVENTS
|
|
15892
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
16160
|
+
if (this._eventList.length > MAX_EVENTS$6) {
|
|
15893
16161
|
this._eventList.pop();
|
|
15894
16162
|
}
|
|
15895
16163
|
}
|
|
@@ -15919,7 +16187,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
15919
16187
|
};
|
|
15920
16188
|
this._eventList.unshift(newEvent);
|
|
15921
16189
|
// Trim queue if exceeded MAX_EVENTS
|
|
15922
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
16190
|
+
if (this._eventList.length > MAX_EVENTS$6) {
|
|
15923
16191
|
this._eventList.pop();
|
|
15924
16192
|
}
|
|
15925
16193
|
}
|
|
@@ -15952,7 +16220,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
15952
16220
|
};
|
|
15953
16221
|
this._eventList.unshift(newEvent);
|
|
15954
16222
|
// Trim queue if exceeded MAX_EVENTS
|
|
15955
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
16223
|
+
if (this._eventList.length > MAX_EVENTS$6) {
|
|
15956
16224
|
this._eventList.pop();
|
|
15957
16225
|
}
|
|
15958
16226
|
}
|
|
@@ -16059,7 +16327,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
16059
16327
|
async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
|
|
16060
16328
|
const markdown = await this.getReport(strategyName, columns);
|
|
16061
16329
|
const timestamp = Date.now();
|
|
16062
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
16330
|
+
const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
16063
16331
|
await Markdown.writeData("schedule", markdown, {
|
|
16064
16332
|
path,
|
|
16065
16333
|
file: filename,
|
|
@@ -16100,7 +16368,7 @@ class ScheduleMarkdownService {
|
|
|
16100
16368
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
16101
16369
|
* Each combination gets its own isolated storage instance.
|
|
16102
16370
|
*/
|
|
16103
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
16371
|
+
this.getStorage = 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));
|
|
16104
16372
|
/**
|
|
16105
16373
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
16106
16374
|
* Protected against multiple subscriptions.
|
|
@@ -16303,7 +16571,7 @@ class ScheduleMarkdownService {
|
|
|
16303
16571
|
payload,
|
|
16304
16572
|
});
|
|
16305
16573
|
if (payload) {
|
|
16306
|
-
const key = CREATE_KEY_FN$
|
|
16574
|
+
const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
16307
16575
|
this.getStorage.clear(key);
|
|
16308
16576
|
}
|
|
16309
16577
|
else {
|
|
@@ -16323,7 +16591,7 @@ class ScheduleMarkdownService {
|
|
|
16323
16591
|
* @param backtest - Whether running in backtest mode
|
|
16324
16592
|
* @returns Unique string key for memoization
|
|
16325
16593
|
*/
|
|
16326
|
-
const CREATE_KEY_FN$
|
|
16594
|
+
const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
16327
16595
|
const parts = [symbol, strategyName, exchangeName];
|
|
16328
16596
|
if (frameName)
|
|
16329
16597
|
parts.push(frameName);
|
|
@@ -16340,7 +16608,7 @@ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
16340
16608
|
* @param timestamp - Unix timestamp in milliseconds
|
|
16341
16609
|
* @returns Filename string
|
|
16342
16610
|
*/
|
|
16343
|
-
const CREATE_FILE_NAME_FN$
|
|
16611
|
+
const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
16344
16612
|
const parts = [symbol, strategyName, exchangeName];
|
|
16345
16613
|
if (frameName) {
|
|
16346
16614
|
parts.push(frameName);
|
|
@@ -16360,7 +16628,7 @@ function percentile(sortedArray, p) {
|
|
|
16360
16628
|
return sortedArray[Math.max(0, index)];
|
|
16361
16629
|
}
|
|
16362
16630
|
/** Maximum number of performance events to store per strategy */
|
|
16363
|
-
const MAX_EVENTS$
|
|
16631
|
+
const MAX_EVENTS$5 = 10000;
|
|
16364
16632
|
/**
|
|
16365
16633
|
* Storage class for accumulating performance metrics per strategy.
|
|
16366
16634
|
* Maintains a list of all performance events and provides aggregated statistics.
|
|
@@ -16382,7 +16650,7 @@ class PerformanceStorage {
|
|
|
16382
16650
|
addEvent(event) {
|
|
16383
16651
|
this._events.unshift(event);
|
|
16384
16652
|
// Trim queue if exceeded MAX_EVENTS (keep most recent)
|
|
16385
|
-
if (this._events.length > MAX_EVENTS$
|
|
16653
|
+
if (this._events.length > MAX_EVENTS$5) {
|
|
16386
16654
|
this._events.pop();
|
|
16387
16655
|
}
|
|
16388
16656
|
}
|
|
@@ -16524,7 +16792,7 @@ class PerformanceStorage {
|
|
|
16524
16792
|
async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
|
|
16525
16793
|
const markdown = await this.getReport(strategyName, columns);
|
|
16526
16794
|
const timestamp = Date.now();
|
|
16527
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
16795
|
+
const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
16528
16796
|
await Markdown.writeData("performance", markdown, {
|
|
16529
16797
|
path,
|
|
16530
16798
|
file: filename,
|
|
@@ -16571,7 +16839,7 @@ class PerformanceMarkdownService {
|
|
|
16571
16839
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
16572
16840
|
* Each combination gets its own isolated storage instance.
|
|
16573
16841
|
*/
|
|
16574
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
16842
|
+
this.getStorage = 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));
|
|
16575
16843
|
/**
|
|
16576
16844
|
* Subscribes to performance emitter to receive performance events.
|
|
16577
16845
|
* Protected against multiple subscriptions.
|
|
@@ -16738,7 +17006,7 @@ class PerformanceMarkdownService {
|
|
|
16738
17006
|
payload,
|
|
16739
17007
|
});
|
|
16740
17008
|
if (payload) {
|
|
16741
|
-
const key = CREATE_KEY_FN$
|
|
17009
|
+
const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
16742
17010
|
this.getStorage.clear(key);
|
|
16743
17011
|
}
|
|
16744
17012
|
else {
|
|
@@ -16752,7 +17020,7 @@ class PerformanceMarkdownService {
|
|
|
16752
17020
|
* Creates a filename for markdown report based on walker name.
|
|
16753
17021
|
* Filename format: "walkerName-timestamp.md"
|
|
16754
17022
|
*/
|
|
16755
|
-
const CREATE_FILE_NAME_FN$
|
|
17023
|
+
const CREATE_FILE_NAME_FN$5 = (walkerName, timestamp) => {
|
|
16756
17024
|
return `${walkerName}-${timestamp}.md`;
|
|
16757
17025
|
};
|
|
16758
17026
|
/**
|
|
@@ -16787,7 +17055,7 @@ function formatMetric(value) {
|
|
|
16787
17055
|
* Storage class for accumulating walker results.
|
|
16788
17056
|
* Maintains a list of all strategy results and provides methods to generate reports.
|
|
16789
17057
|
*/
|
|
16790
|
-
let ReportStorage$
|
|
17058
|
+
let ReportStorage$4 = class ReportStorage {
|
|
16791
17059
|
constructor(walkerName) {
|
|
16792
17060
|
this.walkerName = walkerName;
|
|
16793
17061
|
/** Walker metadata (set from first addResult call) */
|
|
@@ -16975,7 +17243,7 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
16975
17243
|
async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
16976
17244
|
const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
16977
17245
|
const timestamp = Date.now();
|
|
16978
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
17246
|
+
const filename = CREATE_FILE_NAME_FN$5(this.walkerName, timestamp);
|
|
16979
17247
|
await Markdown.writeData("walker", markdown, {
|
|
16980
17248
|
path,
|
|
16981
17249
|
file: filename,
|
|
@@ -17011,7 +17279,7 @@ class WalkerMarkdownService {
|
|
|
17011
17279
|
* Memoized function to get or create ReportStorage for a walker.
|
|
17012
17280
|
* Each walker gets its own isolated storage instance.
|
|
17013
17281
|
*/
|
|
17014
|
-
this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$
|
|
17282
|
+
this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$4(walkerName));
|
|
17015
17283
|
/**
|
|
17016
17284
|
* Subscribes to walker emitter to receive walker progress events.
|
|
17017
17285
|
* Protected against multiple subscriptions.
|
|
@@ -17208,7 +17476,7 @@ class WalkerMarkdownService {
|
|
|
17208
17476
|
* @param backtest - Whether running in backtest mode
|
|
17209
17477
|
* @returns Unique string key for memoization
|
|
17210
17478
|
*/
|
|
17211
|
-
const CREATE_KEY_FN$
|
|
17479
|
+
const CREATE_KEY_FN$a = (exchangeName, frameName, backtest) => {
|
|
17212
17480
|
const parts = [exchangeName];
|
|
17213
17481
|
if (frameName)
|
|
17214
17482
|
parts.push(frameName);
|
|
@@ -17219,7 +17487,7 @@ const CREATE_KEY_FN$9 = (exchangeName, frameName, backtest) => {
|
|
|
17219
17487
|
* Creates a filename for markdown report based on memoization key components.
|
|
17220
17488
|
* Filename format: "strategyName_exchangeName_frameName-timestamp.md"
|
|
17221
17489
|
*/
|
|
17222
|
-
const CREATE_FILE_NAME_FN$
|
|
17490
|
+
const CREATE_FILE_NAME_FN$4 = (strategyName, exchangeName, frameName, timestamp) => {
|
|
17223
17491
|
const parts = [strategyName, exchangeName];
|
|
17224
17492
|
if (frameName) {
|
|
17225
17493
|
parts.push(frameName);
|
|
@@ -17252,7 +17520,7 @@ function isUnsafe(value) {
|
|
|
17252
17520
|
return false;
|
|
17253
17521
|
}
|
|
17254
17522
|
/** Maximum number of signals to store per symbol in heatmap reports */
|
|
17255
|
-
const MAX_EVENTS$
|
|
17523
|
+
const MAX_EVENTS$4 = 250;
|
|
17256
17524
|
/**
|
|
17257
17525
|
* Storage class for accumulating closed signals per strategy and generating heatmap.
|
|
17258
17526
|
* Maintains symbol-level statistics and provides portfolio-wide metrics.
|
|
@@ -17278,7 +17546,7 @@ class HeatmapStorage {
|
|
|
17278
17546
|
const signals = this.symbolData.get(symbol);
|
|
17279
17547
|
signals.unshift(data);
|
|
17280
17548
|
// Trim queue if exceeded MAX_EVENTS per symbol
|
|
17281
|
-
if (signals.length > MAX_EVENTS$
|
|
17549
|
+
if (signals.length > MAX_EVENTS$4) {
|
|
17282
17550
|
signals.pop();
|
|
17283
17551
|
}
|
|
17284
17552
|
}
|
|
@@ -17529,7 +17797,7 @@ class HeatmapStorage {
|
|
|
17529
17797
|
async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
|
|
17530
17798
|
const markdown = await this.getReport(strategyName, columns);
|
|
17531
17799
|
const timestamp = Date.now();
|
|
17532
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
17800
|
+
const filename = CREATE_FILE_NAME_FN$4(strategyName, this.exchangeName, this.frameName, timestamp);
|
|
17533
17801
|
await Markdown.writeData("heat", markdown, {
|
|
17534
17802
|
path,
|
|
17535
17803
|
file: filename,
|
|
@@ -17575,7 +17843,7 @@ class HeatMarkdownService {
|
|
|
17575
17843
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
17576
17844
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
17577
17845
|
*/
|
|
17578
|
-
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
17846
|
+
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
17579
17847
|
/**
|
|
17580
17848
|
* Subscribes to signal emitter to receive tick events.
|
|
17581
17849
|
* Protected against multiple subscriptions.
|
|
@@ -17770,7 +18038,7 @@ class HeatMarkdownService {
|
|
|
17770
18038
|
payload,
|
|
17771
18039
|
});
|
|
17772
18040
|
if (payload) {
|
|
17773
|
-
const key = CREATE_KEY_FN$
|
|
18041
|
+
const key = CREATE_KEY_FN$a(payload.exchangeName, payload.frameName, payload.backtest);
|
|
17774
18042
|
this.getStorage.clear(key);
|
|
17775
18043
|
}
|
|
17776
18044
|
else {
|
|
@@ -18801,7 +19069,7 @@ class ClientPartial {
|
|
|
18801
19069
|
* @param backtest - Whether running in backtest mode
|
|
18802
19070
|
* @returns Unique string key for memoization
|
|
18803
19071
|
*/
|
|
18804
|
-
const CREATE_KEY_FN$
|
|
19072
|
+
const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
18805
19073
|
/**
|
|
18806
19074
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
18807
19075
|
*
|
|
@@ -18923,7 +19191,7 @@ class PartialConnectionService {
|
|
|
18923
19191
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
18924
19192
|
* Value: ClientPartial instance with logger and event emitters
|
|
18925
19193
|
*/
|
|
18926
|
-
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
19194
|
+
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
|
|
18927
19195
|
return new ClientPartial({
|
|
18928
19196
|
signalId,
|
|
18929
19197
|
logger: this.loggerService,
|
|
@@ -19013,7 +19281,7 @@ class PartialConnectionService {
|
|
|
19013
19281
|
const partial = this.getPartial(data.id, backtest);
|
|
19014
19282
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
19015
19283
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
19016
|
-
const key = CREATE_KEY_FN$
|
|
19284
|
+
const key = CREATE_KEY_FN$9(data.id, backtest);
|
|
19017
19285
|
this.getPartial.clear(key);
|
|
19018
19286
|
};
|
|
19019
19287
|
}
|
|
@@ -19029,7 +19297,7 @@ class PartialConnectionService {
|
|
|
19029
19297
|
* @param backtest - Whether running in backtest mode
|
|
19030
19298
|
* @returns Unique string key for memoization
|
|
19031
19299
|
*/
|
|
19032
|
-
const CREATE_KEY_FN$
|
|
19300
|
+
const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
19033
19301
|
const parts = [symbol, strategyName, exchangeName];
|
|
19034
19302
|
if (frameName)
|
|
19035
19303
|
parts.push(frameName);
|
|
@@ -19040,7 +19308,7 @@ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
19040
19308
|
* Creates a filename for markdown report based on memoization key components.
|
|
19041
19309
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
19042
19310
|
*/
|
|
19043
|
-
const CREATE_FILE_NAME_FN$
|
|
19311
|
+
const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
19044
19312
|
const parts = [symbol, strategyName, exchangeName];
|
|
19045
19313
|
if (frameName) {
|
|
19046
19314
|
parts.push(frameName);
|
|
@@ -19051,12 +19319,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
19051
19319
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
19052
19320
|
};
|
|
19053
19321
|
/** Maximum number of events to store in partial reports */
|
|
19054
|
-
const MAX_EVENTS$
|
|
19322
|
+
const MAX_EVENTS$3 = 250;
|
|
19055
19323
|
/**
|
|
19056
19324
|
* Storage class for accumulating partial profit/loss events per symbol-strategy pair.
|
|
19057
19325
|
* Maintains a chronological list of profit and loss level events.
|
|
19058
19326
|
*/
|
|
19059
|
-
let ReportStorage$
|
|
19327
|
+
let ReportStorage$3 = class ReportStorage {
|
|
19060
19328
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
19061
19329
|
this.symbol = symbol;
|
|
19062
19330
|
this.strategyName = strategyName;
|
|
@@ -19093,7 +19361,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
19093
19361
|
backtest,
|
|
19094
19362
|
});
|
|
19095
19363
|
// Trim queue if exceeded MAX_EVENTS
|
|
19096
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19364
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
19097
19365
|
this._eventList.pop();
|
|
19098
19366
|
}
|
|
19099
19367
|
}
|
|
@@ -19125,7 +19393,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
19125
19393
|
backtest,
|
|
19126
19394
|
});
|
|
19127
19395
|
// Trim queue if exceeded MAX_EVENTS
|
|
19128
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19396
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
19129
19397
|
this._eventList.pop();
|
|
19130
19398
|
}
|
|
19131
19399
|
}
|
|
@@ -19201,7 +19469,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
19201
19469
|
async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
|
|
19202
19470
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
19203
19471
|
const timestamp = Date.now();
|
|
19204
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
19472
|
+
const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
19205
19473
|
await Markdown.writeData("partial", markdown, {
|
|
19206
19474
|
path,
|
|
19207
19475
|
file: filename,
|
|
@@ -19242,7 +19510,7 @@ class PartialMarkdownService {
|
|
|
19242
19510
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
19243
19511
|
* Each combination gets its own isolated storage instance.
|
|
19244
19512
|
*/
|
|
19245
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
19513
|
+
this.getStorage = 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));
|
|
19246
19514
|
/**
|
|
19247
19515
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
19248
19516
|
* Protected against multiple subscriptions.
|
|
@@ -19452,7 +19720,7 @@ class PartialMarkdownService {
|
|
|
19452
19720
|
payload,
|
|
19453
19721
|
});
|
|
19454
19722
|
if (payload) {
|
|
19455
|
-
const key = CREATE_KEY_FN$
|
|
19723
|
+
const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
19456
19724
|
this.getStorage.clear(key);
|
|
19457
19725
|
}
|
|
19458
19726
|
else {
|
|
@@ -19468,7 +19736,7 @@ class PartialMarkdownService {
|
|
|
19468
19736
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
19469
19737
|
* @returns Unique string key for memoization
|
|
19470
19738
|
*/
|
|
19471
|
-
const CREATE_KEY_FN$
|
|
19739
|
+
const CREATE_KEY_FN$7 = (context) => {
|
|
19472
19740
|
const parts = [context.strategyName, context.exchangeName];
|
|
19473
19741
|
if (context.frameName)
|
|
19474
19742
|
parts.push(context.frameName);
|
|
@@ -19542,7 +19810,7 @@ class PartialGlobalService {
|
|
|
19542
19810
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
19543
19811
|
* @param methodName - Name of the calling method for error tracking
|
|
19544
19812
|
*/
|
|
19545
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
19813
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
|
|
19546
19814
|
this.loggerService.log("partialGlobalService validate", {
|
|
19547
19815
|
context,
|
|
19548
19816
|
methodName,
|
|
@@ -19997,7 +20265,7 @@ class ClientBreakeven {
|
|
|
19997
20265
|
* @param backtest - Whether running in backtest mode
|
|
19998
20266
|
* @returns Unique string key for memoization
|
|
19999
20267
|
*/
|
|
20000
|
-
const CREATE_KEY_FN$
|
|
20268
|
+
const CREATE_KEY_FN$6 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
20001
20269
|
/**
|
|
20002
20270
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
20003
20271
|
*
|
|
@@ -20083,7 +20351,7 @@ class BreakevenConnectionService {
|
|
|
20083
20351
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
20084
20352
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
20085
20353
|
*/
|
|
20086
|
-
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
20354
|
+
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$6(signalId, backtest), (signalId, backtest) => {
|
|
20087
20355
|
return new ClientBreakeven({
|
|
20088
20356
|
signalId,
|
|
20089
20357
|
logger: this.loggerService,
|
|
@@ -20144,7 +20412,7 @@ class BreakevenConnectionService {
|
|
|
20144
20412
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
20145
20413
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
20146
20414
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
20147
|
-
const key = CREATE_KEY_FN$
|
|
20415
|
+
const key = CREATE_KEY_FN$6(data.id, backtest);
|
|
20148
20416
|
this.getBreakeven.clear(key);
|
|
20149
20417
|
};
|
|
20150
20418
|
}
|
|
@@ -20160,7 +20428,7 @@ class BreakevenConnectionService {
|
|
|
20160
20428
|
* @param backtest - Whether running in backtest mode
|
|
20161
20429
|
* @returns Unique string key for memoization
|
|
20162
20430
|
*/
|
|
20163
|
-
const CREATE_KEY_FN$
|
|
20431
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20164
20432
|
const parts = [symbol, strategyName, exchangeName];
|
|
20165
20433
|
if (frameName)
|
|
20166
20434
|
parts.push(frameName);
|
|
@@ -20171,7 +20439,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
20171
20439
|
* Creates a filename for markdown report based on memoization key components.
|
|
20172
20440
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
20173
20441
|
*/
|
|
20174
|
-
const CREATE_FILE_NAME_FN$
|
|
20442
|
+
const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
20175
20443
|
const parts = [symbol, strategyName, exchangeName];
|
|
20176
20444
|
if (frameName) {
|
|
20177
20445
|
parts.push(frameName);
|
|
@@ -20182,12 +20450,12 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
20182
20450
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
20183
20451
|
};
|
|
20184
20452
|
/** Maximum number of events to store in breakeven reports */
|
|
20185
|
-
const MAX_EVENTS$
|
|
20453
|
+
const MAX_EVENTS$2 = 250;
|
|
20186
20454
|
/**
|
|
20187
20455
|
* Storage class for accumulating breakeven events per symbol-strategy pair.
|
|
20188
20456
|
* Maintains a chronological list of breakeven events.
|
|
20189
20457
|
*/
|
|
20190
|
-
let ReportStorage$
|
|
20458
|
+
let ReportStorage$2 = class ReportStorage {
|
|
20191
20459
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
20192
20460
|
this.symbol = symbol;
|
|
20193
20461
|
this.strategyName = strategyName;
|
|
@@ -20222,7 +20490,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
20222
20490
|
backtest,
|
|
20223
20491
|
});
|
|
20224
20492
|
// Trim queue if exceeded MAX_EVENTS
|
|
20225
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20493
|
+
if (this._eventList.length > MAX_EVENTS$2) {
|
|
20226
20494
|
this._eventList.pop();
|
|
20227
20495
|
}
|
|
20228
20496
|
}
|
|
@@ -20290,7 +20558,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
20290
20558
|
async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
|
|
20291
20559
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
20292
20560
|
const timestamp = Date.now();
|
|
20293
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
20561
|
+
const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
20294
20562
|
await Markdown.writeData("breakeven", markdown, {
|
|
20295
20563
|
path,
|
|
20296
20564
|
file: filename,
|
|
@@ -20331,7 +20599,7 @@ class BreakevenMarkdownService {
|
|
|
20331
20599
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20332
20600
|
* Each combination gets its own isolated storage instance.
|
|
20333
20601
|
*/
|
|
20334
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20602
|
+
this.getStorage = 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));
|
|
20335
20603
|
/**
|
|
20336
20604
|
* Subscribes to breakeven signal emitter to receive events.
|
|
20337
20605
|
* Protected against multiple subscriptions.
|
|
@@ -20520,7 +20788,7 @@ class BreakevenMarkdownService {
|
|
|
20520
20788
|
payload,
|
|
20521
20789
|
});
|
|
20522
20790
|
if (payload) {
|
|
20523
|
-
const key = CREATE_KEY_FN$
|
|
20791
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20524
20792
|
this.getStorage.clear(key);
|
|
20525
20793
|
}
|
|
20526
20794
|
else {
|
|
@@ -20536,7 +20804,7 @@ class BreakevenMarkdownService {
|
|
|
20536
20804
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
20537
20805
|
* @returns Unique string key for memoization
|
|
20538
20806
|
*/
|
|
20539
|
-
const CREATE_KEY_FN$
|
|
20807
|
+
const CREATE_KEY_FN$4 = (context) => {
|
|
20540
20808
|
const parts = [context.strategyName, context.exchangeName];
|
|
20541
20809
|
if (context.frameName)
|
|
20542
20810
|
parts.push(context.frameName);
|
|
@@ -20610,7 +20878,7 @@ class BreakevenGlobalService {
|
|
|
20610
20878
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
20611
20879
|
* @param methodName - Name of the calling method for error tracking
|
|
20612
20880
|
*/
|
|
20613
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
20881
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$4(context), (context, methodName) => {
|
|
20614
20882
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
20615
20883
|
context,
|
|
20616
20884
|
methodName,
|
|
@@ -20823,7 +21091,7 @@ class ConfigValidationService {
|
|
|
20823
21091
|
* @param backtest - Whether running in backtest mode
|
|
20824
21092
|
* @returns Unique string key for memoization
|
|
20825
21093
|
*/
|
|
20826
|
-
const CREATE_KEY_FN$
|
|
21094
|
+
const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20827
21095
|
const parts = [symbol, strategyName, exchangeName];
|
|
20828
21096
|
if (frameName)
|
|
20829
21097
|
parts.push(frameName);
|
|
@@ -20834,7 +21102,7 @@ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
20834
21102
|
* Creates a filename for markdown report based on memoization key components.
|
|
20835
21103
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
20836
21104
|
*/
|
|
20837
|
-
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
21105
|
+
const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
20838
21106
|
const parts = [symbol, strategyName, exchangeName];
|
|
20839
21107
|
if (frameName) {
|
|
20840
21108
|
parts.push(frameName);
|
|
@@ -20845,12 +21113,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
|
|
|
20845
21113
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
20846
21114
|
};
|
|
20847
21115
|
/** Maximum number of events to store in risk reports */
|
|
20848
|
-
const MAX_EVENTS = 250;
|
|
21116
|
+
const MAX_EVENTS$1 = 250;
|
|
20849
21117
|
/**
|
|
20850
21118
|
* Storage class for accumulating risk rejection events per symbol-strategy pair.
|
|
20851
21119
|
* Maintains a chronological list of rejected signals due to risk limits.
|
|
20852
21120
|
*/
|
|
20853
|
-
class ReportStorage {
|
|
21121
|
+
let ReportStorage$1 = class ReportStorage {
|
|
20854
21122
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
20855
21123
|
this.symbol = symbol;
|
|
20856
21124
|
this.strategyName = strategyName;
|
|
@@ -20867,7 +21135,7 @@ class ReportStorage {
|
|
|
20867
21135
|
addRejectionEvent(event) {
|
|
20868
21136
|
this._eventList.unshift(event);
|
|
20869
21137
|
// Trim queue if exceeded MAX_EVENTS
|
|
20870
|
-
if (this._eventList.length > MAX_EVENTS) {
|
|
21138
|
+
if (this._eventList.length > MAX_EVENTS$1) {
|
|
20871
21139
|
this._eventList.pop();
|
|
20872
21140
|
}
|
|
20873
21141
|
}
|
|
@@ -20951,7 +21219,7 @@ class ReportStorage {
|
|
|
20951
21219
|
async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
|
|
20952
21220
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
20953
21221
|
const timestamp = Date.now();
|
|
20954
|
-
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
21222
|
+
const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
20955
21223
|
await Markdown.writeData("risk", markdown, {
|
|
20956
21224
|
path,
|
|
20957
21225
|
file: filename,
|
|
@@ -20962,7 +21230,7 @@ class ReportStorage {
|
|
|
20962
21230
|
frameName: this.frameName
|
|
20963
21231
|
});
|
|
20964
21232
|
}
|
|
20965
|
-
}
|
|
21233
|
+
};
|
|
20966
21234
|
/**
|
|
20967
21235
|
* Service for generating and saving risk rejection markdown reports.
|
|
20968
21236
|
*
|
|
@@ -20992,7 +21260,7 @@ class RiskMarkdownService {
|
|
|
20992
21260
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20993
21261
|
* Each combination gets its own isolated storage instance.
|
|
20994
21262
|
*/
|
|
20995
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21263
|
+
this.getStorage = 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));
|
|
20996
21264
|
/**
|
|
20997
21265
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
20998
21266
|
* Protected against multiple subscriptions.
|
|
@@ -21181,7 +21449,7 @@ class RiskMarkdownService {
|
|
|
21181
21449
|
payload,
|
|
21182
21450
|
});
|
|
21183
21451
|
if (payload) {
|
|
21184
|
-
const key = CREATE_KEY_FN$
|
|
21452
|
+
const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21185
21453
|
this.getStorage.clear(key);
|
|
21186
21454
|
}
|
|
21187
21455
|
else {
|
|
@@ -21465,6 +21733,7 @@ class ReportDummy {
|
|
|
21465
21733
|
*/
|
|
21466
21734
|
const WILDCARD_TARGET = {
|
|
21467
21735
|
backtest: true,
|
|
21736
|
+
strategy: true,
|
|
21468
21737
|
breakeven: true,
|
|
21469
21738
|
heat: true,
|
|
21470
21739
|
live: true,
|
|
@@ -21510,7 +21779,7 @@ class ReportUtils {
|
|
|
21510
21779
|
*
|
|
21511
21780
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
21512
21781
|
*/
|
|
21513
|
-
this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET) => {
|
|
21782
|
+
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) => {
|
|
21514
21783
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
|
|
21515
21784
|
backtest: bt$1,
|
|
21516
21785
|
breakeven,
|
|
@@ -21521,6 +21790,7 @@ class ReportUtils {
|
|
|
21521
21790
|
risk,
|
|
21522
21791
|
schedule,
|
|
21523
21792
|
walker,
|
|
21793
|
+
strategy,
|
|
21524
21794
|
});
|
|
21525
21795
|
const unList = [];
|
|
21526
21796
|
if (bt$1) {
|
|
@@ -21550,6 +21820,9 @@ class ReportUtils {
|
|
|
21550
21820
|
if (walker) {
|
|
21551
21821
|
unList.push(bt.walkerReportService.subscribe());
|
|
21552
21822
|
}
|
|
21823
|
+
if (strategy) {
|
|
21824
|
+
unList.push(bt.scheduleReportService.subscribe());
|
|
21825
|
+
}
|
|
21553
21826
|
return compose(...unList.map((un) => () => void un()));
|
|
21554
21827
|
};
|
|
21555
21828
|
/**
|
|
@@ -21588,7 +21861,7 @@ class ReportUtils {
|
|
|
21588
21861
|
* Report.disable();
|
|
21589
21862
|
* ```
|
|
21590
21863
|
*/
|
|
21591
|
-
this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, } = WILDCARD_TARGET) => {
|
|
21864
|
+
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) => {
|
|
21592
21865
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
|
|
21593
21866
|
backtest: bt$1,
|
|
21594
21867
|
breakeven,
|
|
@@ -21599,6 +21872,7 @@ class ReportUtils {
|
|
|
21599
21872
|
risk,
|
|
21600
21873
|
schedule,
|
|
21601
21874
|
walker,
|
|
21875
|
+
strategy,
|
|
21602
21876
|
});
|
|
21603
21877
|
if (bt$1) {
|
|
21604
21878
|
bt.backtestReportService.unsubscribe();
|
|
@@ -21627,6 +21901,9 @@ class ReportUtils {
|
|
|
21627
21901
|
if (walker) {
|
|
21628
21902
|
bt.walkerReportService.unsubscribe();
|
|
21629
21903
|
}
|
|
21904
|
+
if (strategy) {
|
|
21905
|
+
bt.strategyReportService.unsubscribe();
|
|
21906
|
+
}
|
|
21630
21907
|
};
|
|
21631
21908
|
}
|
|
21632
21909
|
}
|
|
@@ -23062,22 +23339,1243 @@ class RiskReportService {
|
|
|
23062
23339
|
}
|
|
23063
23340
|
}
|
|
23064
23341
|
|
|
23065
|
-
|
|
23066
|
-
|
|
23067
|
-
|
|
23068
|
-
|
|
23069
|
-
|
|
23070
|
-
|
|
23071
|
-
|
|
23072
|
-
{
|
|
23073
|
-
|
|
23074
|
-
|
|
23075
|
-
|
|
23076
|
-
|
|
23077
|
-
|
|
23078
|
-
|
|
23079
|
-
|
|
23080
|
-
|
|
23342
|
+
/**
|
|
23343
|
+
* Extracts execution context timestamp for strategy event logging.
|
|
23344
|
+
*
|
|
23345
|
+
* @param self - The StrategyReportService instance to extract context from
|
|
23346
|
+
* @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
|
|
23347
|
+
* @internal
|
|
23348
|
+
*/
|
|
23349
|
+
const GET_EXECUTION_CONTEXT_FN$1 = (self) => {
|
|
23350
|
+
if (ExecutionContextService.hasContext()) {
|
|
23351
|
+
const { when } = self.executionContextService.context;
|
|
23352
|
+
return { when: when.toISOString() };
|
|
23353
|
+
}
|
|
23354
|
+
return {
|
|
23355
|
+
when: "",
|
|
23356
|
+
};
|
|
23357
|
+
};
|
|
23358
|
+
/**
|
|
23359
|
+
* Service for persisting strategy management events to JSON report files.
|
|
23360
|
+
*
|
|
23361
|
+
* Handles logging of strategy actions (cancel-scheduled, close-pending, partial-profit,
|
|
23362
|
+
* partial-loss, trailing-stop, trailing-take, breakeven) to persistent storage via
|
|
23363
|
+
* the Report class. Each event is written as a separate JSON record.
|
|
23364
|
+
*
|
|
23365
|
+
* Unlike StrategyMarkdownService which accumulates events in memory for markdown reports,
|
|
23366
|
+
* this service writes each event immediately to disk for audit trail purposes.
|
|
23367
|
+
*
|
|
23368
|
+
* Lifecycle:
|
|
23369
|
+
* - Call subscribe() to enable event logging
|
|
23370
|
+
* - Events are written via Report.writeData() with "strategy" category
|
|
23371
|
+
* - Call unsubscribe() to disable event logging
|
|
23372
|
+
*
|
|
23373
|
+
* @example
|
|
23374
|
+
* ```typescript
|
|
23375
|
+
* // Service is typically used internally by strategy management classes
|
|
23376
|
+
* strategyReportService.subscribe();
|
|
23377
|
+
*
|
|
23378
|
+
* // Events are logged automatically when strategy actions occur
|
|
23379
|
+
* await strategyReportService.partialProfit("BTCUSDT", 50, 50100, false, {
|
|
23380
|
+
* strategyName: "my-strategy",
|
|
23381
|
+
* exchangeName: "binance",
|
|
23382
|
+
* frameName: "1h"
|
|
23383
|
+
* });
|
|
23384
|
+
*
|
|
23385
|
+
* strategyReportService.unsubscribe();
|
|
23386
|
+
* ```
|
|
23387
|
+
*
|
|
23388
|
+
* @see StrategyMarkdownService for in-memory event accumulation and markdown report generation
|
|
23389
|
+
* @see Report for the underlying persistence mechanism
|
|
23390
|
+
*/
|
|
23391
|
+
class StrategyReportService {
|
|
23392
|
+
constructor() {
|
|
23393
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
23394
|
+
this.executionContextService = inject(TYPES.executionContextService);
|
|
23395
|
+
this.strategyCoreService = inject(TYPES.strategyCoreService);
|
|
23396
|
+
/**
|
|
23397
|
+
* Logs a cancel-scheduled event when a scheduled signal is cancelled.
|
|
23398
|
+
*
|
|
23399
|
+
* Retrieves the scheduled signal from StrategyCoreService and writes
|
|
23400
|
+
* the cancellation event to the report file.
|
|
23401
|
+
*
|
|
23402
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23403
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23404
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23405
|
+
* @param cancelId - Optional identifier for the cancellation reason
|
|
23406
|
+
*/
|
|
23407
|
+
this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
|
|
23408
|
+
this.loggerService.log("strategyReportService cancelScheduled", {
|
|
23409
|
+
symbol,
|
|
23410
|
+
isBacktest,
|
|
23411
|
+
cancelId,
|
|
23412
|
+
});
|
|
23413
|
+
if (!this.subscribe.hasValue()) {
|
|
23414
|
+
return;
|
|
23415
|
+
}
|
|
23416
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23417
|
+
const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
|
|
23418
|
+
exchangeName: context.exchangeName,
|
|
23419
|
+
strategyName: context.strategyName,
|
|
23420
|
+
frameName: context.frameName,
|
|
23421
|
+
});
|
|
23422
|
+
if (!scheduledRow) {
|
|
23423
|
+
return;
|
|
23424
|
+
}
|
|
23425
|
+
await Report.writeData("strategy", {
|
|
23426
|
+
action: "cancel-scheduled",
|
|
23427
|
+
cancelId,
|
|
23428
|
+
symbol,
|
|
23429
|
+
createdAt,
|
|
23430
|
+
}, {
|
|
23431
|
+
signalId: scheduledRow.id,
|
|
23432
|
+
exchangeName: context.exchangeName,
|
|
23433
|
+
frameName: context.frameName,
|
|
23434
|
+
strategyName: context.strategyName,
|
|
23435
|
+
symbol,
|
|
23436
|
+
walkerName: "",
|
|
23437
|
+
});
|
|
23438
|
+
};
|
|
23439
|
+
/**
|
|
23440
|
+
* Logs a close-pending event when a pending signal is closed.
|
|
23441
|
+
*
|
|
23442
|
+
* Retrieves the pending signal from StrategyCoreService and writes
|
|
23443
|
+
* the close event to the report file.
|
|
23444
|
+
*
|
|
23445
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23446
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23447
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23448
|
+
* @param closeId - Optional identifier for the close reason
|
|
23449
|
+
*/
|
|
23450
|
+
this.closePending = async (symbol, isBacktest, context, closeId) => {
|
|
23451
|
+
this.loggerService.log("strategyReportService closePending", {
|
|
23452
|
+
symbol,
|
|
23453
|
+
isBacktest,
|
|
23454
|
+
closeId,
|
|
23455
|
+
});
|
|
23456
|
+
if (!this.subscribe.hasValue()) {
|
|
23457
|
+
return;
|
|
23458
|
+
}
|
|
23459
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23460
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23461
|
+
exchangeName: context.exchangeName,
|
|
23462
|
+
strategyName: context.strategyName,
|
|
23463
|
+
frameName: context.frameName,
|
|
23464
|
+
});
|
|
23465
|
+
if (!pendingRow) {
|
|
23466
|
+
return;
|
|
23467
|
+
}
|
|
23468
|
+
await Report.writeData("strategy", {
|
|
23469
|
+
action: "close-pending",
|
|
23470
|
+
closeId,
|
|
23471
|
+
symbol,
|
|
23472
|
+
createdAt,
|
|
23473
|
+
}, {
|
|
23474
|
+
signalId: pendingRow.id,
|
|
23475
|
+
exchangeName: context.exchangeName,
|
|
23476
|
+
frameName: context.frameName,
|
|
23477
|
+
strategyName: context.strategyName,
|
|
23478
|
+
symbol,
|
|
23479
|
+
walkerName: "",
|
|
23480
|
+
});
|
|
23481
|
+
};
|
|
23482
|
+
/**
|
|
23483
|
+
* Logs a partial-profit event when a portion of the position is closed at profit.
|
|
23484
|
+
*
|
|
23485
|
+
* Records the percentage closed and current price when partial profit-taking occurs.
|
|
23486
|
+
*
|
|
23487
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23488
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
23489
|
+
* @param currentPrice - Current market price at time of partial close
|
|
23490
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23491
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23492
|
+
*/
|
|
23493
|
+
this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
|
|
23494
|
+
this.loggerService.log("strategyReportService partialProfit", {
|
|
23495
|
+
symbol,
|
|
23496
|
+
percentToClose,
|
|
23497
|
+
currentPrice,
|
|
23498
|
+
isBacktest,
|
|
23499
|
+
});
|
|
23500
|
+
if (!this.subscribe.hasValue()) {
|
|
23501
|
+
return;
|
|
23502
|
+
}
|
|
23503
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23504
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23505
|
+
exchangeName: context.exchangeName,
|
|
23506
|
+
strategyName: context.strategyName,
|
|
23507
|
+
frameName: context.frameName,
|
|
23508
|
+
});
|
|
23509
|
+
if (!pendingRow) {
|
|
23510
|
+
return;
|
|
23511
|
+
}
|
|
23512
|
+
await Report.writeData("strategy", {
|
|
23513
|
+
action: "partial-profit",
|
|
23514
|
+
percentToClose,
|
|
23515
|
+
currentPrice,
|
|
23516
|
+
symbol,
|
|
23517
|
+
createdAt,
|
|
23518
|
+
}, {
|
|
23519
|
+
signalId: pendingRow.id,
|
|
23520
|
+
exchangeName: context.exchangeName,
|
|
23521
|
+
frameName: context.frameName,
|
|
23522
|
+
strategyName: context.strategyName,
|
|
23523
|
+
symbol,
|
|
23524
|
+
walkerName: "",
|
|
23525
|
+
});
|
|
23526
|
+
};
|
|
23527
|
+
/**
|
|
23528
|
+
* Logs a partial-loss event when a portion of the position is closed at loss.
|
|
23529
|
+
*
|
|
23530
|
+
* Records the percentage closed and current price when partial loss-cutting occurs.
|
|
23531
|
+
*
|
|
23532
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23533
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
23534
|
+
* @param currentPrice - Current market price at time of partial close
|
|
23535
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23536
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23537
|
+
*/
|
|
23538
|
+
this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
|
|
23539
|
+
this.loggerService.log("strategyReportService partialLoss", {
|
|
23540
|
+
symbol,
|
|
23541
|
+
percentToClose,
|
|
23542
|
+
currentPrice,
|
|
23543
|
+
isBacktest,
|
|
23544
|
+
});
|
|
23545
|
+
if (!this.subscribe.hasValue()) {
|
|
23546
|
+
return;
|
|
23547
|
+
}
|
|
23548
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23549
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23550
|
+
exchangeName: context.exchangeName,
|
|
23551
|
+
strategyName: context.strategyName,
|
|
23552
|
+
frameName: context.frameName,
|
|
23553
|
+
});
|
|
23554
|
+
if (!pendingRow) {
|
|
23555
|
+
return;
|
|
23556
|
+
}
|
|
23557
|
+
await Report.writeData("strategy", {
|
|
23558
|
+
action: "partial-loss",
|
|
23559
|
+
percentToClose,
|
|
23560
|
+
currentPrice,
|
|
23561
|
+
symbol,
|
|
23562
|
+
createdAt,
|
|
23563
|
+
}, {
|
|
23564
|
+
signalId: pendingRow.id,
|
|
23565
|
+
exchangeName: context.exchangeName,
|
|
23566
|
+
frameName: context.frameName,
|
|
23567
|
+
strategyName: context.strategyName,
|
|
23568
|
+
symbol,
|
|
23569
|
+
walkerName: "",
|
|
23570
|
+
});
|
|
23571
|
+
};
|
|
23572
|
+
/**
|
|
23573
|
+
* Logs a trailing-stop event when the stop-loss is adjusted.
|
|
23574
|
+
*
|
|
23575
|
+
* Records the percentage shift and current price when trailing stop moves.
|
|
23576
|
+
*
|
|
23577
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23578
|
+
* @param percentShift - Percentage the stop-loss was shifted
|
|
23579
|
+
* @param currentPrice - Current market price at time of adjustment
|
|
23580
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23581
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23582
|
+
*/
|
|
23583
|
+
this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
|
|
23584
|
+
this.loggerService.log("strategyReportService trailingStop", {
|
|
23585
|
+
symbol,
|
|
23586
|
+
percentShift,
|
|
23587
|
+
currentPrice,
|
|
23588
|
+
isBacktest,
|
|
23589
|
+
});
|
|
23590
|
+
if (!this.subscribe.hasValue()) {
|
|
23591
|
+
return;
|
|
23592
|
+
}
|
|
23593
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23594
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23595
|
+
exchangeName: context.exchangeName,
|
|
23596
|
+
strategyName: context.strategyName,
|
|
23597
|
+
frameName: context.frameName,
|
|
23598
|
+
});
|
|
23599
|
+
if (!pendingRow) {
|
|
23600
|
+
return;
|
|
23601
|
+
}
|
|
23602
|
+
await Report.writeData("strategy", {
|
|
23603
|
+
action: "trailing-stop",
|
|
23604
|
+
percentShift,
|
|
23605
|
+
currentPrice,
|
|
23606
|
+
symbol,
|
|
23607
|
+
createdAt,
|
|
23608
|
+
}, {
|
|
23609
|
+
signalId: pendingRow.id,
|
|
23610
|
+
exchangeName: context.exchangeName,
|
|
23611
|
+
frameName: context.frameName,
|
|
23612
|
+
strategyName: context.strategyName,
|
|
23613
|
+
symbol,
|
|
23614
|
+
walkerName: "",
|
|
23615
|
+
});
|
|
23616
|
+
};
|
|
23617
|
+
/**
|
|
23618
|
+
* Logs a trailing-take event when the take-profit is adjusted.
|
|
23619
|
+
*
|
|
23620
|
+
* Records the percentage shift and current price when trailing take-profit moves.
|
|
23621
|
+
*
|
|
23622
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23623
|
+
* @param percentShift - Percentage the take-profit was shifted
|
|
23624
|
+
* @param currentPrice - Current market price at time of adjustment
|
|
23625
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23626
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23627
|
+
*/
|
|
23628
|
+
this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
|
|
23629
|
+
this.loggerService.log("strategyReportService trailingTake", {
|
|
23630
|
+
symbol,
|
|
23631
|
+
percentShift,
|
|
23632
|
+
currentPrice,
|
|
23633
|
+
isBacktest,
|
|
23634
|
+
});
|
|
23635
|
+
if (!this.subscribe.hasValue()) {
|
|
23636
|
+
return;
|
|
23637
|
+
}
|
|
23638
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23639
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23640
|
+
exchangeName: context.exchangeName,
|
|
23641
|
+
strategyName: context.strategyName,
|
|
23642
|
+
frameName: context.frameName,
|
|
23643
|
+
});
|
|
23644
|
+
if (!pendingRow) {
|
|
23645
|
+
return;
|
|
23646
|
+
}
|
|
23647
|
+
await Report.writeData("strategy", {
|
|
23648
|
+
action: "trailing-take",
|
|
23649
|
+
percentShift,
|
|
23650
|
+
currentPrice,
|
|
23651
|
+
symbol,
|
|
23652
|
+
createdAt,
|
|
23653
|
+
}, {
|
|
23654
|
+
signalId: pendingRow.id,
|
|
23655
|
+
exchangeName: context.exchangeName,
|
|
23656
|
+
frameName: context.frameName,
|
|
23657
|
+
strategyName: context.strategyName,
|
|
23658
|
+
symbol,
|
|
23659
|
+
walkerName: "",
|
|
23660
|
+
});
|
|
23661
|
+
};
|
|
23662
|
+
/**
|
|
23663
|
+
* Logs a breakeven event when the stop-loss is moved to entry price.
|
|
23664
|
+
*
|
|
23665
|
+
* Records the current price when breakeven protection is activated.
|
|
23666
|
+
*
|
|
23667
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23668
|
+
* @param currentPrice - Current market price at time of breakeven activation
|
|
23669
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
23670
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
23671
|
+
*/
|
|
23672
|
+
this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
|
|
23673
|
+
this.loggerService.log("strategyReportService breakeven", {
|
|
23674
|
+
symbol,
|
|
23675
|
+
currentPrice,
|
|
23676
|
+
isBacktest,
|
|
23677
|
+
});
|
|
23678
|
+
if (!this.subscribe.hasValue()) {
|
|
23679
|
+
return;
|
|
23680
|
+
}
|
|
23681
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN$1(this);
|
|
23682
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
23683
|
+
exchangeName: context.exchangeName,
|
|
23684
|
+
strategyName: context.strategyName,
|
|
23685
|
+
frameName: context.frameName,
|
|
23686
|
+
});
|
|
23687
|
+
if (!pendingRow) {
|
|
23688
|
+
return;
|
|
23689
|
+
}
|
|
23690
|
+
await Report.writeData("strategy", {
|
|
23691
|
+
action: "breakeven",
|
|
23692
|
+
currentPrice,
|
|
23693
|
+
symbol,
|
|
23694
|
+
createdAt,
|
|
23695
|
+
}, {
|
|
23696
|
+
signalId: pendingRow.id,
|
|
23697
|
+
exchangeName: context.exchangeName,
|
|
23698
|
+
frameName: context.frameName,
|
|
23699
|
+
strategyName: context.strategyName,
|
|
23700
|
+
symbol,
|
|
23701
|
+
walkerName: "",
|
|
23702
|
+
});
|
|
23703
|
+
};
|
|
23704
|
+
/**
|
|
23705
|
+
* Initializes the service for event logging.
|
|
23706
|
+
*
|
|
23707
|
+
* Must be called before any events can be logged. Uses singleshot pattern
|
|
23708
|
+
* to ensure only one subscription exists at a time.
|
|
23709
|
+
*
|
|
23710
|
+
* @returns Cleanup function that clears the subscription when called
|
|
23711
|
+
*/
|
|
23712
|
+
this.subscribe = singleshot(() => {
|
|
23713
|
+
this.loggerService.log("strategyReportService subscribe");
|
|
23714
|
+
const unCancelSchedule = strategyCommitSubject
|
|
23715
|
+
.filter(({ action }) => action === "cancel-scheduled")
|
|
23716
|
+
.connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
|
|
23717
|
+
exchangeName: event.exchangeName,
|
|
23718
|
+
frameName: event.frameName,
|
|
23719
|
+
strategyName: event.strategyName,
|
|
23720
|
+
}, event.cancelId));
|
|
23721
|
+
const unClosePending = strategyCommitSubject
|
|
23722
|
+
.filter(({ action }) => action === "close-pending")
|
|
23723
|
+
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
23724
|
+
exchangeName: event.exchangeName,
|
|
23725
|
+
frameName: event.frameName,
|
|
23726
|
+
strategyName: event.strategyName,
|
|
23727
|
+
}, event.closeId));
|
|
23728
|
+
const unPartialProfit = strategyCommitSubject
|
|
23729
|
+
.filter(({ action }) => action === "partial-profit")
|
|
23730
|
+
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
23731
|
+
exchangeName: event.exchangeName,
|
|
23732
|
+
frameName: event.frameName,
|
|
23733
|
+
strategyName: event.strategyName,
|
|
23734
|
+
}));
|
|
23735
|
+
const unPartialLoss = strategyCommitSubject
|
|
23736
|
+
.filter(({ action }) => action === "partial-loss")
|
|
23737
|
+
.connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
23738
|
+
exchangeName: event.exchangeName,
|
|
23739
|
+
frameName: event.frameName,
|
|
23740
|
+
strategyName: event.strategyName,
|
|
23741
|
+
}));
|
|
23742
|
+
const unTrailingStop = strategyCommitSubject
|
|
23743
|
+
.filter(({ action }) => action === "trailing-stop")
|
|
23744
|
+
.connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
23745
|
+
exchangeName: event.exchangeName,
|
|
23746
|
+
frameName: event.frameName,
|
|
23747
|
+
strategyName: event.strategyName,
|
|
23748
|
+
}));
|
|
23749
|
+
const unTrailingTake = strategyCommitSubject
|
|
23750
|
+
.filter(({ action }) => action === "trailing-take")
|
|
23751
|
+
.connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
23752
|
+
exchangeName: event.exchangeName,
|
|
23753
|
+
frameName: event.frameName,
|
|
23754
|
+
strategyName: event.strategyName,
|
|
23755
|
+
}));
|
|
23756
|
+
const unBreakeven = strategyCommitSubject
|
|
23757
|
+
.filter(({ action }) => action === "breakeven")
|
|
23758
|
+
.connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
|
|
23759
|
+
exchangeName: event.exchangeName,
|
|
23760
|
+
frameName: event.frameName,
|
|
23761
|
+
strategyName: event.strategyName,
|
|
23762
|
+
}));
|
|
23763
|
+
const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
|
|
23764
|
+
return () => {
|
|
23765
|
+
disposeFn();
|
|
23766
|
+
this.subscribe.clear();
|
|
23767
|
+
};
|
|
23768
|
+
});
|
|
23769
|
+
/**
|
|
23770
|
+
* Stops event logging and cleans up the subscription.
|
|
23771
|
+
*
|
|
23772
|
+
* Safe to call multiple times - only clears if subscription exists.
|
|
23773
|
+
*/
|
|
23774
|
+
this.unsubscribe = async () => {
|
|
23775
|
+
this.loggerService.log("strategyReportService unsubscribe");
|
|
23776
|
+
if (this.subscribe.hasValue()) {
|
|
23777
|
+
const lastSubscription = this.subscribe();
|
|
23778
|
+
lastSubscription();
|
|
23779
|
+
}
|
|
23780
|
+
};
|
|
23781
|
+
}
|
|
23782
|
+
}
|
|
23783
|
+
|
|
23784
|
+
/**
|
|
23785
|
+
* Extracts execution context timestamp for strategy event logging.
|
|
23786
|
+
*
|
|
23787
|
+
* @param self - The StrategyMarkdownService instance to extract context from
|
|
23788
|
+
* @returns Object containing ISO 8601 formatted timestamp, or empty string if no context
|
|
23789
|
+
* @internal
|
|
23790
|
+
*/
|
|
23791
|
+
const GET_EXECUTION_CONTEXT_FN = (self) => {
|
|
23792
|
+
if (ExecutionContextService.hasContext()) {
|
|
23793
|
+
const { when } = self.executionContextService.context;
|
|
23794
|
+
return { when: when.toISOString() };
|
|
23795
|
+
}
|
|
23796
|
+
return {
|
|
23797
|
+
when: "",
|
|
23798
|
+
};
|
|
23799
|
+
};
|
|
23800
|
+
/**
|
|
23801
|
+
* Creates a unique key for memoizing ReportStorage instances.
|
|
23802
|
+
*
|
|
23803
|
+
* Key format: `{symbol}:{strategyName}:{exchangeName}[:{frameName}]:{backtest|live}`
|
|
23804
|
+
*
|
|
23805
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23806
|
+
* @param strategyName - Name of the trading strategy
|
|
23807
|
+
* @param exchangeName - Name of the exchange
|
|
23808
|
+
* @param frameName - Timeframe name (optional, included if present)
|
|
23809
|
+
* @param backtest - Whether this is backtest or live mode
|
|
23810
|
+
* @returns Colon-separated key string for memoization
|
|
23811
|
+
* @internal
|
|
23812
|
+
*/
|
|
23813
|
+
const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
23814
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
23815
|
+
if (frameName)
|
|
23816
|
+
parts.push(frameName);
|
|
23817
|
+
parts.push(backtest ? "backtest" : "live");
|
|
23818
|
+
return parts.join(":");
|
|
23819
|
+
};
|
|
23820
|
+
/**
|
|
23821
|
+
* Creates a filename for markdown report output.
|
|
23822
|
+
*
|
|
23823
|
+
* Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
|
|
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 (indicates backtest mode if present)
|
|
23829
|
+
* @param timestamp - Unix timestamp in milliseconds for uniqueness
|
|
23830
|
+
* @returns Underscore-separated filename with .md extension
|
|
23831
|
+
* @internal
|
|
23832
|
+
*/
|
|
23833
|
+
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
23834
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
23835
|
+
if (frameName) {
|
|
23836
|
+
parts.push(frameName);
|
|
23837
|
+
parts.push("backtest");
|
|
23838
|
+
}
|
|
23839
|
+
else
|
|
23840
|
+
parts.push("live");
|
|
23841
|
+
return `${parts.join("_")}-${timestamp}.md`;
|
|
23842
|
+
};
|
|
23843
|
+
/**
|
|
23844
|
+
* Maximum number of events to store per symbol-strategy pair.
|
|
23845
|
+
* Older events are discarded when this limit is exceeded.
|
|
23846
|
+
* @internal
|
|
23847
|
+
*/
|
|
23848
|
+
const MAX_EVENTS = 250;
|
|
23849
|
+
/**
|
|
23850
|
+
* In-memory storage for accumulating strategy events per symbol-strategy pair.
|
|
23851
|
+
*
|
|
23852
|
+
* Maintains a rolling window of the most recent events (up to MAX_EVENTS),
|
|
23853
|
+
* with newer events added to the front of the list. Provides methods to:
|
|
23854
|
+
* - Add new events (FIFO queue with max size)
|
|
23855
|
+
* - Retrieve aggregated statistics
|
|
23856
|
+
* - Generate markdown reports
|
|
23857
|
+
* - Dump reports to disk
|
|
23858
|
+
*
|
|
23859
|
+
* @internal
|
|
23860
|
+
*/
|
|
23861
|
+
class ReportStorage {
|
|
23862
|
+
/**
|
|
23863
|
+
* Creates a new ReportStorage instance.
|
|
23864
|
+
*
|
|
23865
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
23866
|
+
* @param strategyName - Name of the trading strategy
|
|
23867
|
+
* @param exchangeName - Name of the exchange
|
|
23868
|
+
* @param frameName - Timeframe name for backtest identification
|
|
23869
|
+
*/
|
|
23870
|
+
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
23871
|
+
this.symbol = symbol;
|
|
23872
|
+
this.strategyName = strategyName;
|
|
23873
|
+
this.exchangeName = exchangeName;
|
|
23874
|
+
this.frameName = frameName;
|
|
23875
|
+
this._eventList = [];
|
|
23876
|
+
}
|
|
23877
|
+
/**
|
|
23878
|
+
* Adds a new event to the storage.
|
|
23879
|
+
*
|
|
23880
|
+
* Events are added to the front of the list (most recent first).
|
|
23881
|
+
* If the list exceeds MAX_EVENTS, the oldest event is removed.
|
|
23882
|
+
*
|
|
23883
|
+
* @param event - The strategy event to store
|
|
23884
|
+
*/
|
|
23885
|
+
addEvent(event) {
|
|
23886
|
+
this._eventList.unshift(event);
|
|
23887
|
+
if (this._eventList.length > MAX_EVENTS) {
|
|
23888
|
+
this._eventList.pop();
|
|
23889
|
+
}
|
|
23890
|
+
}
|
|
23891
|
+
/**
|
|
23892
|
+
* Retrieves aggregated statistics from stored events.
|
|
23893
|
+
*
|
|
23894
|
+
* Calculates counts for each action type from the event list.
|
|
23895
|
+
*
|
|
23896
|
+
* @returns Promise resolving to StrategyStatisticsModel with event list and counts
|
|
23897
|
+
*/
|
|
23898
|
+
async getData() {
|
|
23899
|
+
if (this._eventList.length === 0) {
|
|
23900
|
+
return {
|
|
23901
|
+
eventList: [],
|
|
23902
|
+
totalEvents: 0,
|
|
23903
|
+
cancelScheduledCount: 0,
|
|
23904
|
+
closePendingCount: 0,
|
|
23905
|
+
partialProfitCount: 0,
|
|
23906
|
+
partialLossCount: 0,
|
|
23907
|
+
trailingStopCount: 0,
|
|
23908
|
+
trailingTakeCount: 0,
|
|
23909
|
+
breakevenCount: 0,
|
|
23910
|
+
};
|
|
23911
|
+
}
|
|
23912
|
+
return {
|
|
23913
|
+
eventList: this._eventList,
|
|
23914
|
+
totalEvents: this._eventList.length,
|
|
23915
|
+
cancelScheduledCount: this._eventList.filter(e => e.action === "cancel-scheduled").length,
|
|
23916
|
+
closePendingCount: this._eventList.filter(e => e.action === "close-pending").length,
|
|
23917
|
+
partialProfitCount: this._eventList.filter(e => e.action === "partial-profit").length,
|
|
23918
|
+
partialLossCount: this._eventList.filter(e => e.action === "partial-loss").length,
|
|
23919
|
+
trailingStopCount: this._eventList.filter(e => e.action === "trailing-stop").length,
|
|
23920
|
+
trailingTakeCount: this._eventList.filter(e => e.action === "trailing-take").length,
|
|
23921
|
+
breakevenCount: this._eventList.filter(e => e.action === "breakeven").length,
|
|
23922
|
+
};
|
|
23923
|
+
}
|
|
23924
|
+
/**
|
|
23925
|
+
* Generates a markdown report from stored events.
|
|
23926
|
+
*
|
|
23927
|
+
* Creates a formatted markdown document containing:
|
|
23928
|
+
* - Header with symbol and strategy name
|
|
23929
|
+
* - Table of all events with configurable columns
|
|
23930
|
+
* - Summary statistics with counts by action type
|
|
23931
|
+
*
|
|
23932
|
+
* @param symbol - Trading pair symbol for report header
|
|
23933
|
+
* @param strategyName - Strategy name for report header
|
|
23934
|
+
* @param columns - Column configuration for the event table
|
|
23935
|
+
* @returns Promise resolving to formatted markdown string
|
|
23936
|
+
*/
|
|
23937
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.strategy_columns) {
|
|
23938
|
+
const stats = await this.getData();
|
|
23939
|
+
if (stats.totalEvents === 0) {
|
|
23940
|
+
return [
|
|
23941
|
+
`# Strategy Report: ${symbol}:${strategyName}`,
|
|
23942
|
+
"",
|
|
23943
|
+
"No strategy events recorded yet."
|
|
23944
|
+
].join("\n");
|
|
23945
|
+
}
|
|
23946
|
+
const visibleColumns = [];
|
|
23947
|
+
for (const col of columns) {
|
|
23948
|
+
if (await col.isVisible()) {
|
|
23949
|
+
visibleColumns.push(col);
|
|
23950
|
+
}
|
|
23951
|
+
}
|
|
23952
|
+
const header = visibleColumns.map((col) => col.label);
|
|
23953
|
+
const separator = visibleColumns.map(() => "---");
|
|
23954
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
23955
|
+
const tableData = [header, separator, ...rows];
|
|
23956
|
+
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
23957
|
+
return [
|
|
23958
|
+
`# Strategy Report: ${symbol}:${strategyName}`,
|
|
23959
|
+
"",
|
|
23960
|
+
table,
|
|
23961
|
+
"",
|
|
23962
|
+
`**Total events:** ${stats.totalEvents}`,
|
|
23963
|
+
`- Cancel scheduled: ${stats.cancelScheduledCount}`,
|
|
23964
|
+
`- Close pending: ${stats.closePendingCount}`,
|
|
23965
|
+
`- Partial profit: ${stats.partialProfitCount}`,
|
|
23966
|
+
`- Partial loss: ${stats.partialLossCount}`,
|
|
23967
|
+
`- Trailing stop: ${stats.trailingStopCount}`,
|
|
23968
|
+
`- Trailing take: ${stats.trailingTakeCount}`,
|
|
23969
|
+
`- Breakeven: ${stats.breakevenCount}`,
|
|
23970
|
+
].join("\n");
|
|
23971
|
+
}
|
|
23972
|
+
/**
|
|
23973
|
+
* Generates and saves a markdown report to disk.
|
|
23974
|
+
*
|
|
23975
|
+
* Creates the output directory if it doesn't exist and writes
|
|
23976
|
+
* the report with a timestamped filename.
|
|
23977
|
+
*
|
|
23978
|
+
* @param symbol - Trading pair symbol for report
|
|
23979
|
+
* @param strategyName - Strategy name for report
|
|
23980
|
+
* @param path - Output directory path (default: "./dump/strategy")
|
|
23981
|
+
* @param columns - Column configuration for the event table
|
|
23982
|
+
*/
|
|
23983
|
+
async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
|
|
23984
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
23985
|
+
const timestamp = Date.now();
|
|
23986
|
+
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
23987
|
+
await Markdown.writeData("strategy", markdown, {
|
|
23988
|
+
path,
|
|
23989
|
+
file: filename,
|
|
23990
|
+
symbol: this.symbol,
|
|
23991
|
+
strategyName: this.strategyName,
|
|
23992
|
+
exchangeName: this.exchangeName,
|
|
23993
|
+
signalId: "",
|
|
23994
|
+
frameName: this.frameName
|
|
23995
|
+
});
|
|
23996
|
+
}
|
|
23997
|
+
}
|
|
23998
|
+
/**
|
|
23999
|
+
* Service for accumulating strategy management events and generating markdown reports.
|
|
24000
|
+
*
|
|
24001
|
+
* Collects strategy actions (cancel-scheduled, close-pending, partial-profit,
|
|
24002
|
+
* partial-loss, trailing-stop, trailing-take, breakeven) in memory and provides
|
|
24003
|
+
* methods to retrieve statistics, generate reports, and export to files.
|
|
24004
|
+
*
|
|
24005
|
+
* Unlike StrategyReportService which writes each event to disk immediately,
|
|
24006
|
+
* this service accumulates events in ReportStorage instances (max 250 per
|
|
24007
|
+
* symbol-strategy pair) for batch reporting.
|
|
24008
|
+
*
|
|
24009
|
+
* Features:
|
|
24010
|
+
* - In-memory event accumulation with memoized storage per symbol-strategy pair
|
|
24011
|
+
* - Statistical data extraction (event counts by action type)
|
|
24012
|
+
* - Markdown report generation with configurable columns
|
|
24013
|
+
* - File export with timestamped filenames
|
|
24014
|
+
* - Selective or full cache clearing
|
|
24015
|
+
*
|
|
24016
|
+
* Lifecycle:
|
|
24017
|
+
* - Call subscribe() to enable event collection
|
|
24018
|
+
* - Events are collected automatically via cancelScheduled, closePending, etc.
|
|
24019
|
+
* - Use getData(), getReport(), or dump() to retrieve accumulated data
|
|
24020
|
+
* - Call unsubscribe() to disable collection and clear all data
|
|
24021
|
+
*
|
|
24022
|
+
* @example
|
|
24023
|
+
* ```typescript
|
|
24024
|
+
* strategyMarkdownService.subscribe();
|
|
24025
|
+
*
|
|
24026
|
+
* // Events are collected automatically during strategy execution
|
|
24027
|
+
* // ...
|
|
24028
|
+
*
|
|
24029
|
+
* // Get statistics
|
|
24030
|
+
* const stats = await strategyMarkdownService.getData("BTCUSDT", "my-strategy", "binance", "1h", true);
|
|
24031
|
+
*
|
|
24032
|
+
* // Generate markdown report
|
|
24033
|
+
* const report = await strategyMarkdownService.getReport("BTCUSDT", "my-strategy", "binance", "1h", true);
|
|
24034
|
+
*
|
|
24035
|
+
* // Export to file
|
|
24036
|
+
* await strategyMarkdownService.dump("BTCUSDT", "my-strategy", "binance", "1h", true);
|
|
24037
|
+
*
|
|
24038
|
+
* strategyMarkdownService.unsubscribe();
|
|
24039
|
+
* ```
|
|
24040
|
+
*
|
|
24041
|
+
* @see StrategyReportService for immediate event persistence to JSON files
|
|
24042
|
+
* @see Strategy for the high-level utility class that wraps this service
|
|
24043
|
+
*/
|
|
24044
|
+
class StrategyMarkdownService {
|
|
24045
|
+
constructor() {
|
|
24046
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
24047
|
+
this.executionContextService = inject(TYPES.executionContextService);
|
|
24048
|
+
this.strategyCoreService = inject(TYPES.strategyCoreService);
|
|
24049
|
+
/**
|
|
24050
|
+
* Memoized factory for ReportStorage instances.
|
|
24051
|
+
*
|
|
24052
|
+
* Creates and caches ReportStorage per unique symbol-strategy-exchange-frame-backtest combination.
|
|
24053
|
+
* Uses CREATE_KEY_FN for cache key generation.
|
|
24054
|
+
*
|
|
24055
|
+
* @internal
|
|
24056
|
+
*/
|
|
24057
|
+
this.getStorage = 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));
|
|
24058
|
+
/**
|
|
24059
|
+
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
24060
|
+
*
|
|
24061
|
+
* Retrieves the scheduled signal from StrategyCoreService and stores
|
|
24062
|
+
* the cancellation event in the appropriate ReportStorage.
|
|
24063
|
+
*
|
|
24064
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24065
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24066
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24067
|
+
* @param cancelId - Optional identifier for the cancellation reason
|
|
24068
|
+
*/
|
|
24069
|
+
this.cancelScheduled = async (symbol, isBacktest, context, cancelId) => {
|
|
24070
|
+
this.loggerService.log("strategyMarkdownService cancelScheduled", {
|
|
24071
|
+
symbol,
|
|
24072
|
+
isBacktest,
|
|
24073
|
+
cancelId,
|
|
24074
|
+
});
|
|
24075
|
+
if (!this.subscribe.hasValue()) {
|
|
24076
|
+
return;
|
|
24077
|
+
}
|
|
24078
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24079
|
+
const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
|
|
24080
|
+
exchangeName: context.exchangeName,
|
|
24081
|
+
strategyName: context.strategyName,
|
|
24082
|
+
frameName: context.frameName,
|
|
24083
|
+
});
|
|
24084
|
+
if (!scheduledRow) {
|
|
24085
|
+
return;
|
|
24086
|
+
}
|
|
24087
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24088
|
+
storage.addEvent({
|
|
24089
|
+
timestamp: Date.now(),
|
|
24090
|
+
symbol,
|
|
24091
|
+
strategyName: context.strategyName,
|
|
24092
|
+
exchangeName: context.exchangeName,
|
|
24093
|
+
frameName: context.frameName,
|
|
24094
|
+
signalId: scheduledRow.id,
|
|
24095
|
+
action: "cancel-scheduled",
|
|
24096
|
+
cancelId,
|
|
24097
|
+
createdAt,
|
|
24098
|
+
backtest: isBacktest,
|
|
24099
|
+
});
|
|
24100
|
+
};
|
|
24101
|
+
/**
|
|
24102
|
+
* Records a close-pending event when a pending signal is closed.
|
|
24103
|
+
*
|
|
24104
|
+
* Retrieves the pending signal from StrategyCoreService and stores
|
|
24105
|
+
* the close event in the appropriate ReportStorage.
|
|
24106
|
+
*
|
|
24107
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24108
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24109
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24110
|
+
* @param closeId - Optional identifier for the close reason
|
|
24111
|
+
*/
|
|
24112
|
+
this.closePending = async (symbol, isBacktest, context, closeId) => {
|
|
24113
|
+
this.loggerService.log("strategyMarkdownService closePending", {
|
|
24114
|
+
symbol,
|
|
24115
|
+
isBacktest,
|
|
24116
|
+
closeId,
|
|
24117
|
+
});
|
|
24118
|
+
if (!this.subscribe.hasValue()) {
|
|
24119
|
+
return;
|
|
24120
|
+
}
|
|
24121
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24122
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24123
|
+
exchangeName: context.exchangeName,
|
|
24124
|
+
strategyName: context.strategyName,
|
|
24125
|
+
frameName: context.frameName,
|
|
24126
|
+
});
|
|
24127
|
+
if (!pendingRow) {
|
|
24128
|
+
return;
|
|
24129
|
+
}
|
|
24130
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24131
|
+
storage.addEvent({
|
|
24132
|
+
timestamp: Date.now(),
|
|
24133
|
+
symbol,
|
|
24134
|
+
strategyName: context.strategyName,
|
|
24135
|
+
exchangeName: context.exchangeName,
|
|
24136
|
+
frameName: context.frameName,
|
|
24137
|
+
signalId: pendingRow.id,
|
|
24138
|
+
action: "close-pending",
|
|
24139
|
+
closeId,
|
|
24140
|
+
createdAt,
|
|
24141
|
+
backtest: isBacktest,
|
|
24142
|
+
});
|
|
24143
|
+
};
|
|
24144
|
+
/**
|
|
24145
|
+
* Records a partial-profit event when a portion of the position is closed at profit.
|
|
24146
|
+
*
|
|
24147
|
+
* Stores the percentage closed and current price when partial profit-taking occurs.
|
|
24148
|
+
*
|
|
24149
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24150
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
24151
|
+
* @param currentPrice - Current market price at time of partial close
|
|
24152
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24153
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24154
|
+
*/
|
|
24155
|
+
this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
|
|
24156
|
+
this.loggerService.log("strategyMarkdownService partialProfit", {
|
|
24157
|
+
symbol,
|
|
24158
|
+
percentToClose,
|
|
24159
|
+
currentPrice,
|
|
24160
|
+
isBacktest,
|
|
24161
|
+
});
|
|
24162
|
+
if (!this.subscribe.hasValue()) {
|
|
24163
|
+
return;
|
|
24164
|
+
}
|
|
24165
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24166
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24167
|
+
exchangeName: context.exchangeName,
|
|
24168
|
+
strategyName: context.strategyName,
|
|
24169
|
+
frameName: context.frameName,
|
|
24170
|
+
});
|
|
24171
|
+
if (!pendingRow) {
|
|
24172
|
+
return;
|
|
24173
|
+
}
|
|
24174
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24175
|
+
storage.addEvent({
|
|
24176
|
+
timestamp: Date.now(),
|
|
24177
|
+
symbol,
|
|
24178
|
+
strategyName: context.strategyName,
|
|
24179
|
+
exchangeName: context.exchangeName,
|
|
24180
|
+
frameName: context.frameName,
|
|
24181
|
+
signalId: pendingRow.id,
|
|
24182
|
+
action: "partial-profit",
|
|
24183
|
+
percentToClose,
|
|
24184
|
+
currentPrice,
|
|
24185
|
+
createdAt,
|
|
24186
|
+
backtest: isBacktest,
|
|
24187
|
+
});
|
|
24188
|
+
};
|
|
24189
|
+
/**
|
|
24190
|
+
* Records a partial-loss event when a portion of the position is closed at loss.
|
|
24191
|
+
*
|
|
24192
|
+
* Stores the percentage closed and current price when partial loss-cutting occurs.
|
|
24193
|
+
*
|
|
24194
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24195
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
24196
|
+
* @param currentPrice - Current market price at time of partial close
|
|
24197
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24198
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24199
|
+
*/
|
|
24200
|
+
this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context) => {
|
|
24201
|
+
this.loggerService.log("strategyMarkdownService partialLoss", {
|
|
24202
|
+
symbol,
|
|
24203
|
+
percentToClose,
|
|
24204
|
+
currentPrice,
|
|
24205
|
+
isBacktest,
|
|
24206
|
+
});
|
|
24207
|
+
if (!this.subscribe.hasValue()) {
|
|
24208
|
+
return;
|
|
24209
|
+
}
|
|
24210
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24211
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24212
|
+
exchangeName: context.exchangeName,
|
|
24213
|
+
strategyName: context.strategyName,
|
|
24214
|
+
frameName: context.frameName,
|
|
24215
|
+
});
|
|
24216
|
+
if (!pendingRow) {
|
|
24217
|
+
return;
|
|
24218
|
+
}
|
|
24219
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24220
|
+
storage.addEvent({
|
|
24221
|
+
timestamp: Date.now(),
|
|
24222
|
+
symbol,
|
|
24223
|
+
strategyName: context.strategyName,
|
|
24224
|
+
exchangeName: context.exchangeName,
|
|
24225
|
+
frameName: context.frameName,
|
|
24226
|
+
signalId: pendingRow.id,
|
|
24227
|
+
action: "partial-loss",
|
|
24228
|
+
percentToClose,
|
|
24229
|
+
currentPrice,
|
|
24230
|
+
createdAt,
|
|
24231
|
+
backtest: isBacktest,
|
|
24232
|
+
});
|
|
24233
|
+
};
|
|
24234
|
+
/**
|
|
24235
|
+
* Records a trailing-stop event when the stop-loss is adjusted.
|
|
24236
|
+
*
|
|
24237
|
+
* Stores the percentage shift and current price when trailing stop moves.
|
|
24238
|
+
*
|
|
24239
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24240
|
+
* @param percentShift - Percentage the stop-loss was shifted
|
|
24241
|
+
* @param currentPrice - Current market price at time of adjustment
|
|
24242
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24243
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24244
|
+
*/
|
|
24245
|
+
this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context) => {
|
|
24246
|
+
this.loggerService.log("strategyMarkdownService trailingStop", {
|
|
24247
|
+
symbol,
|
|
24248
|
+
percentShift,
|
|
24249
|
+
currentPrice,
|
|
24250
|
+
isBacktest,
|
|
24251
|
+
});
|
|
24252
|
+
if (!this.subscribe.hasValue()) {
|
|
24253
|
+
return;
|
|
24254
|
+
}
|
|
24255
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24256
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24257
|
+
exchangeName: context.exchangeName,
|
|
24258
|
+
strategyName: context.strategyName,
|
|
24259
|
+
frameName: context.frameName,
|
|
24260
|
+
});
|
|
24261
|
+
if (!pendingRow) {
|
|
24262
|
+
return;
|
|
24263
|
+
}
|
|
24264
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24265
|
+
storage.addEvent({
|
|
24266
|
+
timestamp: Date.now(),
|
|
24267
|
+
symbol,
|
|
24268
|
+
strategyName: context.strategyName,
|
|
24269
|
+
exchangeName: context.exchangeName,
|
|
24270
|
+
frameName: context.frameName,
|
|
24271
|
+
signalId: pendingRow.id,
|
|
24272
|
+
action: "trailing-stop",
|
|
24273
|
+
percentShift,
|
|
24274
|
+
currentPrice,
|
|
24275
|
+
createdAt,
|
|
24276
|
+
backtest: isBacktest,
|
|
24277
|
+
});
|
|
24278
|
+
};
|
|
24279
|
+
/**
|
|
24280
|
+
* Records a trailing-take event when the take-profit is adjusted.
|
|
24281
|
+
*
|
|
24282
|
+
* Stores the percentage shift and current price when trailing take-profit moves.
|
|
24283
|
+
*
|
|
24284
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24285
|
+
* @param percentShift - Percentage the take-profit was shifted
|
|
24286
|
+
* @param currentPrice - Current market price at time of adjustment
|
|
24287
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24288
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24289
|
+
*/
|
|
24290
|
+
this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context) => {
|
|
24291
|
+
this.loggerService.log("strategyMarkdownService trailingTake", {
|
|
24292
|
+
symbol,
|
|
24293
|
+
percentShift,
|
|
24294
|
+
currentPrice,
|
|
24295
|
+
isBacktest,
|
|
24296
|
+
});
|
|
24297
|
+
if (!this.subscribe.hasValue()) {
|
|
24298
|
+
return;
|
|
24299
|
+
}
|
|
24300
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24301
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24302
|
+
exchangeName: context.exchangeName,
|
|
24303
|
+
strategyName: context.strategyName,
|
|
24304
|
+
frameName: context.frameName,
|
|
24305
|
+
});
|
|
24306
|
+
if (!pendingRow) {
|
|
24307
|
+
return;
|
|
24308
|
+
}
|
|
24309
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24310
|
+
storage.addEvent({
|
|
24311
|
+
timestamp: Date.now(),
|
|
24312
|
+
symbol,
|
|
24313
|
+
strategyName: context.strategyName,
|
|
24314
|
+
exchangeName: context.exchangeName,
|
|
24315
|
+
frameName: context.frameName,
|
|
24316
|
+
signalId: pendingRow.id,
|
|
24317
|
+
action: "trailing-take",
|
|
24318
|
+
percentShift,
|
|
24319
|
+
currentPrice,
|
|
24320
|
+
createdAt,
|
|
24321
|
+
backtest: isBacktest,
|
|
24322
|
+
});
|
|
24323
|
+
};
|
|
24324
|
+
/**
|
|
24325
|
+
* Records a breakeven event when the stop-loss is moved to entry price.
|
|
24326
|
+
*
|
|
24327
|
+
* Stores the current price when breakeven protection is activated.
|
|
24328
|
+
*
|
|
24329
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24330
|
+
* @param currentPrice - Current market price at time of breakeven activation
|
|
24331
|
+
* @param isBacktest - Whether this is a backtest or live trading event
|
|
24332
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
24333
|
+
*/
|
|
24334
|
+
this.breakeven = async (symbol, currentPrice, isBacktest, context) => {
|
|
24335
|
+
this.loggerService.log("strategyMarkdownService breakeven", {
|
|
24336
|
+
symbol,
|
|
24337
|
+
currentPrice,
|
|
24338
|
+
isBacktest,
|
|
24339
|
+
});
|
|
24340
|
+
if (!this.subscribe.hasValue()) {
|
|
24341
|
+
return;
|
|
24342
|
+
}
|
|
24343
|
+
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN(this);
|
|
24344
|
+
const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
|
|
24345
|
+
exchangeName: context.exchangeName,
|
|
24346
|
+
strategyName: context.strategyName,
|
|
24347
|
+
frameName: context.frameName,
|
|
24348
|
+
});
|
|
24349
|
+
if (!pendingRow) {
|
|
24350
|
+
return;
|
|
24351
|
+
}
|
|
24352
|
+
const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
|
|
24353
|
+
storage.addEvent({
|
|
24354
|
+
timestamp: Date.now(),
|
|
24355
|
+
symbol,
|
|
24356
|
+
strategyName: context.strategyName,
|
|
24357
|
+
exchangeName: context.exchangeName,
|
|
24358
|
+
frameName: context.frameName,
|
|
24359
|
+
signalId: pendingRow.id,
|
|
24360
|
+
action: "breakeven",
|
|
24361
|
+
currentPrice,
|
|
24362
|
+
createdAt,
|
|
24363
|
+
backtest: isBacktest,
|
|
24364
|
+
});
|
|
24365
|
+
};
|
|
24366
|
+
/**
|
|
24367
|
+
* Retrieves aggregated statistics from accumulated strategy events.
|
|
24368
|
+
*
|
|
24369
|
+
* Returns counts for each action type and the full event list from the
|
|
24370
|
+
* ReportStorage for the specified symbol-strategy pair.
|
|
24371
|
+
*
|
|
24372
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24373
|
+
* @param strategyName - Name of the trading strategy
|
|
24374
|
+
* @param exchangeName - Name of the exchange
|
|
24375
|
+
* @param frameName - Timeframe name for backtest identification
|
|
24376
|
+
* @param backtest - Whether to get backtest or live data
|
|
24377
|
+
* @returns Promise resolving to StrategyStatisticsModel with event list and counts
|
|
24378
|
+
* @throws Error if service not initialized (subscribe() not called)
|
|
24379
|
+
*/
|
|
24380
|
+
this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24381
|
+
this.loggerService.log("strategyMarkdownService getData", {
|
|
24382
|
+
symbol,
|
|
24383
|
+
strategyName,
|
|
24384
|
+
exchangeName,
|
|
24385
|
+
frameName,
|
|
24386
|
+
backtest,
|
|
24387
|
+
});
|
|
24388
|
+
if (!this.subscribe.hasValue()) {
|
|
24389
|
+
throw new Error("StrategyMarkdownService not initialized. Call subscribe() before getting data.");
|
|
24390
|
+
}
|
|
24391
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
24392
|
+
return storage.getData();
|
|
24393
|
+
};
|
|
24394
|
+
/**
|
|
24395
|
+
* Generates a markdown report from accumulated strategy events.
|
|
24396
|
+
*
|
|
24397
|
+
* Creates a formatted markdown document containing:
|
|
24398
|
+
* - Header with symbol and strategy name
|
|
24399
|
+
* - Table of all events with configurable columns
|
|
24400
|
+
* - Summary statistics with counts by action type
|
|
24401
|
+
*
|
|
24402
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24403
|
+
* @param strategyName - Name of the trading strategy
|
|
24404
|
+
* @param exchangeName - Name of the exchange
|
|
24405
|
+
* @param frameName - Timeframe name for backtest identification
|
|
24406
|
+
* @param backtest - Whether to get backtest or live data
|
|
24407
|
+
* @param columns - Column configuration for the event table
|
|
24408
|
+
* @returns Promise resolving to formatted markdown string
|
|
24409
|
+
* @throws Error if service not initialized (subscribe() not called)
|
|
24410
|
+
*/
|
|
24411
|
+
this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.strategy_columns) => {
|
|
24412
|
+
this.loggerService.log("strategyMarkdownService getReport", {
|
|
24413
|
+
symbol,
|
|
24414
|
+
strategyName,
|
|
24415
|
+
exchangeName,
|
|
24416
|
+
frameName,
|
|
24417
|
+
backtest,
|
|
24418
|
+
});
|
|
24419
|
+
if (!this.subscribe.hasValue()) {
|
|
24420
|
+
throw new Error("StrategyMarkdownService not initialized. Call subscribe() before generating reports.");
|
|
24421
|
+
}
|
|
24422
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
24423
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
24424
|
+
};
|
|
24425
|
+
/**
|
|
24426
|
+
* Generates and saves a markdown report to disk.
|
|
24427
|
+
*
|
|
24428
|
+
* Creates the output directory if it doesn't exist and writes
|
|
24429
|
+
* the report with a timestamped filename via Markdown.writeData().
|
|
24430
|
+
*
|
|
24431
|
+
* Filename format: `{symbol}_{strategyName}_{exchangeName}[_{frameName}_backtest|_live]-{timestamp}.md`
|
|
24432
|
+
*
|
|
24433
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
24434
|
+
* @param strategyName - Name of the trading strategy
|
|
24435
|
+
* @param exchangeName - Name of the exchange
|
|
24436
|
+
* @param frameName - Timeframe name for backtest identification
|
|
24437
|
+
* @param backtest - Whether to dump backtest or live data
|
|
24438
|
+
* @param path - Output directory path (default: "./dump/strategy")
|
|
24439
|
+
* @param columns - Column configuration for the event table
|
|
24440
|
+
* @returns Promise that resolves when file is written
|
|
24441
|
+
* @throws Error if service not initialized (subscribe() not called)
|
|
24442
|
+
*/
|
|
24443
|
+
this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) => {
|
|
24444
|
+
this.loggerService.log("strategyMarkdownService dump", {
|
|
24445
|
+
symbol,
|
|
24446
|
+
strategyName,
|
|
24447
|
+
exchangeName,
|
|
24448
|
+
frameName,
|
|
24449
|
+
backtest,
|
|
24450
|
+
path,
|
|
24451
|
+
});
|
|
24452
|
+
if (!this.subscribe.hasValue()) {
|
|
24453
|
+
throw new Error("StrategyMarkdownService not initialized. Call subscribe() before dumping reports.");
|
|
24454
|
+
}
|
|
24455
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
24456
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
24457
|
+
};
|
|
24458
|
+
/**
|
|
24459
|
+
* Clears accumulated events from storage.
|
|
24460
|
+
*
|
|
24461
|
+
* Can clear either a specific symbol-strategy pair or all stored data.
|
|
24462
|
+
*
|
|
24463
|
+
* @param payload - Optional filter to clear specific storage. If omitted, clears all.
|
|
24464
|
+
* @param payload.symbol - Trading pair symbol
|
|
24465
|
+
* @param payload.strategyName - Strategy name
|
|
24466
|
+
* @param payload.exchangeName - Exchange name
|
|
24467
|
+
* @param payload.frameName - Frame name
|
|
24468
|
+
* @param payload.backtest - Backtest mode flag
|
|
24469
|
+
*/
|
|
24470
|
+
this.clear = async (payload) => {
|
|
24471
|
+
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
24472
|
+
if (payload) {
|
|
24473
|
+
const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
24474
|
+
this.getStorage.clear(key);
|
|
24475
|
+
}
|
|
24476
|
+
else {
|
|
24477
|
+
this.getStorage.clear();
|
|
24478
|
+
}
|
|
24479
|
+
};
|
|
24480
|
+
/**
|
|
24481
|
+
* Initializes the service for event collection.
|
|
24482
|
+
*
|
|
24483
|
+
* Must be called before any events can be collected or reports generated.
|
|
24484
|
+
* Uses singleshot pattern to ensure only one subscription exists at a time.
|
|
24485
|
+
*
|
|
24486
|
+
* @returns Cleanup function that clears the subscription and all accumulated data
|
|
24487
|
+
*/
|
|
24488
|
+
this.subscribe = singleshot(() => {
|
|
24489
|
+
this.loggerService.log("strategyMarkdownService subscribe");
|
|
24490
|
+
const unCancelSchedule = strategyCommitSubject
|
|
24491
|
+
.filter(({ action }) => action === "cancel-scheduled")
|
|
24492
|
+
.connect(async (event) => await this.cancelScheduled(event.symbol, event.backtest, {
|
|
24493
|
+
exchangeName: event.exchangeName,
|
|
24494
|
+
frameName: event.frameName,
|
|
24495
|
+
strategyName: event.strategyName,
|
|
24496
|
+
}, event.cancelId));
|
|
24497
|
+
const unClosePending = strategyCommitSubject
|
|
24498
|
+
.filter(({ action }) => action === "close-pending")
|
|
24499
|
+
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
24500
|
+
exchangeName: event.exchangeName,
|
|
24501
|
+
frameName: event.frameName,
|
|
24502
|
+
strategyName: event.strategyName,
|
|
24503
|
+
}, event.closeId));
|
|
24504
|
+
const unPartialProfit = strategyCommitSubject
|
|
24505
|
+
.filter(({ action }) => action === "partial-profit")
|
|
24506
|
+
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
24507
|
+
exchangeName: event.exchangeName,
|
|
24508
|
+
frameName: event.frameName,
|
|
24509
|
+
strategyName: event.strategyName,
|
|
24510
|
+
}));
|
|
24511
|
+
const unPartialLoss = strategyCommitSubject
|
|
24512
|
+
.filter(({ action }) => action === "partial-loss")
|
|
24513
|
+
.connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
24514
|
+
exchangeName: event.exchangeName,
|
|
24515
|
+
frameName: event.frameName,
|
|
24516
|
+
strategyName: event.strategyName,
|
|
24517
|
+
}));
|
|
24518
|
+
const unTrailingStop = strategyCommitSubject
|
|
24519
|
+
.filter(({ action }) => action === "trailing-stop")
|
|
24520
|
+
.connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
24521
|
+
exchangeName: event.exchangeName,
|
|
24522
|
+
frameName: event.frameName,
|
|
24523
|
+
strategyName: event.strategyName,
|
|
24524
|
+
}));
|
|
24525
|
+
const unTrailingTake = strategyCommitSubject
|
|
24526
|
+
.filter(({ action }) => action === "trailing-take")
|
|
24527
|
+
.connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
24528
|
+
exchangeName: event.exchangeName,
|
|
24529
|
+
frameName: event.frameName,
|
|
24530
|
+
strategyName: event.strategyName,
|
|
24531
|
+
}));
|
|
24532
|
+
const unBreakeven = strategyCommitSubject
|
|
24533
|
+
.filter(({ action }) => action === "breakeven")
|
|
24534
|
+
.connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
|
|
24535
|
+
exchangeName: event.exchangeName,
|
|
24536
|
+
frameName: event.frameName,
|
|
24537
|
+
strategyName: event.strategyName,
|
|
24538
|
+
}));
|
|
24539
|
+
const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
|
|
24540
|
+
return () => {
|
|
24541
|
+
disposeFn();
|
|
24542
|
+
this.subscribe.clear();
|
|
24543
|
+
this.clear();
|
|
24544
|
+
};
|
|
24545
|
+
});
|
|
24546
|
+
/**
|
|
24547
|
+
* Stops event collection and clears all accumulated data.
|
|
24548
|
+
*
|
|
24549
|
+
* Invokes the cleanup function returned by subscribe(), which clears
|
|
24550
|
+
* both the subscription and all ReportStorage instances.
|
|
24551
|
+
* Safe to call multiple times - only acts if subscription exists.
|
|
24552
|
+
*/
|
|
24553
|
+
this.unsubscribe = async () => {
|
|
24554
|
+
this.loggerService.log("strategyMarkdownService unsubscribe");
|
|
24555
|
+
if (this.subscribe.hasValue()) {
|
|
24556
|
+
const lastSubscription = this.subscribe();
|
|
24557
|
+
lastSubscription();
|
|
24558
|
+
}
|
|
24559
|
+
};
|
|
24560
|
+
}
|
|
24561
|
+
}
|
|
24562
|
+
|
|
24563
|
+
{
|
|
24564
|
+
provide(TYPES.loggerService, () => new LoggerService());
|
|
24565
|
+
}
|
|
24566
|
+
{
|
|
24567
|
+
provide(TYPES.executionContextService, () => new ExecutionContextService());
|
|
24568
|
+
provide(TYPES.methodContextService, () => new MethodContextService());
|
|
24569
|
+
}
|
|
24570
|
+
{
|
|
24571
|
+
provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
|
|
24572
|
+
provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
|
|
24573
|
+
provide(TYPES.frameConnectionService, () => new FrameConnectionService());
|
|
24574
|
+
provide(TYPES.sizingConnectionService, () => new SizingConnectionService());
|
|
24575
|
+
provide(TYPES.riskConnectionService, () => new RiskConnectionService());
|
|
24576
|
+
provide(TYPES.actionConnectionService, () => new ActionConnectionService());
|
|
24577
|
+
provide(TYPES.partialConnectionService, () => new PartialConnectionService());
|
|
24578
|
+
provide(TYPES.breakevenConnectionService, () => new BreakevenConnectionService());
|
|
23081
24579
|
}
|
|
23082
24580
|
{
|
|
23083
24581
|
provide(TYPES.exchangeSchemaService, () => new ExchangeSchemaService());
|
|
@@ -23125,6 +24623,7 @@ class RiskReportService {
|
|
|
23125
24623
|
provide(TYPES.partialMarkdownService, () => new PartialMarkdownService());
|
|
23126
24624
|
provide(TYPES.breakevenMarkdownService, () => new BreakevenMarkdownService());
|
|
23127
24625
|
provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
|
|
24626
|
+
provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
|
|
23128
24627
|
}
|
|
23129
24628
|
{
|
|
23130
24629
|
provide(TYPES.backtestReportService, () => new BacktestReportService());
|
|
@@ -23136,6 +24635,7 @@ class RiskReportService {
|
|
|
23136
24635
|
provide(TYPES.partialReportService, () => new PartialReportService());
|
|
23137
24636
|
provide(TYPES.breakevenReportService, () => new BreakevenReportService());
|
|
23138
24637
|
provide(TYPES.riskReportService, () => new RiskReportService());
|
|
24638
|
+
provide(TYPES.strategyReportService, () => new StrategyReportService());
|
|
23139
24639
|
}
|
|
23140
24640
|
{
|
|
23141
24641
|
provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
|
|
@@ -23212,6 +24712,7 @@ const markdownServices = {
|
|
|
23212
24712
|
partialMarkdownService: inject(TYPES.partialMarkdownService),
|
|
23213
24713
|
breakevenMarkdownService: inject(TYPES.breakevenMarkdownService),
|
|
23214
24714
|
riskMarkdownService: inject(TYPES.riskMarkdownService),
|
|
24715
|
+
strategyMarkdownService: inject(TYPES.strategyMarkdownService),
|
|
23215
24716
|
};
|
|
23216
24717
|
const reportServices = {
|
|
23217
24718
|
backtestReportService: inject(TYPES.backtestReportService),
|
|
@@ -23223,6 +24724,7 @@ const reportServices = {
|
|
|
23223
24724
|
partialReportService: inject(TYPES.partialReportService),
|
|
23224
24725
|
breakevenReportService: inject(TYPES.breakevenReportService),
|
|
23225
24726
|
riskReportService: inject(TYPES.riskReportService),
|
|
24727
|
+
strategyReportService: inject(TYPES.strategyReportService),
|
|
23226
24728
|
};
|
|
23227
24729
|
const validationServices = {
|
|
23228
24730
|
exchangeValidationService: inject(TYPES.exchangeValidationService),
|
|
@@ -25301,6 +26803,8 @@ const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
|
|
|
25301
26803
|
const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
|
|
25302
26804
|
const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
|
|
25303
26805
|
const LISTEN_ACTIVE_PING_ONCE_METHOD_NAME = "event.listenActivePingOnce";
|
|
26806
|
+
const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
|
|
26807
|
+
const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
|
|
25304
26808
|
/**
|
|
25305
26809
|
* Subscribes to all signal events with queued async processing.
|
|
25306
26810
|
*
|
|
@@ -26330,6 +27834,78 @@ function listenActivePingOnce(filterFn, fn) {
|
|
|
26330
27834
|
bt.loggerService.log(LISTEN_ACTIVE_PING_ONCE_METHOD_NAME);
|
|
26331
27835
|
return activePingSubject.filter(filterFn).once(fn);
|
|
26332
27836
|
}
|
|
27837
|
+
/**
|
|
27838
|
+
* Subscribes to strategy management events with queued async processing.
|
|
27839
|
+
*
|
|
27840
|
+
* Emits when strategy management actions are executed:
|
|
27841
|
+
* - cancel-scheduled: Scheduled signal cancelled
|
|
27842
|
+
* - close-pending: Pending signal closed
|
|
27843
|
+
* - partial-profit: Partial close at profit level
|
|
27844
|
+
* - partial-loss: Partial close at loss level
|
|
27845
|
+
* - trailing-stop: Stop-loss adjusted
|
|
27846
|
+
* - trailing-take: Take-profit adjusted
|
|
27847
|
+
* - breakeven: Stop-loss moved to entry price
|
|
27848
|
+
*
|
|
27849
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
27850
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
27851
|
+
*
|
|
27852
|
+
* @param fn - Callback function to handle strategy commit events
|
|
27853
|
+
* @returns Unsubscribe function to stop listening
|
|
27854
|
+
*
|
|
27855
|
+
* @example
|
|
27856
|
+
* ```typescript
|
|
27857
|
+
* import { listenStrategyCommit } from "./function/event";
|
|
27858
|
+
*
|
|
27859
|
+
* const unsubscribe = listenStrategyCommit((event) => {
|
|
27860
|
+
* console.log(`[${event.action}] ${event.symbol}`);
|
|
27861
|
+
* console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
|
|
27862
|
+
* if (event.action === "partial-profit") {
|
|
27863
|
+
* console.log(`Closed ${event.percentToClose}% at ${event.currentPrice}`);
|
|
27864
|
+
* }
|
|
27865
|
+
* });
|
|
27866
|
+
*
|
|
27867
|
+
* // Later: stop listening
|
|
27868
|
+
* unsubscribe();
|
|
27869
|
+
* ```
|
|
27870
|
+
*/
|
|
27871
|
+
function listenStrategyCommit(fn) {
|
|
27872
|
+
bt.loggerService.log(LISTEN_STRATEGY_COMMIT_METHOD_NAME);
|
|
27873
|
+
return strategyCommitSubject.subscribe(queued(async (event) => fn(event)));
|
|
27874
|
+
}
|
|
27875
|
+
/**
|
|
27876
|
+
* Subscribes to filtered strategy management events with one-time execution.
|
|
27877
|
+
*
|
|
27878
|
+
* Listens for events matching the filter predicate, then executes callback once
|
|
27879
|
+
* and automatically unsubscribes. Useful for waiting for specific strategy actions.
|
|
27880
|
+
*
|
|
27881
|
+
* @param filterFn - Predicate to filter which events trigger the callback
|
|
27882
|
+
* @param fn - Callback function to handle the filtered event (called only once)
|
|
27883
|
+
* @returns Unsubscribe function to cancel the listener before it fires
|
|
27884
|
+
*
|
|
27885
|
+
* @example
|
|
27886
|
+
* ```typescript
|
|
27887
|
+
* import { listenStrategyCommitOnce } from "./function/event";
|
|
27888
|
+
*
|
|
27889
|
+
* // Wait for first trailing stop adjustment
|
|
27890
|
+
* listenStrategyCommitOnce(
|
|
27891
|
+
* (event) => event.action === "trailing-stop",
|
|
27892
|
+
* (event) => console.log("Trailing stop adjusted:", event.symbol)
|
|
27893
|
+
* );
|
|
27894
|
+
*
|
|
27895
|
+
* // Wait for breakeven on BTCUSDT
|
|
27896
|
+
* const cancel = listenStrategyCommitOnce(
|
|
27897
|
+
* (event) => event.action === "breakeven" && event.symbol === "BTCUSDT",
|
|
27898
|
+
* (event) => console.log("BTCUSDT moved to breakeven at", event.currentPrice)
|
|
27899
|
+
* );
|
|
27900
|
+
*
|
|
27901
|
+
* // Cancel if needed before event fires
|
|
27902
|
+
* cancel();
|
|
27903
|
+
* ```
|
|
27904
|
+
*/
|
|
27905
|
+
function listenStrategyCommitOnce(filterFn, fn) {
|
|
27906
|
+
bt.loggerService.log(LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME);
|
|
27907
|
+
return strategyCommitSubject.filter(filterFn).once(fn);
|
|
27908
|
+
}
|
|
26333
27909
|
|
|
26334
27910
|
const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
|
|
26335
27911
|
const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
|
|
@@ -30775,6 +32351,7 @@ class NotificationInstance {
|
|
|
30775
32351
|
priceTakeProfit: data.signal.priceTakeProfit,
|
|
30776
32352
|
priceStopLoss: data.signal.priceStopLoss,
|
|
30777
32353
|
note: data.signal.note,
|
|
32354
|
+
createdAt: data.createdAt,
|
|
30778
32355
|
});
|
|
30779
32356
|
}
|
|
30780
32357
|
else if (data.action === "closed") {
|
|
@@ -30796,6 +32373,7 @@ class NotificationInstance {
|
|
|
30796
32373
|
closeReason: data.closeReason,
|
|
30797
32374
|
duration: durationMin,
|
|
30798
32375
|
note: data.signal.note,
|
|
32376
|
+
createdAt: data.createdAt,
|
|
30799
32377
|
});
|
|
30800
32378
|
}
|
|
30801
32379
|
else if (data.action === "scheduled") {
|
|
@@ -30812,6 +32390,7 @@ class NotificationInstance {
|
|
|
30812
32390
|
priceOpen: data.signal.priceOpen,
|
|
30813
32391
|
scheduledAt: data.signal.scheduledAt,
|
|
30814
32392
|
currentPrice: data.currentPrice,
|
|
32393
|
+
createdAt: data.createdAt,
|
|
30815
32394
|
});
|
|
30816
32395
|
}
|
|
30817
32396
|
else if (data.action === "cancelled") {
|
|
@@ -30830,6 +32409,7 @@ class NotificationInstance {
|
|
|
30830
32409
|
cancelReason: data.reason,
|
|
30831
32410
|
cancelId: data.cancelId,
|
|
30832
32411
|
duration: durationMin,
|
|
32412
|
+
createdAt: data.createdAt,
|
|
30833
32413
|
});
|
|
30834
32414
|
}
|
|
30835
32415
|
};
|
|
@@ -30838,7 +32418,7 @@ class NotificationInstance {
|
|
|
30838
32418
|
*/
|
|
30839
32419
|
this._handlePartialProfit = async (data) => {
|
|
30840
32420
|
this._addNotification({
|
|
30841
|
-
type: "
|
|
32421
|
+
type: "partial_profit.available",
|
|
30842
32422
|
id: CREATE_KEY_FN(),
|
|
30843
32423
|
timestamp: data.timestamp,
|
|
30844
32424
|
backtest: data.backtest,
|
|
@@ -30857,7 +32437,7 @@ class NotificationInstance {
|
|
|
30857
32437
|
*/
|
|
30858
32438
|
this._handlePartialLoss = async (data) => {
|
|
30859
32439
|
this._addNotification({
|
|
30860
|
-
type: "
|
|
32440
|
+
type: "partial_loss.available",
|
|
30861
32441
|
id: CREATE_KEY_FN(),
|
|
30862
32442
|
timestamp: data.timestamp,
|
|
30863
32443
|
backtest: data.backtest,
|
|
@@ -30872,50 +32452,109 @@ class NotificationInstance {
|
|
|
30872
32452
|
});
|
|
30873
32453
|
};
|
|
30874
32454
|
/**
|
|
30875
|
-
* Processes
|
|
32455
|
+
* Processes breakeven events.
|
|
30876
32456
|
*/
|
|
30877
|
-
this.
|
|
32457
|
+
this._handleBreakeven = async (data) => {
|
|
30878
32458
|
this._addNotification({
|
|
30879
|
-
type: "
|
|
32459
|
+
type: "breakeven.available",
|
|
30880
32460
|
id: CREATE_KEY_FN(),
|
|
30881
32461
|
timestamp: data.timestamp,
|
|
30882
32462
|
backtest: data.backtest,
|
|
30883
32463
|
symbol: data.symbol,
|
|
30884
32464
|
strategyName: data.strategyName,
|
|
30885
32465
|
exchangeName: data.exchangeName,
|
|
30886
|
-
|
|
30887
|
-
rejectionId: data.rejectionId,
|
|
30888
|
-
activePositionCount: data.activePositionCount,
|
|
32466
|
+
signalId: data.data.id,
|
|
30889
32467
|
currentPrice: data.currentPrice,
|
|
30890
|
-
|
|
32468
|
+
priceOpen: data.data.priceOpen,
|
|
32469
|
+
position: data.data.position,
|
|
30891
32470
|
});
|
|
30892
32471
|
};
|
|
30893
32472
|
/**
|
|
30894
|
-
* Processes
|
|
32473
|
+
* Processes strategy commit events.
|
|
30895
32474
|
*/
|
|
30896
|
-
this.
|
|
30897
|
-
|
|
30898
|
-
|
|
30899
|
-
|
|
30900
|
-
|
|
30901
|
-
|
|
30902
|
-
|
|
30903
|
-
|
|
30904
|
-
|
|
30905
|
-
|
|
32475
|
+
this._handleStrategyCommit = async (data) => {
|
|
32476
|
+
if (data.action === "partial-profit") {
|
|
32477
|
+
this._addNotification({
|
|
32478
|
+
type: "partial_profit.commit",
|
|
32479
|
+
id: CREATE_KEY_FN(),
|
|
32480
|
+
timestamp: Date.now(),
|
|
32481
|
+
backtest: data.backtest,
|
|
32482
|
+
symbol: data.symbol,
|
|
32483
|
+
strategyName: data.strategyName,
|
|
32484
|
+
exchangeName: data.exchangeName,
|
|
32485
|
+
percentToClose: data.percentToClose,
|
|
32486
|
+
currentPrice: data.currentPrice,
|
|
32487
|
+
});
|
|
32488
|
+
}
|
|
32489
|
+
else if (data.action === "partial-loss") {
|
|
32490
|
+
this._addNotification({
|
|
32491
|
+
type: "partial_loss.commit",
|
|
32492
|
+
id: CREATE_KEY_FN(),
|
|
32493
|
+
timestamp: Date.now(),
|
|
32494
|
+
backtest: data.backtest,
|
|
32495
|
+
symbol: data.symbol,
|
|
32496
|
+
strategyName: data.strategyName,
|
|
32497
|
+
exchangeName: data.exchangeName,
|
|
32498
|
+
percentToClose: data.percentToClose,
|
|
32499
|
+
currentPrice: data.currentPrice,
|
|
32500
|
+
});
|
|
32501
|
+
}
|
|
32502
|
+
else if (data.action === "breakeven") {
|
|
32503
|
+
this._addNotification({
|
|
32504
|
+
type: "breakeven.commit",
|
|
32505
|
+
id: CREATE_KEY_FN(),
|
|
32506
|
+
timestamp: Date.now(),
|
|
32507
|
+
backtest: data.backtest,
|
|
32508
|
+
symbol: data.symbol,
|
|
32509
|
+
strategyName: data.strategyName,
|
|
32510
|
+
exchangeName: data.exchangeName,
|
|
32511
|
+
currentPrice: data.currentPrice,
|
|
32512
|
+
});
|
|
32513
|
+
}
|
|
32514
|
+
else if (data.action === "trailing-stop") {
|
|
32515
|
+
this._addNotification({
|
|
32516
|
+
type: "trailing_stop.commit",
|
|
32517
|
+
id: CREATE_KEY_FN(),
|
|
32518
|
+
timestamp: Date.now(),
|
|
32519
|
+
backtest: data.backtest,
|
|
32520
|
+
symbol: data.symbol,
|
|
32521
|
+
strategyName: data.strategyName,
|
|
32522
|
+
exchangeName: data.exchangeName,
|
|
32523
|
+
percentShift: data.percentShift,
|
|
32524
|
+
currentPrice: data.currentPrice,
|
|
32525
|
+
});
|
|
32526
|
+
}
|
|
32527
|
+
else if (data.action === "trailing-take") {
|
|
32528
|
+
this._addNotification({
|
|
32529
|
+
type: "trailing_take.commit",
|
|
32530
|
+
id: CREATE_KEY_FN(),
|
|
32531
|
+
timestamp: Date.now(),
|
|
32532
|
+
backtest: data.backtest,
|
|
32533
|
+
symbol: data.symbol,
|
|
32534
|
+
strategyName: data.strategyName,
|
|
32535
|
+
exchangeName: data.exchangeName,
|
|
32536
|
+
percentShift: data.percentShift,
|
|
32537
|
+
currentPrice: data.currentPrice,
|
|
32538
|
+
});
|
|
32539
|
+
}
|
|
30906
32540
|
};
|
|
30907
32541
|
/**
|
|
30908
|
-
* Processes
|
|
32542
|
+
* Processes risk rejection events.
|
|
30909
32543
|
*/
|
|
30910
|
-
this.
|
|
32544
|
+
this._handleRisk = async (data) => {
|
|
30911
32545
|
this._addNotification({
|
|
30912
|
-
type: "
|
|
32546
|
+
type: "risk.rejection",
|
|
30913
32547
|
id: CREATE_KEY_FN(),
|
|
30914
|
-
timestamp:
|
|
30915
|
-
backtest:
|
|
32548
|
+
timestamp: data.timestamp,
|
|
32549
|
+
backtest: data.backtest,
|
|
30916
32550
|
symbol: data.symbol,
|
|
30917
32551
|
strategyName: data.strategyName,
|
|
30918
32552
|
exchangeName: data.exchangeName,
|
|
32553
|
+
rejectionNote: data.rejectionNote,
|
|
32554
|
+
rejectionId: data.rejectionId,
|
|
32555
|
+
activePositionCount: data.activePositionCount,
|
|
32556
|
+
currentPrice: data.currentPrice,
|
|
32557
|
+
pendingSignal: data.pendingSignal,
|
|
30919
32558
|
});
|
|
30920
32559
|
};
|
|
30921
32560
|
/**
|
|
@@ -30957,23 +32596,6 @@ class NotificationInstance {
|
|
|
30957
32596
|
backtest: false,
|
|
30958
32597
|
});
|
|
30959
32598
|
};
|
|
30960
|
-
/**
|
|
30961
|
-
* Processes progress events.
|
|
30962
|
-
*/
|
|
30963
|
-
this._handleProgressBacktest = async (data) => {
|
|
30964
|
-
this._addNotification({
|
|
30965
|
-
type: "progress.backtest",
|
|
30966
|
-
id: CREATE_KEY_FN(),
|
|
30967
|
-
timestamp: Date.now(),
|
|
30968
|
-
backtest: true,
|
|
30969
|
-
exchangeName: data.exchangeName,
|
|
30970
|
-
strategyName: data.strategyName,
|
|
30971
|
-
symbol: data.symbol,
|
|
30972
|
-
totalFrames: data.totalFrames,
|
|
30973
|
-
processedFrames: data.processedFrames,
|
|
30974
|
-
progress: data.progress,
|
|
30975
|
-
});
|
|
30976
|
-
};
|
|
30977
32599
|
/**
|
|
30978
32600
|
* Subscribes to all notification emitters and returns an unsubscribe function.
|
|
30979
32601
|
* Protected against multiple subscriptions using singleshot.
|
|
@@ -30992,14 +32614,13 @@ class NotificationInstance {
|
|
|
30992
32614
|
const unSignal = signalEmitter.subscribe(this._handleSignal);
|
|
30993
32615
|
const unProfit = partialProfitSubject.subscribe(this._handlePartialProfit);
|
|
30994
32616
|
const unLoss = partialLossSubject.subscribe(this._handlePartialLoss);
|
|
32617
|
+
const unBreakeven = breakevenSubject.subscribe(this._handleBreakeven);
|
|
32618
|
+
const unStrategyCommit = strategyCommitSubject.subscribe(this._handleStrategyCommit);
|
|
30995
32619
|
const unRisk = riskSubject.subscribe(this._handleRisk);
|
|
30996
|
-
const unDoneLine = doneLiveSubject.subscribe(this._handleDoneLive);
|
|
30997
|
-
const unDoneBacktest = doneBacktestSubject.subscribe(this._handleDoneBacktest);
|
|
30998
32620
|
const unError = errorEmitter.subscribe(this._handleError);
|
|
30999
32621
|
const unExit = exitEmitter.subscribe(this._handleCriticalError);
|
|
31000
32622
|
const unValidation = validationSubject.subscribe(this._handleValidationError);
|
|
31001
|
-
const
|
|
31002
|
-
const disposeFn = compose(() => unSignal(), () => unProfit(), () => unLoss(), () => unRisk(), () => unDoneLine(), () => unDoneBacktest(), () => unError(), () => unExit(), () => unValidation(), () => unProgressBacktest());
|
|
32623
|
+
const disposeFn = compose(() => unSignal(), () => unProfit(), () => unLoss(), () => unBreakeven(), () => unStrategyCommit(), () => unRisk(), () => unError(), () => unExit(), () => unValidation());
|
|
31003
32624
|
return () => {
|
|
31004
32625
|
disposeFn();
|
|
31005
32626
|
this.enable.clear();
|
|
@@ -31389,6 +33010,201 @@ class BreakevenUtils {
|
|
|
31389
33010
|
*/
|
|
31390
33011
|
const Breakeven = new BreakevenUtils();
|
|
31391
33012
|
|
|
33013
|
+
const STRATEGY_METHOD_NAME_GET_DATA = "StrategyUtils.getData";
|
|
33014
|
+
const STRATEGY_METHOD_NAME_GET_REPORT = "StrategyUtils.getReport";
|
|
33015
|
+
const STRATEGY_METHOD_NAME_DUMP = "StrategyUtils.dump";
|
|
33016
|
+
/**
|
|
33017
|
+
* Utility class for accessing strategy management reports and statistics.
|
|
33018
|
+
*
|
|
33019
|
+
* Provides static-like methods (via singleton instance) to retrieve data
|
|
33020
|
+
* accumulated by StrategyMarkdownService from strategy management events.
|
|
33021
|
+
*
|
|
33022
|
+
* Features:
|
|
33023
|
+
* - Statistical data extraction (event counts by action type)
|
|
33024
|
+
* - Markdown report generation with event tables
|
|
33025
|
+
* - File export to disk
|
|
33026
|
+
*
|
|
33027
|
+
* Data source:
|
|
33028
|
+
* - StrategyMarkdownService receives events via direct method calls
|
|
33029
|
+
* - Accumulates events in ReportStorage (max 250 events per symbol-strategy pair)
|
|
33030
|
+
* - Events include: cancel-scheduled, close-pending, partial-profit, partial-loss,
|
|
33031
|
+
* trailing-stop, trailing-take, breakeven
|
|
33032
|
+
*
|
|
33033
|
+
* @example
|
|
33034
|
+
* ```typescript
|
|
33035
|
+
* import { Strategy } from "./classes/Strategy";
|
|
33036
|
+
*
|
|
33037
|
+
* // Get statistical data for BTCUSDT:my-strategy
|
|
33038
|
+
* const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33039
|
+
* console.log(`Total events: ${stats.totalEvents}`);
|
|
33040
|
+
* console.log(`Partial profit events: ${stats.partialProfitCount}`);
|
|
33041
|
+
*
|
|
33042
|
+
* // Generate markdown report
|
|
33043
|
+
* const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33044
|
+
* console.log(markdown); // Formatted table with all events
|
|
33045
|
+
*
|
|
33046
|
+
* // Export report to file
|
|
33047
|
+
* await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }); // Saves to ./dump/strategy/
|
|
33048
|
+
* ```
|
|
33049
|
+
*/
|
|
33050
|
+
class StrategyUtils {
|
|
33051
|
+
constructor() {
|
|
33052
|
+
/**
|
|
33053
|
+
* Retrieves statistical data from accumulated strategy events.
|
|
33054
|
+
*
|
|
33055
|
+
* Delegates to StrategyMarkdownService.getData() which reads from ReportStorage.
|
|
33056
|
+
* Returns aggregated metrics calculated from all strategy events.
|
|
33057
|
+
*
|
|
33058
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
33059
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
33060
|
+
* @param backtest - Whether to get backtest data (default: false)
|
|
33061
|
+
* @returns Promise resolving to StrategyStatisticsModel object with counts and event list
|
|
33062
|
+
*
|
|
33063
|
+
* @example
|
|
33064
|
+
* ```typescript
|
|
33065
|
+
* const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33066
|
+
*
|
|
33067
|
+
* console.log(`Total events: ${stats.totalEvents}`);
|
|
33068
|
+
* console.log(`Partial profit: ${stats.partialProfitCount}`);
|
|
33069
|
+
* console.log(`Trailing stop: ${stats.trailingStopCount}`);
|
|
33070
|
+
*
|
|
33071
|
+
* // Iterate through all events
|
|
33072
|
+
* for (const event of stats.eventList) {
|
|
33073
|
+
* console.log(`Signal ${event.signalId}: ${event.action} at ${event.currentPrice}`);
|
|
33074
|
+
* }
|
|
33075
|
+
* ```
|
|
33076
|
+
*/
|
|
33077
|
+
this.getData = async (symbol, context, backtest = false) => {
|
|
33078
|
+
bt.loggerService.info(STRATEGY_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
|
|
33079
|
+
bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_DATA);
|
|
33080
|
+
bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_DATA);
|
|
33081
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_DATA);
|
|
33082
|
+
{
|
|
33083
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33084
|
+
riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA);
|
|
33085
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_DATA));
|
|
33086
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_DATA));
|
|
33087
|
+
}
|
|
33088
|
+
return await bt.strategyMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
33089
|
+
};
|
|
33090
|
+
/**
|
|
33091
|
+
* Generates markdown report with all strategy events for a symbol-strategy pair.
|
|
33092
|
+
*
|
|
33093
|
+
* Creates formatted table containing:
|
|
33094
|
+
* - Symbol
|
|
33095
|
+
* - Strategy
|
|
33096
|
+
* - Signal ID
|
|
33097
|
+
* - Action (cancel-scheduled, close-pending, partial-profit, etc.)
|
|
33098
|
+
* - Price
|
|
33099
|
+
* - Percent values (% To Close, % Shift)
|
|
33100
|
+
* - Cancel/Close IDs
|
|
33101
|
+
* - Timestamp (ISO 8601)
|
|
33102
|
+
* - Mode (Backtest/Live)
|
|
33103
|
+
*
|
|
33104
|
+
* Also includes summary statistics at the end with counts by action type.
|
|
33105
|
+
*
|
|
33106
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
33107
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
33108
|
+
* @param backtest - Whether to get backtest data (default: false)
|
|
33109
|
+
* @param columns - Optional columns configuration for the report
|
|
33110
|
+
* @returns Promise resolving to markdown formatted report string
|
|
33111
|
+
*
|
|
33112
|
+
* @example
|
|
33113
|
+
* ```typescript
|
|
33114
|
+
* const markdown = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33115
|
+
* console.log(markdown);
|
|
33116
|
+
*
|
|
33117
|
+
* // Output:
|
|
33118
|
+
* // # Strategy Report: BTCUSDT:my-strategy
|
|
33119
|
+
* //
|
|
33120
|
+
* // | Symbol | Strategy | Signal ID | Action | Price | ... |
|
|
33121
|
+
* // | --- | --- | --- | --- | --- | ... |
|
|
33122
|
+
* // | BTCUSDT | my-strategy | abc123 | partial-profit | 50100.00000000 USD | ... |
|
|
33123
|
+
* //
|
|
33124
|
+
* // **Total events:** 5
|
|
33125
|
+
* // - Cancel scheduled: 0
|
|
33126
|
+
* // - Close pending: 1
|
|
33127
|
+
* // - Partial profit: 2
|
|
33128
|
+
* // ...
|
|
33129
|
+
* ```
|
|
33130
|
+
*/
|
|
33131
|
+
this.getReport = async (symbol, context, backtest = false, columns) => {
|
|
33132
|
+
bt.loggerService.info(STRATEGY_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
|
|
33133
|
+
bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_GET_REPORT);
|
|
33134
|
+
bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_GET_REPORT);
|
|
33135
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_GET_REPORT);
|
|
33136
|
+
{
|
|
33137
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33138
|
+
riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT);
|
|
33139
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_GET_REPORT));
|
|
33140
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_GET_REPORT));
|
|
33141
|
+
}
|
|
33142
|
+
return await bt.strategyMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
|
|
33143
|
+
};
|
|
33144
|
+
/**
|
|
33145
|
+
* Generates and saves markdown report to file.
|
|
33146
|
+
*
|
|
33147
|
+
* Creates directory if it doesn't exist.
|
|
33148
|
+
* Filename format: {symbol}_{strategyName}_{exchangeName}_{frameName|live}-{timestamp}.md
|
|
33149
|
+
*
|
|
33150
|
+
* Delegates to StrategyMarkdownService.dump() which:
|
|
33151
|
+
* 1. Generates markdown report via getReport()
|
|
33152
|
+
* 2. Creates output directory (recursive mkdir)
|
|
33153
|
+
* 3. Writes file with UTF-8 encoding
|
|
33154
|
+
* 4. Logs success/failure to console
|
|
33155
|
+
*
|
|
33156
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
33157
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
33158
|
+
* @param backtest - Whether to dump backtest data (default: false)
|
|
33159
|
+
* @param path - Output directory path (default: "./dump/strategy")
|
|
33160
|
+
* @param columns - Optional columns configuration for the report
|
|
33161
|
+
* @returns Promise that resolves when file is written
|
|
33162
|
+
*
|
|
33163
|
+
* @example
|
|
33164
|
+
* ```typescript
|
|
33165
|
+
* // Save to default path: ./dump/strategy/BTCUSDT_my-strategy_binance_1h_backtest-{timestamp}.md
|
|
33166
|
+
* await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true);
|
|
33167
|
+
*
|
|
33168
|
+
* // Save to custom path
|
|
33169
|
+
* await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./reports/strategy");
|
|
33170
|
+
*
|
|
33171
|
+
* // After multiple symbols backtested, export all reports
|
|
33172
|
+
* for (const symbol of ["BTCUSDT", "ETHUSDT", "BNBUSDT"]) {
|
|
33173
|
+
* await Strategy.dump(symbol, { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }, true, "./backtest-results");
|
|
33174
|
+
* }
|
|
33175
|
+
* ```
|
|
33176
|
+
*/
|
|
33177
|
+
this.dump = async (symbol, context, backtest = false, path, columns) => {
|
|
33178
|
+
bt.loggerService.info(STRATEGY_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
|
|
33179
|
+
bt.strategyValidationService.validate(context.strategyName, STRATEGY_METHOD_NAME_DUMP);
|
|
33180
|
+
bt.exchangeValidationService.validate(context.exchangeName, STRATEGY_METHOD_NAME_DUMP);
|
|
33181
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, STRATEGY_METHOD_NAME_DUMP);
|
|
33182
|
+
{
|
|
33183
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33184
|
+
riskName && bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP);
|
|
33185
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, STRATEGY_METHOD_NAME_DUMP));
|
|
33186
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, STRATEGY_METHOD_NAME_DUMP));
|
|
33187
|
+
}
|
|
33188
|
+
await bt.strategyMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
|
|
33189
|
+
};
|
|
33190
|
+
}
|
|
33191
|
+
}
|
|
33192
|
+
/**
|
|
33193
|
+
* Global singleton instance of StrategyUtils.
|
|
33194
|
+
* Provides static-like access to strategy management reporting methods.
|
|
33195
|
+
*
|
|
33196
|
+
* @example
|
|
33197
|
+
* ```typescript
|
|
33198
|
+
* import { Strategy } from "backtest-kit";
|
|
33199
|
+
*
|
|
33200
|
+
* // Usage same as StrategyUtils methods
|
|
33201
|
+
* const stats = await Strategy.getData("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33202
|
+
* const report = await Strategy.getReport("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33203
|
+
* await Strategy.dump("BTCUSDT", { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" });
|
|
33204
|
+
* ```
|
|
33205
|
+
*/
|
|
33206
|
+
const Strategy = new StrategyUtils();
|
|
33207
|
+
|
|
31392
33208
|
/**
|
|
31393
33209
|
* Rounds a price to the appropriate precision based on the tick size.
|
|
31394
33210
|
*
|
|
@@ -31521,4 +33337,4 @@ const set = (object, path, value) => {
|
|
|
31521
33337
|
}
|
|
31522
33338
|
};
|
|
31523
33339
|
|
|
31524
|
-
export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate };
|
|
33340
|
+
export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate };
|