backtest-kit 11.8.0 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +1996 -1996
- package/build/index.cjs +661 -136
- package/build/index.mjs +660 -137
- package/package.json +86 -86
- package/types.d.ts +199 -40
package/build/index.cjs
CHANGED
|
@@ -4681,7 +4681,7 @@ const LOGGER_SERVICE$8 = new LoggerService();
|
|
|
4681
4681
|
* @param backtest - `true` for backtest, `false` for live.
|
|
4682
4682
|
* @returns Colon-joined composite key.
|
|
4683
4683
|
*/
|
|
4684
|
-
const CREATE_KEY_FN$
|
|
4684
|
+
const CREATE_KEY_FN$B = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
4685
4685
|
const parts = [symbol, strategyName, exchangeName];
|
|
4686
4686
|
if (frameName)
|
|
4687
4687
|
parts.push(frameName);
|
|
@@ -4724,7 +4724,7 @@ class LookupUtils {
|
|
|
4724
4724
|
LOGGER_SERVICE$8.info(METHOD_NAME_ADD_ACTIVITY, {
|
|
4725
4725
|
activity,
|
|
4726
4726
|
});
|
|
4727
|
-
const key = CREATE_KEY_FN$
|
|
4727
|
+
const key = CREATE_KEY_FN$B(activity.symbol, activity.context.strategyName, activity.context.exchangeName, activity.context.frameName, activity.backtest);
|
|
4728
4728
|
this._lookupMap.set(key, activity);
|
|
4729
4729
|
};
|
|
4730
4730
|
/**
|
|
@@ -4738,7 +4738,7 @@ class LookupUtils {
|
|
|
4738
4738
|
LOGGER_SERVICE$8.info(METHOD_NAME_REMOVE_ACTIVITY, {
|
|
4739
4739
|
activity,
|
|
4740
4740
|
});
|
|
4741
|
-
const key = CREATE_KEY_FN$
|
|
4741
|
+
const key = CREATE_KEY_FN$B(activity.symbol, activity.context.strategyName, activity.context.exchangeName, activity.context.frameName, activity.backtest);
|
|
4742
4742
|
this._lookupMap.delete(key);
|
|
4743
4743
|
};
|
|
4744
4744
|
/**
|
|
@@ -12356,7 +12356,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
12356
12356
|
* @param backtest - Whether running in backtest mode
|
|
12357
12357
|
* @returns Unique string key for memoization
|
|
12358
12358
|
*/
|
|
12359
|
-
const CREATE_KEY_FN$
|
|
12359
|
+
const CREATE_KEY_FN$A = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
12360
12360
|
const parts = [symbol, strategyName, exchangeName];
|
|
12361
12361
|
if (frameName)
|
|
12362
12362
|
parts.push(frameName);
|
|
@@ -12658,7 +12658,7 @@ class StrategyConnectionService {
|
|
|
12658
12658
|
* @param backtest - Whether running in backtest mode
|
|
12659
12659
|
* @returns Configured ClientStrategy instance
|
|
12660
12660
|
*/
|
|
12661
|
-
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12661
|
+
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$A(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
12662
12662
|
const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
12663
12663
|
return new ClientStrategy({
|
|
12664
12664
|
symbol,
|
|
@@ -13620,7 +13620,7 @@ class StrategyConnectionService {
|
|
|
13620
13620
|
}
|
|
13621
13621
|
return;
|
|
13622
13622
|
}
|
|
13623
|
-
const key = CREATE_KEY_FN$
|
|
13623
|
+
const key = CREATE_KEY_FN$A(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
13624
13624
|
if (!this.getStrategy.has(key)) {
|
|
13625
13625
|
return;
|
|
13626
13626
|
}
|
|
@@ -14166,6 +14166,7 @@ class ClientFrame {
|
|
|
14166
14166
|
}
|
|
14167
14167
|
}
|
|
14168
14168
|
|
|
14169
|
+
const DEFAULT_INTERVAL = "1m";
|
|
14169
14170
|
/**
|
|
14170
14171
|
* Connection service routing frame operations to correct ClientFrame instance.
|
|
14171
14172
|
*
|
|
@@ -14209,7 +14210,7 @@ class FrameConnectionService {
|
|
|
14209
14210
|
logger: this.loggerService,
|
|
14210
14211
|
startDate,
|
|
14211
14212
|
endDate,
|
|
14212
|
-
interval,
|
|
14213
|
+
interval: interval || DEFAULT_INTERVAL,
|
|
14213
14214
|
callbacks,
|
|
14214
14215
|
});
|
|
14215
14216
|
});
|
|
@@ -14868,7 +14869,7 @@ class ClientRisk {
|
|
|
14868
14869
|
* @param backtest - Whether running in backtest mode
|
|
14869
14870
|
* @returns Unique string key for memoization
|
|
14870
14871
|
*/
|
|
14871
|
-
const CREATE_KEY_FN$
|
|
14872
|
+
const CREATE_KEY_FN$z = (riskName, exchangeName, frameName, backtest) => {
|
|
14872
14873
|
const parts = [riskName, exchangeName];
|
|
14873
14874
|
if (frameName)
|
|
14874
14875
|
parts.push(frameName);
|
|
@@ -14968,7 +14969,7 @@ class RiskConnectionService {
|
|
|
14968
14969
|
* @param backtest - True if backtest mode, false if live mode
|
|
14969
14970
|
* @returns Configured ClientRisk instance
|
|
14970
14971
|
*/
|
|
14971
|
-
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
14972
|
+
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$z(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
14972
14973
|
const schema = this.riskSchemaService.get(riskName);
|
|
14973
14974
|
return new ClientRisk({
|
|
14974
14975
|
...schema,
|
|
@@ -15058,7 +15059,7 @@ class RiskConnectionService {
|
|
|
15058
15059
|
payload,
|
|
15059
15060
|
});
|
|
15060
15061
|
if (payload) {
|
|
15061
|
-
const key = CREATE_KEY_FN$
|
|
15062
|
+
const key = CREATE_KEY_FN$z(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
15062
15063
|
this.getRisk.clear(key);
|
|
15063
15064
|
}
|
|
15064
15065
|
else {
|
|
@@ -16177,7 +16178,7 @@ class ClientAction {
|
|
|
16177
16178
|
* @param backtest - Whether running in backtest mode
|
|
16178
16179
|
* @returns Unique string key for memoization
|
|
16179
16180
|
*/
|
|
16180
|
-
const CREATE_KEY_FN$
|
|
16181
|
+
const CREATE_KEY_FN$y = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
16181
16182
|
const parts = [actionName, strategyName, exchangeName];
|
|
16182
16183
|
if (frameName)
|
|
16183
16184
|
parts.push(frameName);
|
|
@@ -16229,7 +16230,7 @@ class ActionConnectionService {
|
|
|
16229
16230
|
* @param backtest - True if backtest mode, false if live mode
|
|
16230
16231
|
* @returns Configured ClientAction instance
|
|
16231
16232
|
*/
|
|
16232
|
-
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
16233
|
+
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$y(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
16233
16234
|
const schema = this.actionSchemaService.get(actionName);
|
|
16234
16235
|
return new ClientAction({
|
|
16235
16236
|
...schema,
|
|
@@ -16455,7 +16456,7 @@ class ActionConnectionService {
|
|
|
16455
16456
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
16456
16457
|
return;
|
|
16457
16458
|
}
|
|
16458
|
-
const key = CREATE_KEY_FN$
|
|
16459
|
+
const key = CREATE_KEY_FN$y(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
16459
16460
|
if (!this.getAction.has(key)) {
|
|
16460
16461
|
return;
|
|
16461
16462
|
}
|
|
@@ -16466,14 +16467,14 @@ class ActionConnectionService {
|
|
|
16466
16467
|
}
|
|
16467
16468
|
}
|
|
16468
16469
|
|
|
16469
|
-
const METHOD_NAME_VALIDATE$
|
|
16470
|
+
const METHOD_NAME_VALIDATE$6 = "exchangeCoreService validate";
|
|
16470
16471
|
/**
|
|
16471
16472
|
* Creates a unique key for memoizing validate calls.
|
|
16472
16473
|
* Key format: "exchangeName"
|
|
16473
16474
|
* @param exchangeName - Exchange name
|
|
16474
16475
|
* @returns Unique string key for memoization
|
|
16475
16476
|
*/
|
|
16476
|
-
const CREATE_KEY_FN$
|
|
16477
|
+
const CREATE_KEY_FN$x = (exchangeName) => {
|
|
16477
16478
|
return exchangeName;
|
|
16478
16479
|
};
|
|
16479
16480
|
/**
|
|
@@ -16497,11 +16498,11 @@ class ExchangeCoreService {
|
|
|
16497
16498
|
* @param exchangeName - Name of the exchange to validate
|
|
16498
16499
|
* @returns Promise that resolves when validation is complete
|
|
16499
16500
|
*/
|
|
16500
|
-
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
16501
|
-
this.loggerService.log(METHOD_NAME_VALIDATE$
|
|
16501
|
+
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$x(exchangeName), async (exchangeName) => {
|
|
16502
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$6, {
|
|
16502
16503
|
exchangeName,
|
|
16503
16504
|
});
|
|
16504
|
-
this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$
|
|
16505
|
+
this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$6);
|
|
16505
16506
|
});
|
|
16506
16507
|
/**
|
|
16507
16508
|
* Fetches historical candles with execution context.
|
|
@@ -16770,14 +16771,14 @@ class ExchangeCoreService {
|
|
|
16770
16771
|
}
|
|
16771
16772
|
}
|
|
16772
16773
|
|
|
16773
|
-
const METHOD_NAME_VALIDATE$
|
|
16774
|
+
const METHOD_NAME_VALIDATE$5 = "strategyCoreService validate";
|
|
16774
16775
|
/**
|
|
16775
16776
|
* Creates a unique key for memoizing validate calls.
|
|
16776
16777
|
* Key format: "strategyName:exchangeName:frameName"
|
|
16777
16778
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
16778
16779
|
* @returns Unique string key for memoization
|
|
16779
16780
|
*/
|
|
16780
|
-
const CREATE_KEY_FN$
|
|
16781
|
+
const CREATE_KEY_FN$w = (context) => {
|
|
16781
16782
|
const parts = [context.strategyName, context.exchangeName];
|
|
16782
16783
|
if (context.frameName)
|
|
16783
16784
|
parts.push(context.frameName);
|
|
@@ -16800,6 +16801,7 @@ class StrategyCoreService {
|
|
|
16800
16801
|
this.strategyValidationService = inject(TYPES.strategyValidationService);
|
|
16801
16802
|
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
16802
16803
|
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
16804
|
+
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
16803
16805
|
/**
|
|
16804
16806
|
* Validates strategy and associated risk configuration.
|
|
16805
16807
|
*
|
|
@@ -16809,16 +16811,17 @@ class StrategyCoreService {
|
|
|
16809
16811
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
16810
16812
|
* @returns Promise that resolves when validation is complete
|
|
16811
16813
|
*/
|
|
16812
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
16813
|
-
this.loggerService.log(METHOD_NAME_VALIDATE$
|
|
16814
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$w(context), async (context) => {
|
|
16815
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$5, {
|
|
16814
16816
|
context,
|
|
16815
16817
|
});
|
|
16816
|
-
const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
|
|
16817
|
-
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$
|
|
16818
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$
|
|
16819
|
-
context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$
|
|
16820
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$
|
|
16821
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$
|
|
16818
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
16819
|
+
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$5);
|
|
16820
|
+
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$5);
|
|
16821
|
+
context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$5);
|
|
16822
|
+
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$5);
|
|
16823
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$5));
|
|
16824
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE$5));
|
|
16822
16825
|
});
|
|
16823
16826
|
/**
|
|
16824
16827
|
* Retrieves the currently active pending signal for the symbol.
|
|
@@ -18179,7 +18182,7 @@ class SizingGlobalService {
|
|
|
18179
18182
|
* @param context - Context with riskName, exchangeName, frameName
|
|
18180
18183
|
* @returns Unique string key for memoization
|
|
18181
18184
|
*/
|
|
18182
|
-
const CREATE_KEY_FN$
|
|
18185
|
+
const CREATE_KEY_FN$v = (context) => {
|
|
18183
18186
|
const parts = [context.riskName, context.exchangeName];
|
|
18184
18187
|
if (context.frameName)
|
|
18185
18188
|
parts.push(context.frameName);
|
|
@@ -18205,7 +18208,7 @@ class RiskGlobalService {
|
|
|
18205
18208
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
18206
18209
|
* @returns Promise that resolves when validation is complete
|
|
18207
18210
|
*/
|
|
18208
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
18211
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$v(context), async (context) => {
|
|
18209
18212
|
this.loggerService.log("riskGlobalService validate", {
|
|
18210
18213
|
context,
|
|
18211
18214
|
});
|
|
@@ -18297,14 +18300,14 @@ class RiskGlobalService {
|
|
|
18297
18300
|
}
|
|
18298
18301
|
}
|
|
18299
18302
|
|
|
18300
|
-
const METHOD_NAME_VALIDATE$
|
|
18303
|
+
const METHOD_NAME_VALIDATE$4 = "actionCoreService validate";
|
|
18301
18304
|
/**
|
|
18302
18305
|
* Creates a unique key for memoizing validate calls.
|
|
18303
18306
|
* Key format: "strategyName:exchangeName:frameName"
|
|
18304
18307
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
18305
18308
|
* @returns Unique string key for memoization
|
|
18306
18309
|
*/
|
|
18307
|
-
const CREATE_KEY_FN$
|
|
18310
|
+
const CREATE_KEY_FN$u = (context) => {
|
|
18308
18311
|
const parts = [context.strategyName, context.exchangeName];
|
|
18309
18312
|
if (context.frameName)
|
|
18310
18313
|
parts.push(context.frameName);
|
|
@@ -18348,17 +18351,17 @@ class ActionCoreService {
|
|
|
18348
18351
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
18349
18352
|
* @returns Promise that resolves when all validations complete
|
|
18350
18353
|
*/
|
|
18351
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
18352
|
-
this.loggerService.log(METHOD_NAME_VALIDATE$
|
|
18354
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$u(context), async (context) => {
|
|
18355
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$4, {
|
|
18353
18356
|
context,
|
|
18354
18357
|
});
|
|
18355
18358
|
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
18356
|
-
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$
|
|
18357
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$
|
|
18358
|
-
context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$
|
|
18359
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$
|
|
18360
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$
|
|
18361
|
-
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE$
|
|
18359
|
+
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$4);
|
|
18360
|
+
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$4);
|
|
18361
|
+
context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$4);
|
|
18362
|
+
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$4);
|
|
18363
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$4));
|
|
18364
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE$4));
|
|
18362
18365
|
});
|
|
18363
18366
|
/**
|
|
18364
18367
|
* Initializes all ClientAction instances for the strategy.
|
|
@@ -18854,8 +18857,8 @@ class FrameSchemaService {
|
|
|
18854
18857
|
if (typeof frameSchema.frameName !== "string") {
|
|
18855
18858
|
throw new Error(`frame schema validation failed: missing frameName`);
|
|
18856
18859
|
}
|
|
18857
|
-
if (typeof frameSchema.interval !== "string") {
|
|
18858
|
-
throw new Error(`frame schema validation failed:
|
|
18860
|
+
if (frameSchema.interval && typeof frameSchema.interval !== "string") {
|
|
18861
|
+
throw new Error(`frame schema validation failed: invalid interval for frameName=${frameSchema.frameName}`);
|
|
18859
18862
|
}
|
|
18860
18863
|
if (!(frameSchema.startDate instanceof Date)) {
|
|
18861
18864
|
throw new Error(`frame schema validation failed: missing startDate for frameName=${frameSchema.frameName}`);
|
|
@@ -20848,6 +20851,17 @@ class WalkerLogicPublicService {
|
|
|
20848
20851
|
}
|
|
20849
20852
|
|
|
20850
20853
|
const METHOD_NAME_RUN$2 = "liveCommandService run";
|
|
20854
|
+
const METHOD_NAME_VALIDATE$3 = "liveCommandService validate";
|
|
20855
|
+
/**
|
|
20856
|
+
* Creates a unique key for memoizing validate calls.
|
|
20857
|
+
* Key format: "strategyName:exchangeName:frameName"
|
|
20858
|
+
* @param context - Context with strategyName, exchangeName, frameName
|
|
20859
|
+
* @returns Unique string key for memoization
|
|
20860
|
+
*/
|
|
20861
|
+
const CREATE_KEY_FN$t = (context) => {
|
|
20862
|
+
const parts = [context.strategyName, context.exchangeName];
|
|
20863
|
+
return parts.join(":");
|
|
20864
|
+
};
|
|
20851
20865
|
/**
|
|
20852
20866
|
* Global service providing access to live trading functionality.
|
|
20853
20867
|
*
|
|
@@ -20863,6 +20877,25 @@ class LiveCommandService {
|
|
|
20863
20877
|
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
20864
20878
|
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
20865
20879
|
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
20880
|
+
/**
|
|
20881
|
+
* Validates strategy and associated risk configuration.
|
|
20882
|
+
* Memoized to avoid redundant validations for the same strategy-exchange combination.
|
|
20883
|
+
*
|
|
20884
|
+
* @param context - Context with strategyName, exchangeName
|
|
20885
|
+
* @param methodName - Name of the calling method for error tracking
|
|
20886
|
+
*/
|
|
20887
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$t(context), (context, methodName) => {
|
|
20888
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$3, {
|
|
20889
|
+
context,
|
|
20890
|
+
methodName,
|
|
20891
|
+
});
|
|
20892
|
+
this.strategyValidationService.validate(context.strategyName, methodName);
|
|
20893
|
+
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
20894
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
20895
|
+
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
20896
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
20897
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, methodName));
|
|
20898
|
+
});
|
|
20866
20899
|
/**
|
|
20867
20900
|
* Runs live trading for a symbol with context propagation.
|
|
20868
20901
|
*
|
|
@@ -20877,22 +20910,26 @@ class LiveCommandService {
|
|
|
20877
20910
|
symbol,
|
|
20878
20911
|
context,
|
|
20879
20912
|
});
|
|
20880
|
-
|
|
20881
|
-
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_RUN$2);
|
|
20882
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN$2);
|
|
20883
|
-
}
|
|
20884
|
-
{
|
|
20885
|
-
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
20886
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$2);
|
|
20887
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$2));
|
|
20888
|
-
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_RUN$2));
|
|
20889
|
-
}
|
|
20913
|
+
this.validate(context, METHOD_NAME_RUN$2);
|
|
20890
20914
|
return this.liveLogicPublicService.run(symbol, context);
|
|
20891
20915
|
};
|
|
20892
20916
|
}
|
|
20893
20917
|
}
|
|
20894
20918
|
|
|
20895
20919
|
const METHOD_NAME_RUN$1 = "backtestCommandService run";
|
|
20920
|
+
const METHOD_NAME_VALIDATE$2 = "backtestCommandService validate";
|
|
20921
|
+
/**
|
|
20922
|
+
* Creates a unique key for memoizing validate calls.
|
|
20923
|
+
* Key format: "strategyName:exchangeName:frameName"
|
|
20924
|
+
* @param context - Context with strategyName, exchangeName, frameName
|
|
20925
|
+
* @returns Unique string key for memoization
|
|
20926
|
+
*/
|
|
20927
|
+
const CREATE_KEY_FN$s = (context) => {
|
|
20928
|
+
const parts = [context.strategyName, context.exchangeName];
|
|
20929
|
+
if (context.frameName)
|
|
20930
|
+
parts.push(context.frameName);
|
|
20931
|
+
return parts.join(":");
|
|
20932
|
+
};
|
|
20896
20933
|
/**
|
|
20897
20934
|
* Global service providing access to backtest functionality.
|
|
20898
20935
|
*
|
|
@@ -20909,6 +20946,26 @@ class BacktestCommandService {
|
|
|
20909
20946
|
this.strategyValidationService = inject(TYPES.strategyValidationService);
|
|
20910
20947
|
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
20911
20948
|
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
20949
|
+
/**
|
|
20950
|
+
* Validates strategy and associated risk configuration.
|
|
20951
|
+
* Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
|
|
20952
|
+
*
|
|
20953
|
+
* @param context - Context with strategyName, exchangeName and frameName
|
|
20954
|
+
* @param methodName - Name of the calling method for error tracking
|
|
20955
|
+
*/
|
|
20956
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$s(context), (context, methodName) => {
|
|
20957
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
20958
|
+
context,
|
|
20959
|
+
methodName,
|
|
20960
|
+
});
|
|
20961
|
+
this.strategyValidationService.validate(context.strategyName, methodName);
|
|
20962
|
+
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
20963
|
+
this.frameValidationService.validate(context.frameName, methodName);
|
|
20964
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
20965
|
+
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
20966
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
20967
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, methodName));
|
|
20968
|
+
});
|
|
20912
20969
|
/**
|
|
20913
20970
|
* Runs backtest for a symbol with context propagation.
|
|
20914
20971
|
*
|
|
@@ -20921,23 +20978,26 @@ class BacktestCommandService {
|
|
|
20921
20978
|
symbol,
|
|
20922
20979
|
context,
|
|
20923
20980
|
});
|
|
20924
|
-
|
|
20925
|
-
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_RUN$1);
|
|
20926
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN$1);
|
|
20927
|
-
this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN$1);
|
|
20928
|
-
}
|
|
20929
|
-
{
|
|
20930
|
-
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
20931
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1);
|
|
20932
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1));
|
|
20933
|
-
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_RUN$1));
|
|
20934
|
-
}
|
|
20981
|
+
this.validate(context, METHOD_NAME_RUN$1);
|
|
20935
20982
|
return this.backtestLogicPublicService.run(symbol, context);
|
|
20936
20983
|
};
|
|
20937
20984
|
}
|
|
20938
20985
|
}
|
|
20939
20986
|
|
|
20940
20987
|
const METHOD_NAME_RUN = "walkerCommandService run";
|
|
20988
|
+
const METHOD_NAME_VALIDATE$1 = "walkerCommandService validate";
|
|
20989
|
+
/**
|
|
20990
|
+
* Creates a unique key for memoizing validate calls.
|
|
20991
|
+
* Key format: "walkerName:exchangeName:frameName"
|
|
20992
|
+
* @param context - Context with walkerName, exchangeName, frameName
|
|
20993
|
+
* @returns Unique string key for memoization
|
|
20994
|
+
*/
|
|
20995
|
+
const CREATE_KEY_FN$r = (context) => {
|
|
20996
|
+
const parts = [context.walkerName, context.exchangeName];
|
|
20997
|
+
if (context.frameName)
|
|
20998
|
+
parts.push(context.frameName);
|
|
20999
|
+
return parts.join(":");
|
|
21000
|
+
};
|
|
20941
21001
|
/**
|
|
20942
21002
|
* Global service providing access to walker functionality.
|
|
20943
21003
|
*
|
|
@@ -20956,6 +21016,34 @@ class WalkerCommandService {
|
|
|
20956
21016
|
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
20957
21017
|
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
20958
21018
|
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
21019
|
+
/**
|
|
21020
|
+
* Validates walker and associated strategy configurations.
|
|
21021
|
+
* Memoized to avoid redundant validations for the same walker-exchange-frame combination.
|
|
21022
|
+
*
|
|
21023
|
+
* Strategy/risk/action validation is performed explicitly here in addition to the
|
|
21024
|
+
* cascade inside WalkerValidationService — this is critical-path code and the
|
|
21025
|
+
* redundant check is intentional defense-in-depth.
|
|
21026
|
+
*
|
|
21027
|
+
* @param context - Context with walkerName, exchangeName and frameName
|
|
21028
|
+
* @param methodName - Name of the calling method for error tracking
|
|
21029
|
+
*/
|
|
21030
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$r(context), (context, methodName) => {
|
|
21031
|
+
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
21032
|
+
context,
|
|
21033
|
+
methodName,
|
|
21034
|
+
});
|
|
21035
|
+
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
21036
|
+
this.frameValidationService.validate(context.frameName, methodName);
|
|
21037
|
+
this.walkerValidationService.validate(context.walkerName, methodName);
|
|
21038
|
+
const walkerSchema = this.walkerSchemaService.get(context.walkerName);
|
|
21039
|
+
for (const strategyName of walkerSchema.strategies) {
|
|
21040
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(strategyName);
|
|
21041
|
+
this.strategyValidationService.validate(strategyName, methodName);
|
|
21042
|
+
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
21043
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
21044
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, methodName));
|
|
21045
|
+
}
|
|
21046
|
+
});
|
|
20959
21047
|
/**
|
|
20960
21048
|
* Runs walker comparison for a symbol with context propagation.
|
|
20961
21049
|
*
|
|
@@ -20967,26 +21055,30 @@ class WalkerCommandService {
|
|
|
20967
21055
|
symbol,
|
|
20968
21056
|
context,
|
|
20969
21057
|
});
|
|
20970
|
-
|
|
20971
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN);
|
|
20972
|
-
this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN);
|
|
20973
|
-
this.walkerValidationService.validate(context.walkerName, METHOD_NAME_RUN);
|
|
20974
|
-
}
|
|
20975
|
-
{
|
|
20976
|
-
const walkerSchema = this.walkerSchemaService.get(context.walkerName);
|
|
20977
|
-
for (const strategyName of walkerSchema.strategies) {
|
|
20978
|
-
const { riskName, riskList, actions } = this.strategySchemaService.get(strategyName);
|
|
20979
|
-
this.strategyValidationService.validate(strategyName, METHOD_NAME_RUN);
|
|
20980
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN);
|
|
20981
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN));
|
|
20982
|
-
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_RUN));
|
|
20983
|
-
}
|
|
20984
|
-
}
|
|
21058
|
+
this.validate(context, METHOD_NAME_RUN);
|
|
20985
21059
|
return this.walkerLogicPublicService.run(symbol, context);
|
|
20986
21060
|
};
|
|
20987
21061
|
}
|
|
20988
21062
|
}
|
|
20989
21063
|
|
|
21064
|
+
/**
|
|
21065
|
+
* Derives the number of decimal places to show for a price based on the
|
|
21066
|
+
* magnitude of its integer part. Larger prices need fewer decimals; sub-dollar
|
|
21067
|
+
* prices keep full precision.
|
|
21068
|
+
* @param value - The price to derive the decimal scale for
|
|
21069
|
+
* @returns The number of digits after the decimal point
|
|
21070
|
+
*/
|
|
21071
|
+
const getPriceScale = (value) => {
|
|
21072
|
+
const abs = Math.abs(value);
|
|
21073
|
+
if (abs >= 1) {
|
|
21074
|
+
// 1..9 -> 4, 10..99 -> 3, 100..999 -> 2, 1000+ -> 2 (floor), capped at 2
|
|
21075
|
+
const digits = Math.floor(Math.log10(abs)) + 1;
|
|
21076
|
+
return Math.max(2, 6 - digits);
|
|
21077
|
+
}
|
|
21078
|
+
// Sub-dollar prices need more precision.
|
|
21079
|
+
return 8;
|
|
21080
|
+
};
|
|
21081
|
+
|
|
20990
21082
|
/**
|
|
20991
21083
|
* Converts markdown content to plain text with minimal formatting
|
|
20992
21084
|
* @param content - Markdown string to convert
|
|
@@ -21095,43 +21187,43 @@ const backtest_columns = [
|
|
|
21095
21187
|
{
|
|
21096
21188
|
key: "openPrice",
|
|
21097
21189
|
label: "Open Price",
|
|
21098
|
-
format: (data) => `${data.signal.priceOpen.toFixed(
|
|
21190
|
+
format: (data) => `${data.signal.priceOpen.toFixed(getPriceScale(data.signal.priceOpen))} USD`,
|
|
21099
21191
|
isVisible: () => true,
|
|
21100
21192
|
},
|
|
21101
21193
|
{
|
|
21102
21194
|
key: "closePrice",
|
|
21103
21195
|
label: "Close Price",
|
|
21104
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
21196
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
21105
21197
|
isVisible: () => true,
|
|
21106
21198
|
},
|
|
21107
21199
|
{
|
|
21108
21200
|
key: "takeProfit",
|
|
21109
21201
|
label: "Take Profit",
|
|
21110
|
-
format: (data) => `${data.signal.priceTakeProfit.toFixed(
|
|
21202
|
+
format: (data) => `${data.signal.priceTakeProfit.toFixed(getPriceScale(data.signal.priceTakeProfit))} USD`,
|
|
21111
21203
|
isVisible: () => true,
|
|
21112
21204
|
},
|
|
21113
21205
|
{
|
|
21114
21206
|
key: "stopLoss",
|
|
21115
21207
|
label: "Stop Loss",
|
|
21116
|
-
format: (data) => `${data.signal.priceStopLoss.toFixed(
|
|
21208
|
+
format: (data) => `${data.signal.priceStopLoss.toFixed(getPriceScale(data.signal.priceStopLoss))} USD`,
|
|
21117
21209
|
isVisible: () => true,
|
|
21118
21210
|
},
|
|
21119
21211
|
{
|
|
21120
21212
|
key: "originalPriceTakeProfit",
|
|
21121
21213
|
label: "Original TP",
|
|
21122
|
-
format: (data) => `${data.signal.originalPriceTakeProfit.toFixed(
|
|
21214
|
+
format: (data) => `${data.signal.originalPriceTakeProfit.toFixed(getPriceScale(data.signal.originalPriceTakeProfit))} USD`,
|
|
21123
21215
|
isVisible: () => true,
|
|
21124
21216
|
},
|
|
21125
21217
|
{
|
|
21126
21218
|
key: "originalPriceStopLoss",
|
|
21127
21219
|
label: "Original SL",
|
|
21128
|
-
format: (data) => `${data.signal.originalPriceStopLoss.toFixed(
|
|
21220
|
+
format: (data) => `${data.signal.originalPriceStopLoss.toFixed(getPriceScale(data.signal.originalPriceStopLoss))} USD`,
|
|
21129
21221
|
isVisible: () => true,
|
|
21130
21222
|
},
|
|
21131
21223
|
{
|
|
21132
21224
|
key: "originalPriceOpen",
|
|
21133
21225
|
label: "Original Entry",
|
|
21134
|
-
format: (data) => `${data.signal.originalPriceOpen.toFixed(
|
|
21226
|
+
format: (data) => `${data.signal.originalPriceOpen.toFixed(getPriceScale(data.signal.originalPriceOpen))} USD`,
|
|
21135
21227
|
isVisible: () => true,
|
|
21136
21228
|
},
|
|
21137
21229
|
{
|
|
@@ -21434,6 +21526,71 @@ const heat_columns = [
|
|
|
21434
21526
|
: "N/A",
|
|
21435
21527
|
isVisible: () => true,
|
|
21436
21528
|
},
|
|
21529
|
+
{
|
|
21530
|
+
key: "trend",
|
|
21531
|
+
label: "Trend",
|
|
21532
|
+
format: (data) => data.trend ?? "N/A",
|
|
21533
|
+
isVisible: () => true,
|
|
21534
|
+
},
|
|
21535
|
+
{
|
|
21536
|
+
key: "trendStrength",
|
|
21537
|
+
label: "Trend %/d",
|
|
21538
|
+
format: (data) => data.trendStrength !== null ? functoolsKit.str(data.trendStrength, "%") : "N/A",
|
|
21539
|
+
isVisible: () => true,
|
|
21540
|
+
},
|
|
21541
|
+
{
|
|
21542
|
+
key: "trendConfidence",
|
|
21543
|
+
label: "Trend R²",
|
|
21544
|
+
format: (data) => data.trendConfidence !== null ? data.trendConfidence.toFixed(3) : "N/A",
|
|
21545
|
+
isVisible: () => true,
|
|
21546
|
+
},
|
|
21547
|
+
{
|
|
21548
|
+
key: "buyerPressure",
|
|
21549
|
+
label: "Buyer Pres",
|
|
21550
|
+
format: (data) => data.buyerPressure !== null
|
|
21551
|
+
? (data.buyerPressure * 100).toFixed(1) + "%"
|
|
21552
|
+
: "N/A",
|
|
21553
|
+
isVisible: () => true,
|
|
21554
|
+
},
|
|
21555
|
+
{
|
|
21556
|
+
key: "sellerPressure",
|
|
21557
|
+
label: "Seller Pres",
|
|
21558
|
+
format: (data) => data.sellerPressure !== null
|
|
21559
|
+
? (data.sellerPressure * 100).toFixed(1) + "%"
|
|
21560
|
+
: "N/A",
|
|
21561
|
+
isVisible: () => true,
|
|
21562
|
+
},
|
|
21563
|
+
{
|
|
21564
|
+
key: "buyerStrength",
|
|
21565
|
+
label: "Buyer Str",
|
|
21566
|
+
format: (data) => data.buyerStrength !== null
|
|
21567
|
+
? (data.buyerStrength * 100).toFixed(1) + "%"
|
|
21568
|
+
: "N/A",
|
|
21569
|
+
isVisible: () => true,
|
|
21570
|
+
},
|
|
21571
|
+
{
|
|
21572
|
+
key: "sellerStrength",
|
|
21573
|
+
label: "Seller Str",
|
|
21574
|
+
format: (data) => data.sellerStrength !== null
|
|
21575
|
+
? (data.sellerStrength * 100).toFixed(1) + "%"
|
|
21576
|
+
: "N/A",
|
|
21577
|
+
isVisible: () => true,
|
|
21578
|
+
},
|
|
21579
|
+
{
|
|
21580
|
+
key: "pressureImbalance",
|
|
21581
|
+
label: "Pres Imb",
|
|
21582
|
+
format: (data) => data.pressureImbalance !== null
|
|
21583
|
+
? (data.pressureImbalance > 0 ? "+" : "") +
|
|
21584
|
+
data.pressureImbalance.toFixed(3)
|
|
21585
|
+
: "N/A",
|
|
21586
|
+
isVisible: () => true,
|
|
21587
|
+
},
|
|
21588
|
+
{
|
|
21589
|
+
key: "medianStepSize",
|
|
21590
|
+
label: "Median Step",
|
|
21591
|
+
format: (data) => data.medianStepSize !== null ? functoolsKit.str(data.medianStepSize, "%") : "N/A",
|
|
21592
|
+
isVisible: () => true,
|
|
21593
|
+
},
|
|
21437
21594
|
{
|
|
21438
21595
|
key: "sortinoRatio",
|
|
21439
21596
|
label: "Sortino",
|
|
@@ -21530,34 +21687,34 @@ const live_columns = [
|
|
|
21530
21687
|
{
|
|
21531
21688
|
key: "currentPrice",
|
|
21532
21689
|
label: "Current Price",
|
|
21533
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
21690
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
21534
21691
|
isVisible: () => true,
|
|
21535
21692
|
},
|
|
21536
21693
|
{
|
|
21537
21694
|
key: "openPrice",
|
|
21538
21695
|
label: "Open Price",
|
|
21539
|
-
format: (data) => data.priceOpen !== undefined ? `${data.priceOpen.toFixed(
|
|
21696
|
+
format: (data) => data.priceOpen !== undefined ? `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD` : "N/A",
|
|
21540
21697
|
isVisible: () => true,
|
|
21541
21698
|
},
|
|
21542
21699
|
{
|
|
21543
21700
|
key: "takeProfit",
|
|
21544
21701
|
label: "Take Profit",
|
|
21545
21702
|
format: (data) => data.priceTakeProfit !== undefined
|
|
21546
|
-
? `${data.priceTakeProfit.toFixed(
|
|
21703
|
+
? `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`
|
|
21547
21704
|
: "N/A",
|
|
21548
21705
|
isVisible: () => true,
|
|
21549
21706
|
},
|
|
21550
21707
|
{
|
|
21551
21708
|
key: "stopLoss",
|
|
21552
21709
|
label: "Stop Loss",
|
|
21553
|
-
format: (data) => data.priceStopLoss !== undefined ? `${data.priceStopLoss.toFixed(
|
|
21710
|
+
format: (data) => data.priceStopLoss !== undefined ? `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD` : "N/A",
|
|
21554
21711
|
isVisible: () => true,
|
|
21555
21712
|
},
|
|
21556
21713
|
{
|
|
21557
21714
|
key: "originalPriceTakeProfit",
|
|
21558
21715
|
label: "Original TP",
|
|
21559
21716
|
format: (data) => data.originalPriceTakeProfit !== undefined
|
|
21560
|
-
? `${data.originalPriceTakeProfit.toFixed(
|
|
21717
|
+
? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD`
|
|
21561
21718
|
: "N/A",
|
|
21562
21719
|
isVisible: () => true,
|
|
21563
21720
|
},
|
|
@@ -21565,7 +21722,7 @@ const live_columns = [
|
|
|
21565
21722
|
key: "originalPriceStopLoss",
|
|
21566
21723
|
label: "Original SL",
|
|
21567
21724
|
format: (data) => data.originalPriceStopLoss !== undefined
|
|
21568
|
-
? `${data.originalPriceStopLoss.toFixed(
|
|
21725
|
+
? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD`
|
|
21569
21726
|
: "N/A",
|
|
21570
21727
|
isVisible: () => true,
|
|
21571
21728
|
},
|
|
@@ -21573,7 +21730,7 @@ const live_columns = [
|
|
|
21573
21730
|
key: "originalPriceOpen",
|
|
21574
21731
|
label: "Original Entry",
|
|
21575
21732
|
format: (data) => data.originalPriceOpen !== undefined
|
|
21576
|
-
? `${data.originalPriceOpen.toFixed(
|
|
21733
|
+
? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD`
|
|
21577
21734
|
: "N/A",
|
|
21578
21735
|
isVisible: () => true,
|
|
21579
21736
|
},
|
|
@@ -21744,43 +21901,43 @@ const partial_columns = [
|
|
|
21744
21901
|
{
|
|
21745
21902
|
key: "currentPrice",
|
|
21746
21903
|
label: "Current Price",
|
|
21747
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
21904
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
21748
21905
|
isVisible: () => true,
|
|
21749
21906
|
},
|
|
21750
21907
|
{
|
|
21751
21908
|
key: "priceOpen",
|
|
21752
21909
|
label: "Entry Price",
|
|
21753
|
-
format: (data) => (data.priceOpen ? `${data.priceOpen.toFixed(
|
|
21910
|
+
format: (data) => (data.priceOpen ? `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD` : "N/A"),
|
|
21754
21911
|
isVisible: () => true,
|
|
21755
21912
|
},
|
|
21756
21913
|
{
|
|
21757
21914
|
key: "priceTakeProfit",
|
|
21758
21915
|
label: "Take Profit",
|
|
21759
|
-
format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(
|
|
21916
|
+
format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD` : "N/A"),
|
|
21760
21917
|
isVisible: () => true,
|
|
21761
21918
|
},
|
|
21762
21919
|
{
|
|
21763
21920
|
key: "priceStopLoss",
|
|
21764
21921
|
label: "Stop Loss",
|
|
21765
|
-
format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(
|
|
21922
|
+
format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD` : "N/A"),
|
|
21766
21923
|
isVisible: () => true,
|
|
21767
21924
|
},
|
|
21768
21925
|
{
|
|
21769
21926
|
key: "originalPriceTakeProfit",
|
|
21770
21927
|
label: "Original TP",
|
|
21771
|
-
format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(
|
|
21928
|
+
format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD` : "N/A"),
|
|
21772
21929
|
isVisible: () => true,
|
|
21773
21930
|
},
|
|
21774
21931
|
{
|
|
21775
21932
|
key: "originalPriceStopLoss",
|
|
21776
21933
|
label: "Original SL",
|
|
21777
|
-
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(
|
|
21934
|
+
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD` : "N/A"),
|
|
21778
21935
|
isVisible: () => true,
|
|
21779
21936
|
},
|
|
21780
21937
|
{
|
|
21781
21938
|
key: "originalPriceOpen",
|
|
21782
21939
|
label: "Original Entry",
|
|
21783
|
-
format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(
|
|
21940
|
+
format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD` : "N/A"),
|
|
21784
21941
|
isVisible: () => true,
|
|
21785
21942
|
},
|
|
21786
21943
|
{
|
|
@@ -21904,43 +22061,43 @@ const breakeven_columns = [
|
|
|
21904
22061
|
{
|
|
21905
22062
|
key: "priceOpen",
|
|
21906
22063
|
label: "Entry Price",
|
|
21907
|
-
format: (data) => `${data.priceOpen.toFixed(
|
|
22064
|
+
format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
|
|
21908
22065
|
isVisible: () => true,
|
|
21909
22066
|
},
|
|
21910
22067
|
{
|
|
21911
22068
|
key: "currentPrice",
|
|
21912
22069
|
label: "Breakeven Price",
|
|
21913
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
22070
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
21914
22071
|
isVisible: () => true,
|
|
21915
22072
|
},
|
|
21916
22073
|
{
|
|
21917
22074
|
key: "priceTakeProfit",
|
|
21918
22075
|
label: "Take Profit",
|
|
21919
|
-
format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(
|
|
22076
|
+
format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD` : "N/A"),
|
|
21920
22077
|
isVisible: () => true,
|
|
21921
22078
|
},
|
|
21922
22079
|
{
|
|
21923
22080
|
key: "priceStopLoss",
|
|
21924
22081
|
label: "Stop Loss",
|
|
21925
|
-
format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(
|
|
22082
|
+
format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD` : "N/A"),
|
|
21926
22083
|
isVisible: () => true,
|
|
21927
22084
|
},
|
|
21928
22085
|
{
|
|
21929
22086
|
key: "originalPriceTakeProfit",
|
|
21930
22087
|
label: "Original TP",
|
|
21931
|
-
format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(
|
|
22088
|
+
format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD` : "N/A"),
|
|
21932
22089
|
isVisible: () => true,
|
|
21933
22090
|
},
|
|
21934
22091
|
{
|
|
21935
22092
|
key: "originalPriceStopLoss",
|
|
21936
22093
|
label: "Original SL",
|
|
21937
|
-
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(
|
|
22094
|
+
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD` : "N/A"),
|
|
21938
22095
|
isVisible: () => true,
|
|
21939
22096
|
},
|
|
21940
22097
|
{
|
|
21941
22098
|
key: "originalPriceOpen",
|
|
21942
22099
|
label: "Original Entry",
|
|
21943
|
-
format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(
|
|
22100
|
+
format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD` : "N/A"),
|
|
21944
22101
|
isVisible: () => true,
|
|
21945
22102
|
},
|
|
21946
22103
|
{
|
|
@@ -22197,7 +22354,7 @@ const risk_columns = [
|
|
|
22197
22354
|
key: "openPrice",
|
|
22198
22355
|
label: "Open Price",
|
|
22199
22356
|
format: (data) => data.currentSignal.priceOpen !== undefined
|
|
22200
|
-
? `${data.currentSignal.priceOpen.toFixed(
|
|
22357
|
+
? `${data.currentSignal.priceOpen.toFixed(getPriceScale(data.currentSignal.priceOpen))} USD`
|
|
22201
22358
|
: "N/A",
|
|
22202
22359
|
isVisible: () => true,
|
|
22203
22360
|
},
|
|
@@ -22205,7 +22362,7 @@ const risk_columns = [
|
|
|
22205
22362
|
key: "takeProfit",
|
|
22206
22363
|
label: "Take Profit",
|
|
22207
22364
|
format: (data) => data.currentSignal.priceTakeProfit !== undefined
|
|
22208
|
-
? `${data.currentSignal.priceTakeProfit.toFixed(
|
|
22365
|
+
? `${data.currentSignal.priceTakeProfit.toFixed(getPriceScale(data.currentSignal.priceTakeProfit))} USD`
|
|
22209
22366
|
: "N/A",
|
|
22210
22367
|
isVisible: () => true,
|
|
22211
22368
|
},
|
|
@@ -22213,7 +22370,7 @@ const risk_columns = [
|
|
|
22213
22370
|
key: "stopLoss",
|
|
22214
22371
|
label: "Stop Loss",
|
|
22215
22372
|
format: (data) => data.currentSignal.priceStopLoss !== undefined
|
|
22216
|
-
? `${data.currentSignal.priceStopLoss.toFixed(
|
|
22373
|
+
? `${data.currentSignal.priceStopLoss.toFixed(getPriceScale(data.currentSignal.priceStopLoss))} USD`
|
|
22217
22374
|
: "N/A",
|
|
22218
22375
|
isVisible: () => true,
|
|
22219
22376
|
},
|
|
@@ -22221,7 +22378,7 @@ const risk_columns = [
|
|
|
22221
22378
|
key: "originalPriceTakeProfit",
|
|
22222
22379
|
label: "Original TP",
|
|
22223
22380
|
format: (data) => data.currentSignal.originalPriceTakeProfit !== undefined
|
|
22224
|
-
? `${data.currentSignal.originalPriceTakeProfit.toFixed(
|
|
22381
|
+
? `${data.currentSignal.originalPriceTakeProfit.toFixed(getPriceScale(data.currentSignal.originalPriceTakeProfit))} USD`
|
|
22225
22382
|
: "N/A",
|
|
22226
22383
|
isVisible: () => true,
|
|
22227
22384
|
},
|
|
@@ -22229,7 +22386,7 @@ const risk_columns = [
|
|
|
22229
22386
|
key: "originalPriceStopLoss",
|
|
22230
22387
|
label: "Original SL",
|
|
22231
22388
|
format: (data) => data.currentSignal.originalPriceStopLoss !== undefined
|
|
22232
|
-
? `${data.currentSignal.originalPriceStopLoss.toFixed(
|
|
22389
|
+
? `${data.currentSignal.originalPriceStopLoss.toFixed(getPriceScale(data.currentSignal.originalPriceStopLoss))} USD`
|
|
22233
22390
|
: "N/A",
|
|
22234
22391
|
isVisible: () => true,
|
|
22235
22392
|
},
|
|
@@ -22237,7 +22394,7 @@ const risk_columns = [
|
|
|
22237
22394
|
key: "originalPriceOpen",
|
|
22238
22395
|
label: "Original Entry",
|
|
22239
22396
|
format: (data) => data.currentSignal.originalPriceOpen !== undefined
|
|
22240
|
-
? `${data.currentSignal.originalPriceOpen.toFixed(
|
|
22397
|
+
? `${data.currentSignal.originalPriceOpen.toFixed(getPriceScale(data.currentSignal.originalPriceOpen))} USD`
|
|
22241
22398
|
: "N/A",
|
|
22242
22399
|
isVisible: () => true,
|
|
22243
22400
|
},
|
|
@@ -22268,7 +22425,7 @@ const risk_columns = [
|
|
|
22268
22425
|
{
|
|
22269
22426
|
key: "currentPrice",
|
|
22270
22427
|
label: "Current Price",
|
|
22271
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
22428
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
22272
22429
|
isVisible: () => true,
|
|
22273
22430
|
},
|
|
22274
22431
|
{
|
|
@@ -22394,32 +22551,32 @@ const schedule_columns = [
|
|
|
22394
22551
|
{
|
|
22395
22552
|
key: "currentPrice",
|
|
22396
22553
|
label: "Current Price",
|
|
22397
|
-
format: (data) => data.currentPrice ? `${data.currentPrice.toFixed(
|
|
22554
|
+
format: (data) => data.currentPrice ? `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD` : "N/A",
|
|
22398
22555
|
isVisible: () => true,
|
|
22399
22556
|
},
|
|
22400
22557
|
{
|
|
22401
22558
|
key: "priceOpen",
|
|
22402
22559
|
label: "Entry Price",
|
|
22403
|
-
format: (data) => `${data.priceOpen.toFixed(
|
|
22560
|
+
format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
|
|
22404
22561
|
isVisible: () => true,
|
|
22405
22562
|
},
|
|
22406
22563
|
{
|
|
22407
22564
|
key: "takeProfit",
|
|
22408
22565
|
label: "Take Profit",
|
|
22409
|
-
format: (data) => `${data.priceTakeProfit.toFixed(
|
|
22566
|
+
format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
|
|
22410
22567
|
isVisible: () => true,
|
|
22411
22568
|
},
|
|
22412
22569
|
{
|
|
22413
22570
|
key: "stopLoss",
|
|
22414
22571
|
label: "Stop Loss",
|
|
22415
|
-
format: (data) => `${data.priceStopLoss.toFixed(
|
|
22572
|
+
format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
|
|
22416
22573
|
isVisible: () => true,
|
|
22417
22574
|
},
|
|
22418
22575
|
{
|
|
22419
22576
|
key: "originalPriceTakeProfit",
|
|
22420
22577
|
label: "Original TP",
|
|
22421
22578
|
format: (data) => data.originalPriceTakeProfit !== undefined
|
|
22422
|
-
? `${data.originalPriceTakeProfit.toFixed(
|
|
22579
|
+
? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD`
|
|
22423
22580
|
: "N/A",
|
|
22424
22581
|
isVisible: () => true,
|
|
22425
22582
|
},
|
|
@@ -22427,7 +22584,7 @@ const schedule_columns = [
|
|
|
22427
22584
|
key: "originalPriceStopLoss",
|
|
22428
22585
|
label: "Original SL",
|
|
22429
22586
|
format: (data) => data.originalPriceStopLoss !== undefined
|
|
22430
|
-
? `${data.originalPriceStopLoss.toFixed(
|
|
22587
|
+
? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD`
|
|
22431
22588
|
: "N/A",
|
|
22432
22589
|
isVisible: () => true,
|
|
22433
22590
|
},
|
|
@@ -22435,7 +22592,7 @@ const schedule_columns = [
|
|
|
22435
22592
|
key: "originalPriceOpen",
|
|
22436
22593
|
label: "Original Entry",
|
|
22437
22594
|
format: (data) => data.originalPriceOpen !== undefined
|
|
22438
|
-
? `${data.originalPriceOpen.toFixed(
|
|
22595
|
+
? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD`
|
|
22439
22596
|
: "N/A",
|
|
22440
22597
|
isVisible: () => true,
|
|
22441
22598
|
},
|
|
@@ -22560,7 +22717,7 @@ const strategy_columns = [
|
|
|
22560
22717
|
{
|
|
22561
22718
|
key: "currentPrice",
|
|
22562
22719
|
label: "Price",
|
|
22563
|
-
format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(
|
|
22720
|
+
format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD` : "N/A"),
|
|
22564
22721
|
isVisible: () => true,
|
|
22565
22722
|
},
|
|
22566
22723
|
{
|
|
@@ -22680,25 +22837,25 @@ const sync_columns = [
|
|
|
22680
22837
|
{
|
|
22681
22838
|
key: "currentPrice",
|
|
22682
22839
|
label: "Current Price",
|
|
22683
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
22840
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
22684
22841
|
isVisible: () => true,
|
|
22685
22842
|
},
|
|
22686
22843
|
{
|
|
22687
22844
|
key: "priceOpen",
|
|
22688
22845
|
label: "Entry Price",
|
|
22689
|
-
format: (data) => `${data.priceOpen.toFixed(
|
|
22846
|
+
format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
|
|
22690
22847
|
isVisible: () => true,
|
|
22691
22848
|
},
|
|
22692
22849
|
{
|
|
22693
22850
|
key: "priceTakeProfit",
|
|
22694
22851
|
label: "Take Profit",
|
|
22695
|
-
format: (data) => `${data.priceTakeProfit.toFixed(
|
|
22852
|
+
format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
|
|
22696
22853
|
isVisible: () => true,
|
|
22697
22854
|
},
|
|
22698
22855
|
{
|
|
22699
22856
|
key: "priceStopLoss",
|
|
22700
22857
|
label: "Stop Loss",
|
|
22701
|
-
format: (data) => `${data.priceStopLoss.toFixed(
|
|
22858
|
+
format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
|
|
22702
22859
|
isVisible: () => true,
|
|
22703
22860
|
},
|
|
22704
22861
|
{
|
|
@@ -22791,25 +22948,25 @@ const highest_profit_columns = [
|
|
|
22791
22948
|
{
|
|
22792
22949
|
key: "currentPrice",
|
|
22793
22950
|
label: "Peak Price",
|
|
22794
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
22951
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
22795
22952
|
isVisible: () => true,
|
|
22796
22953
|
},
|
|
22797
22954
|
{
|
|
22798
22955
|
key: "priceOpen",
|
|
22799
22956
|
label: "Entry Price",
|
|
22800
|
-
format: (data) => `${data.priceOpen.toFixed(
|
|
22957
|
+
format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
|
|
22801
22958
|
isVisible: () => true,
|
|
22802
22959
|
},
|
|
22803
22960
|
{
|
|
22804
22961
|
key: "priceTakeProfit",
|
|
22805
22962
|
label: "Take Profit",
|
|
22806
|
-
format: (data) => `${data.priceTakeProfit.toFixed(
|
|
22963
|
+
format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
|
|
22807
22964
|
isVisible: () => true,
|
|
22808
22965
|
},
|
|
22809
22966
|
{
|
|
22810
22967
|
key: "priceStopLoss",
|
|
22811
22968
|
label: "Stop Loss",
|
|
22812
|
-
format: (data) => `${data.priceStopLoss.toFixed(
|
|
22969
|
+
format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
|
|
22813
22970
|
isVisible: () => true,
|
|
22814
22971
|
},
|
|
22815
22972
|
{
|
|
@@ -22878,25 +23035,25 @@ const max_drawdown_columns = [
|
|
|
22878
23035
|
{
|
|
22879
23036
|
key: "currentPrice",
|
|
22880
23037
|
label: "DD Price",
|
|
22881
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
23038
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
22882
23039
|
isVisible: () => true,
|
|
22883
23040
|
},
|
|
22884
23041
|
{
|
|
22885
23042
|
key: "priceOpen",
|
|
22886
23043
|
label: "Entry Price",
|
|
22887
|
-
format: (data) => `${data.priceOpen.toFixed(
|
|
23044
|
+
format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
|
|
22888
23045
|
isVisible: () => true,
|
|
22889
23046
|
},
|
|
22890
23047
|
{
|
|
22891
23048
|
key: "priceTakeProfit",
|
|
22892
23049
|
label: "Take Profit",
|
|
22893
|
-
format: (data) => `${data.priceTakeProfit.toFixed(
|
|
23050
|
+
format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
|
|
22894
23051
|
isVisible: () => true,
|
|
22895
23052
|
},
|
|
22896
23053
|
{
|
|
22897
23054
|
key: "priceStopLoss",
|
|
22898
23055
|
label: "Stop Loss",
|
|
22899
|
-
format: (data) => `${data.priceStopLoss.toFixed(
|
|
23056
|
+
format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
|
|
22900
23057
|
isVisible: () => true,
|
|
22901
23058
|
},
|
|
22902
23059
|
{
|
|
@@ -23778,6 +23935,184 @@ MarkdownFileBase = functoolsKit.makeExtendable(MarkdownFileBase);
|
|
|
23778
23935
|
ReportBase = functoolsKit.makeExtendable(ReportBase);
|
|
23779
23936
|
const ReportWriter = new ReportWriterAdapter();
|
|
23780
23937
|
|
|
23938
|
+
/**
|
|
23939
|
+
* Price-profile metrics derived purely from a series of trade closes — no
|
|
23940
|
+
* candles, no exchange queries. Designed for `BacktestMarkdownService`,
|
|
23941
|
+
* `LiveMarkdownService` and per-symbol Heat: all three already have a
|
|
23942
|
+
* chronological series of `(closeAt, close)` points and nothing else.
|
|
23943
|
+
*
|
|
23944
|
+
* Conventions
|
|
23945
|
+
* -----------
|
|
23946
|
+
* - Pressure: fraction of up-moves vs down-moves (frequency).
|
|
23947
|
+
* - Strength: fraction of upward magnitude vs total movement.
|
|
23948
|
+
* - A divergence between pressure and strength surfaces asymmetry — e.g.
|
|
23949
|
+
* frequent shallow up-moves vs rare deep down-moves is "rising on weak
|
|
23950
|
+
* buys, falling on strong sells".
|
|
23951
|
+
* - Trend: linear regression of log(close) vs days. Slope in %/day,
|
|
23952
|
+
* confidence in R². Classification is bivariate (slope × R²): neither
|
|
23953
|
+
* axis alone fires, both must agree. Slope threshold is normalised by
|
|
23954
|
+
* medianStepSize so the metric self-tunes to the instrument's typical
|
|
23955
|
+
* move size.
|
|
23956
|
+
*/
|
|
23957
|
+
/** Minimum samples to surface any price-profile metric. Below this the
|
|
23958
|
+
* per-trade step-distribution and the regression are statistically noisy. */
|
|
23959
|
+
const MIN_SIGNALS = 10;
|
|
23960
|
+
/** R² gate for declaring any trend at all. Below this the regression is
|
|
23961
|
+
* too weak to claim a direction — treat the series as sideways even if
|
|
23962
|
+
* the slope is large. 0.30 is the conventional weak-to-moderate-fit
|
|
23963
|
+
* boundary in econometrics (Cohen's f² ≈ 0.43). */
|
|
23964
|
+
const R2_TREND_GATE = 0.30;
|
|
23965
|
+
/** Slope-magnitude threshold relative to medianStepSize for declaring the
|
|
23966
|
+
* trend strong enough to call bullish/bearish. Below this the regression
|
|
23967
|
+
* fits but the actual drift is weaker than the typical daily step, so we
|
|
23968
|
+
* downgrade to "neutral" (a real but uninteresting tilt). */
|
|
23969
|
+
const SLOPE_VS_STEP_GATE = 0.25;
|
|
23970
|
+
const isFiniteNumber = (v) => typeof v === "number" && Number.isFinite(v);
|
|
23971
|
+
const median = (values) => {
|
|
23972
|
+
const sorted = values.slice().sort((a, b) => a - b);
|
|
23973
|
+
const mid = sorted.length >> 1;
|
|
23974
|
+
return sorted.length % 2 === 0
|
|
23975
|
+
? (sorted[mid - 1] + sorted[mid]) / 2
|
|
23976
|
+
: sorted[mid];
|
|
23977
|
+
};
|
|
23978
|
+
const emptyProfile = () => ({
|
|
23979
|
+
medianStepSize: null,
|
|
23980
|
+
buyerPressure: null,
|
|
23981
|
+
sellerPressure: null,
|
|
23982
|
+
buyerStrength: null,
|
|
23983
|
+
sellerStrength: null,
|
|
23984
|
+
pressureImbalance: null,
|
|
23985
|
+
trend: null,
|
|
23986
|
+
trendStrength: null,
|
|
23987
|
+
trendConfidence: null,
|
|
23988
|
+
});
|
|
23989
|
+
/**
|
|
23990
|
+
* Computes the price-profile bundle for a chronological series of trade
|
|
23991
|
+
* closes. The input is expected to be sorted by `closeAt` ascending; the
|
|
23992
|
+
* function does not sort defensively (the markdown services already sort).
|
|
23993
|
+
*
|
|
23994
|
+
* @param series - One point per closed trade: timestamp (ms) and close price.
|
|
23995
|
+
* @returns A bundle of nine metrics, each `null` when the input is too small
|
|
23996
|
+
* or numerically unsafe.
|
|
23997
|
+
*/
|
|
23998
|
+
const getPriceProfile = (series) => {
|
|
23999
|
+
const valid = series.filter((p) => isFiniteNumber(p.closeAt) &&
|
|
24000
|
+
p.closeAt > 0 &&
|
|
24001
|
+
isFiniteNumber(p.close) &&
|
|
24002
|
+
p.close > 0);
|
|
24003
|
+
if (valid.length < MIN_SIGNALS)
|
|
24004
|
+
return emptyProfile();
|
|
24005
|
+
const n = valid.length;
|
|
24006
|
+
const closes = valid.map((p) => p.close);
|
|
24007
|
+
const times = valid.map((p) => p.closeAt);
|
|
24008
|
+
const absSteps = [];
|
|
24009
|
+
let upMoves = 0;
|
|
24010
|
+
let downMoves = 0;
|
|
24011
|
+
let upMagnitude = 0;
|
|
24012
|
+
let downMagnitude = 0;
|
|
24013
|
+
for (let i = 1; i < n; i++) {
|
|
24014
|
+
const prev = closes[i - 1];
|
|
24015
|
+
const cur = closes[i];
|
|
24016
|
+
const ret = (cur - prev) / prev;
|
|
24017
|
+
const abs = Math.abs(ret);
|
|
24018
|
+
absSteps.push(abs);
|
|
24019
|
+
if (ret > 0) {
|
|
24020
|
+
upMoves++;
|
|
24021
|
+
upMagnitude += abs;
|
|
24022
|
+
}
|
|
24023
|
+
else if (ret < 0) {
|
|
24024
|
+
downMoves++;
|
|
24025
|
+
downMagnitude += abs;
|
|
24026
|
+
}
|
|
24027
|
+
}
|
|
24028
|
+
if (absSteps.length === 0)
|
|
24029
|
+
return emptyProfile();
|
|
24030
|
+
const medianStepSize = median(absSteps) * 100; // percent
|
|
24031
|
+
// --- Pressure / strength ---
|
|
24032
|
+
const decisiveMoves = upMoves + downMoves;
|
|
24033
|
+
const totalMagnitude = upMagnitude + downMagnitude;
|
|
24034
|
+
const buyerPressure = decisiveMoves > 0 ? upMoves / decisiveMoves : null;
|
|
24035
|
+
const sellerPressure = decisiveMoves > 0 ? downMoves / decisiveMoves : null;
|
|
24036
|
+
const buyerStrength = totalMagnitude > 0 ? upMagnitude / totalMagnitude : null;
|
|
24037
|
+
const sellerStrength = totalMagnitude > 0 ? downMagnitude / totalMagnitude : null;
|
|
24038
|
+
const pressureImbalance = buyerStrength !== null && sellerStrength !== null
|
|
24039
|
+
? buyerStrength - sellerStrength
|
|
24040
|
+
: null;
|
|
24041
|
+
// --- Trend: linear regression of log(close) vs days ---
|
|
24042
|
+
// log-price slope is scale-invariant: 1%/day means the same whether the
|
|
24043
|
+
// asset trades at $0.01 or $10000. Use `closeAt[0]` as time origin so x_i
|
|
24044
|
+
// starts at zero — keeps numerical conditioning sane on long horizons.
|
|
24045
|
+
const t0 = times[0];
|
|
24046
|
+
const xs = new Array(n);
|
|
24047
|
+
const ys = new Array(n);
|
|
24048
|
+
for (let i = 0; i < n; i++) {
|
|
24049
|
+
xs[i] = (times[i] - t0) / (1000 * 60 * 60 * 24); // days
|
|
24050
|
+
ys[i] = Math.log(closes[i]);
|
|
24051
|
+
}
|
|
24052
|
+
// Calendar span must be non-degenerate for the slope to mean anything.
|
|
24053
|
+
const xRange = xs[n - 1] - xs[0];
|
|
24054
|
+
let trend = null;
|
|
24055
|
+
let trendStrength = null;
|
|
24056
|
+
let trendConfidence = null;
|
|
24057
|
+
if (xRange > 0) {
|
|
24058
|
+
let sumX = 0;
|
|
24059
|
+
let sumY = 0;
|
|
24060
|
+
for (let i = 0; i < n; i++) {
|
|
24061
|
+
sumX += xs[i];
|
|
24062
|
+
sumY += ys[i];
|
|
24063
|
+
}
|
|
24064
|
+
const meanX = sumX / n;
|
|
24065
|
+
const meanY = sumY / n;
|
|
24066
|
+
let ssXX = 0;
|
|
24067
|
+
let ssXY = 0;
|
|
24068
|
+
let ssYY = 0;
|
|
24069
|
+
for (let i = 0; i < n; i++) {
|
|
24070
|
+
const dx = xs[i] - meanX;
|
|
24071
|
+
const dy = ys[i] - meanY;
|
|
24072
|
+
ssXX += dx * dx;
|
|
24073
|
+
ssXY += dx * dy;
|
|
24074
|
+
ssYY += dy * dy;
|
|
24075
|
+
}
|
|
24076
|
+
if (ssXX > 0) {
|
|
24077
|
+
const slopeLog = ssXY / ssXX; // log-return per day
|
|
24078
|
+
const slopePct = slopeLog * 100; // %/day (small-slope approx)
|
|
24079
|
+
// R² = 1 - SS_res / SS_tot. With a single explanatory variable this
|
|
24080
|
+
// equals (ssXY)² / (ssXX * ssYY) when ssYY > 0.
|
|
24081
|
+
const r2 = ssYY > 0 ? (ssXY * ssXY) / (ssXX * ssYY) : 0;
|
|
24082
|
+
trendStrength = slopePct;
|
|
24083
|
+
trendConfidence = Math.max(0, Math.min(1, r2));
|
|
24084
|
+
if (trendConfidence < R2_TREND_GATE) {
|
|
24085
|
+
trend = "sideways";
|
|
24086
|
+
}
|
|
24087
|
+
else {
|
|
24088
|
+
const slopeMagnitude = Math.abs(slopePct);
|
|
24089
|
+
const stepScale = medianStepSize * SLOPE_VS_STEP_GATE;
|
|
24090
|
+
if (slopeMagnitude < stepScale) {
|
|
24091
|
+
trend = "neutral";
|
|
24092
|
+
}
|
|
24093
|
+
else if (slopePct > 0) {
|
|
24094
|
+
trend = "bullish";
|
|
24095
|
+
}
|
|
24096
|
+
else {
|
|
24097
|
+
trend = "bearish";
|
|
24098
|
+
}
|
|
24099
|
+
}
|
|
24100
|
+
}
|
|
24101
|
+
}
|
|
24102
|
+
const safe = (v) => v === null || !Number.isFinite(v) ? null : v;
|
|
24103
|
+
return {
|
|
24104
|
+
medianStepSize: safe(medianStepSize),
|
|
24105
|
+
buyerPressure: safe(buyerPressure),
|
|
24106
|
+
sellerPressure: safe(sellerPressure),
|
|
24107
|
+
buyerStrength: safe(buyerStrength),
|
|
24108
|
+
sellerStrength: safe(sellerStrength),
|
|
24109
|
+
pressureImbalance: safe(pressureImbalance),
|
|
24110
|
+
trend,
|
|
24111
|
+
trendStrength: safe(trendStrength),
|
|
24112
|
+
trendConfidence: safe(trendConfidence),
|
|
24113
|
+
};
|
|
24114
|
+
};
|
|
24115
|
+
|
|
23781
24116
|
/**
|
|
23782
24117
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
23783
24118
|
* Key format: "symbol:strategyName:exchangeName:frameName:backtest" or "symbol:strategyName:exchangeName:live"
|
|
@@ -23910,6 +24245,15 @@ let ReportStorage$a = class ReportStorage {
|
|
|
23910
24245
|
avgConsecutiveLossPnl: null,
|
|
23911
24246
|
avgWinDuration: null,
|
|
23912
24247
|
avgLossDuration: null,
|
|
24248
|
+
medianStepSize: null,
|
|
24249
|
+
buyerPressure: null,
|
|
24250
|
+
sellerPressure: null,
|
|
24251
|
+
buyerStrength: null,
|
|
24252
|
+
sellerStrength: null,
|
|
24253
|
+
pressureImbalance: null,
|
|
24254
|
+
trend: null,
|
|
24255
|
+
trendStrength: null,
|
|
24256
|
+
trendConfidence: null,
|
|
23913
24257
|
};
|
|
23914
24258
|
}
|
|
23915
24259
|
// Valid signal set — those with usable pendingAt AND closeTimestamp. Single source
|
|
@@ -24206,6 +24550,13 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24206
24550
|
const recoveryFactor = !canComputeRatios || blown || equityMaxDrawdown <= 0
|
|
24207
24551
|
? null
|
|
24208
24552
|
: Math.max(-MAX_CALMAR_RATIO$2, Math.min(MAX_CALMAR_RATIO$2, ((equityFinal - 1) * 100) / equityMaxDrawdown));
|
|
24553
|
+
// Price profile — buyer/seller pressure, trend classification. Walks the
|
|
24554
|
+
// chronological close series (`orderedSignals` is already sorted by
|
|
24555
|
+
// closeTimestamp). N-gated internally by the helper (MIN_SIGNALS = 10).
|
|
24556
|
+
const priceProfile = getPriceProfile(orderedSignals.map((s) => ({
|
|
24557
|
+
closeAt: s.closeTimestamp,
|
|
24558
|
+
close: s.currentPrice,
|
|
24559
|
+
})));
|
|
24209
24560
|
return {
|
|
24210
24561
|
signalList: this._signalList,
|
|
24211
24562
|
totalSignals,
|
|
@@ -24231,6 +24582,15 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24231
24582
|
avgConsecutiveLossPnl: isUnsafe$4(avgConsecutiveLossPnl) ? null : avgConsecutiveLossPnl,
|
|
24232
24583
|
avgWinDuration: isUnsafe$4(avgWinDuration) ? null : avgWinDuration,
|
|
24233
24584
|
avgLossDuration: isUnsafe$4(avgLossDuration) ? null : avgLossDuration,
|
|
24585
|
+
medianStepSize: priceProfile.medianStepSize,
|
|
24586
|
+
buyerPressure: priceProfile.buyerPressure,
|
|
24587
|
+
sellerPressure: priceProfile.sellerPressure,
|
|
24588
|
+
buyerStrength: priceProfile.buyerStrength,
|
|
24589
|
+
sellerStrength: priceProfile.sellerStrength,
|
|
24590
|
+
pressureImbalance: priceProfile.pressureImbalance,
|
|
24591
|
+
trend: priceProfile.trend,
|
|
24592
|
+
trendStrength: priceProfile.trendStrength,
|
|
24593
|
+
trendConfidence: priceProfile.trendConfidence,
|
|
24234
24594
|
};
|
|
24235
24595
|
}
|
|
24236
24596
|
/**
|
|
@@ -24287,6 +24647,15 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24287
24647
|
`**Avg Loss Duration:** ${stats.avgLossDuration === null ? "N/A" : `${stats.avgLossDuration.toFixed(1)} min`}`,
|
|
24288
24648
|
`**Avg Consecutive Win PNL:** ${stats.avgConsecutiveWinPnl === null ? "N/A" : `${stats.avgConsecutiveWinPnl > 0 ? "+" : ""}${stats.avgConsecutiveWinPnl.toFixed(3)}% (higher is better)`}`,
|
|
24289
24649
|
`**Avg Consecutive Loss PNL:** ${stats.avgConsecutiveLossPnl === null ? "N/A" : `${stats.avgConsecutiveLossPnl.toFixed(3)}% (closer to 0 is better)`}`,
|
|
24650
|
+
`**Trend:** ${stats.trend === null ? "N/A" : stats.trend}`,
|
|
24651
|
+
`**Trend Strength:** ${stats.trendStrength === null ? "N/A" : `${stats.trendStrength > 0 ? "+" : ""}${stats.trendStrength.toFixed(3)}%/day`}`,
|
|
24652
|
+
`**Trend Confidence (R²):** ${stats.trendConfidence === null ? "N/A" : stats.trendConfidence.toFixed(3)}`,
|
|
24653
|
+
`**Buyer Pressure:** ${stats.buyerPressure === null ? "N/A" : `${(stats.buyerPressure * 100).toFixed(1)}%`}`,
|
|
24654
|
+
`**Seller Pressure:** ${stats.sellerPressure === null ? "N/A" : `${(stats.sellerPressure * 100).toFixed(1)}%`}`,
|
|
24655
|
+
`**Buyer Strength:** ${stats.buyerStrength === null ? "N/A" : `${(stats.buyerStrength * 100).toFixed(1)}%`}`,
|
|
24656
|
+
`**Seller Strength:** ${stats.sellerStrength === null ? "N/A" : `${(stats.sellerStrength * 100).toFixed(1)}%`}`,
|
|
24657
|
+
`**Pressure Imbalance:** ${stats.pressureImbalance === null ? "N/A" : `${stats.pressureImbalance > 0 ? "+" : ""}${stats.pressureImbalance.toFixed(3)}`}`,
|
|
24658
|
+
`**Median Step Size:** ${stats.medianStepSize === null ? "N/A" : `${stats.medianStepSize.toFixed(3)}%`}`,
|
|
24290
24659
|
"",
|
|
24291
24660
|
`*Win Rate: reliable above 200+ signals; below 30 signals a single streak can shift it by 10-20%.*`,
|
|
24292
24661
|
`*Sharpe Ratio: below 1.0 is poor, 1.0-2.0 is acceptable, above 2.0 is strong. Requires 30+ signals.*`,
|
|
@@ -24300,6 +24669,11 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24300
24669
|
`*Expectancy: per-trade expected value (winProb × avgWin + lossProb × avgLoss). Positive = profitable on average per trade. Break-even trades contribute 0.*`,
|
|
24301
24670
|
`*All metrics require 100+ signals to be statistically reliable. Annualized metrics assume the observed trading frequency and market conditions persist year-round.*`,
|
|
24302
24671
|
`*IMPORTANT: Equity curve, Expected Yearly Returns, Calmar, Recovery and Max Drawdown all assume **100% capital allocation per position** (no portfolio fraction). These metrics ignore the position-sizing subsystem (PositionSize / Kelly / ATR): pnlPercentage is a return on the position's own invested capital, never scaled by account balance. With DCA (commitAverageBuy) the cost basis is the sum of all entries and the entry price is dollar-cost-weighted, so per-trade % is measured against the averaged position, not a fixed stake. If your strategy risks X% of capital per trade, the realized portfolio return / drawdown will be roughly X/100 of the reported figures — these metrics represent a theoretical upper bound under full allocation.*`,
|
|
24672
|
+
`*Trend / Trend Strength / Trend Confidence: linear regression of log(close) vs days across closed-trade prices. "Trend Strength" is the slope in %/day; "Trend Confidence" is R². Classification gates on R² ≥ 0.30 (else "sideways") AND |slope| ≥ 0.25 × medianStepSize (else "neutral" — a real but weak tilt). Self-tunes to the instrument's typical move size — no magic constants on price magnitude.*`,
|
|
24673
|
+
`*Buyer / Seller Pressure: fraction of up-moves (resp. down-moves) among decisive close-to-close changes. Frequency-based; flats excluded.*`,
|
|
24674
|
+
`*Buyer / Seller Strength: share of upward (resp. downward) absolute movement in total movement. Magnitude-based. A divergence between pressure and strength surfaces asymmetry: "frequent shallow buys, rare deep sells" is a different regime than "rare deep buys, frequent shallow sells".*`,
|
|
24675
|
+
`*Pressure Imbalance: buyerStrength − sellerStrength ∈ [−1, +1]. Single signed summary of magnitude bias.*`,
|
|
24676
|
+
`*Median Step Size: median |close[i] − close[i−1]| / close[i−1] across closed trades, in %. Robust to outliers — describes the typical close-to-close jump. Used as the scale that normalises the slope-vs-step trend gate. NOTE: this is NOT classical (candle-based) volatility — there are no candles between trade closes in the report data; it measures step-distribution at the rate trades close.*`,
|
|
24303
24677
|
`*Negative values for Sharpe / Sortino / Calmar / Recovery / Expected Yearly Returns indicate a losing strategy (avgPnl < 0 or totalPnl < 0). "Higher is better" still applies — closer to zero is less bad, positive is profitable.*`,
|
|
24304
24678
|
].join("\n");
|
|
24305
24679
|
}
|
|
@@ -24930,6 +25304,15 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
24930
25304
|
avgConsecutiveLossPnl: null,
|
|
24931
25305
|
avgWinDuration: null,
|
|
24932
25306
|
avgLossDuration: null,
|
|
25307
|
+
medianStepSize: null,
|
|
25308
|
+
buyerPressure: null,
|
|
25309
|
+
sellerPressure: null,
|
|
25310
|
+
buyerStrength: null,
|
|
25311
|
+
sellerStrength: null,
|
|
25312
|
+
pressureImbalance: null,
|
|
25313
|
+
trend: null,
|
|
25314
|
+
trendStrength: null,
|
|
25315
|
+
trendConfidence: null,
|
|
24933
25316
|
};
|
|
24934
25317
|
}
|
|
24935
25318
|
const closedEvents = this._eventList.filter((e) => e.action === "closed");
|
|
@@ -25228,6 +25611,14 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25228
25611
|
const recoveryFactor = !canComputeRatios || blown || equityMaxDrawdown <= 0
|
|
25229
25612
|
? null
|
|
25230
25613
|
: Math.max(-MAX_CALMAR_RATIO$1, Math.min(MAX_CALMAR_RATIO$1, ((equityFinal - 1) * 100) / equityMaxDrawdown));
|
|
25614
|
+
// Price profile — buyer/seller pressure, trend classification. Built only
|
|
25615
|
+
// from closed events' (timestamp, currentPrice) pairs in chronological
|
|
25616
|
+
// close order — the same data the equity curve uses. N-gated internally
|
|
25617
|
+
// by the helper (MIN_SIGNALS = 10).
|
|
25618
|
+
const priceProfile = getPriceProfile(validClosed
|
|
25619
|
+
.slice()
|
|
25620
|
+
.sort((a, b) => a.timestamp - b.timestamp)
|
|
25621
|
+
.map((e) => ({ closeAt: e.timestamp, close: e.currentPrice })));
|
|
25231
25622
|
return {
|
|
25232
25623
|
eventList: this._eventList,
|
|
25233
25624
|
totalEvents: this._eventList.length,
|
|
@@ -25254,6 +25645,15 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25254
25645
|
avgConsecutiveLossPnl: isUnsafe$3(avgConsecutiveLossPnl) ? null : avgConsecutiveLossPnl,
|
|
25255
25646
|
avgWinDuration: isUnsafe$3(avgWinDuration) ? null : avgWinDuration,
|
|
25256
25647
|
avgLossDuration: isUnsafe$3(avgLossDuration) ? null : avgLossDuration,
|
|
25648
|
+
medianStepSize: priceProfile.medianStepSize,
|
|
25649
|
+
buyerPressure: priceProfile.buyerPressure,
|
|
25650
|
+
sellerPressure: priceProfile.sellerPressure,
|
|
25651
|
+
buyerStrength: priceProfile.buyerStrength,
|
|
25652
|
+
sellerStrength: priceProfile.sellerStrength,
|
|
25653
|
+
pressureImbalance: priceProfile.pressureImbalance,
|
|
25654
|
+
trend: priceProfile.trend,
|
|
25655
|
+
trendStrength: priceProfile.trendStrength,
|
|
25656
|
+
trendConfidence: priceProfile.trendConfidence,
|
|
25257
25657
|
};
|
|
25258
25658
|
}
|
|
25259
25659
|
/**
|
|
@@ -25310,6 +25710,15 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25310
25710
|
`**Avg Loss Duration:** ${stats.avgLossDuration === null ? "N/A" : `${stats.avgLossDuration.toFixed(1)} min`}`,
|
|
25311
25711
|
`**Avg Consecutive Win PNL:** ${stats.avgConsecutiveWinPnl === null ? "N/A" : `${stats.avgConsecutiveWinPnl > 0 ? "+" : ""}${stats.avgConsecutiveWinPnl.toFixed(3)}% (higher is better)`}`,
|
|
25312
25712
|
`**Avg Consecutive Loss PNL:** ${stats.avgConsecutiveLossPnl === null ? "N/A" : `${stats.avgConsecutiveLossPnl.toFixed(3)}% (closer to 0 is better)`}`,
|
|
25713
|
+
`**Trend:** ${stats.trend === null ? "N/A" : stats.trend}`,
|
|
25714
|
+
`**Trend Strength:** ${stats.trendStrength === null ? "N/A" : `${stats.trendStrength > 0 ? "+" : ""}${stats.trendStrength.toFixed(3)}%/day`}`,
|
|
25715
|
+
`**Trend Confidence (R²):** ${stats.trendConfidence === null ? "N/A" : stats.trendConfidence.toFixed(3)}`,
|
|
25716
|
+
`**Buyer Pressure:** ${stats.buyerPressure === null ? "N/A" : `${(stats.buyerPressure * 100).toFixed(1)}%`}`,
|
|
25717
|
+
`**Seller Pressure:** ${stats.sellerPressure === null ? "N/A" : `${(stats.sellerPressure * 100).toFixed(1)}%`}`,
|
|
25718
|
+
`**Buyer Strength:** ${stats.buyerStrength === null ? "N/A" : `${(stats.buyerStrength * 100).toFixed(1)}%`}`,
|
|
25719
|
+
`**Seller Strength:** ${stats.sellerStrength === null ? "N/A" : `${(stats.sellerStrength * 100).toFixed(1)}%`}`,
|
|
25720
|
+
`**Pressure Imbalance:** ${stats.pressureImbalance === null ? "N/A" : `${stats.pressureImbalance > 0 ? "+" : ""}${stats.pressureImbalance.toFixed(3)}`}`,
|
|
25721
|
+
`**Median Step Size:** ${stats.medianStepSize === null ? "N/A" : `${stats.medianStepSize.toFixed(3)}%`}`,
|
|
25313
25722
|
"",
|
|
25314
25723
|
`*Win Rate: reliable above 200+ signals; below 30 signals a single streak can shift it by 10-20%.*`,
|
|
25315
25724
|
`*Sharpe Ratio: below 1.0 is poor, 1.0-2.0 is acceptable, above 2.0 is strong. Requires 30+ signals.*`,
|
|
@@ -25323,6 +25732,11 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25323
25732
|
`*Expectancy: per-trade expected value (winProb × avgWin + lossProb × avgLoss). Positive = profitable on average per trade. Break-even trades contribute 0.*`,
|
|
25324
25733
|
`*All metrics require 100+ signals to be statistically reliable. Annualized metrics assume the observed trading frequency and market conditions persist year-round.*`,
|
|
25325
25734
|
`*IMPORTANT: Equity curve, Expected Yearly Returns, Calmar, Recovery and Max Drawdown all assume **100% capital allocation per position** (no portfolio fraction). These metrics ignore the position-sizing subsystem (PositionSize / Kelly / ATR): pnlPercentage is a return on the position's own invested capital, never scaled by account balance. With DCA (commitAverageBuy) the cost basis is the sum of all entries and the entry price is dollar-cost-weighted, so per-trade % is measured against the averaged position, not a fixed stake. If your strategy risks X% of capital per trade, the realized portfolio return / drawdown will be roughly X/100 of the reported figures — these metrics represent a theoretical upper bound under full allocation.*`,
|
|
25735
|
+
`*Trend / Trend Strength / Trend Confidence: linear regression of log(close) vs days across closed-trade prices. "Trend Strength" is the slope in %/day; "Trend Confidence" is R². Classification gates on R² ≥ 0.30 (else "sideways") AND |slope| ≥ 0.25 × medianStepSize (else "neutral" — a real but weak tilt). Self-tunes to the instrument's typical move size — no magic constants on price magnitude.*`,
|
|
25736
|
+
`*Buyer / Seller Pressure: fraction of up-moves (resp. down-moves) among decisive close-to-close changes. Frequency-based; flats excluded.*`,
|
|
25737
|
+
`*Buyer / Seller Strength: share of upward (resp. downward) absolute movement in total movement. Magnitude-based. A divergence between pressure and strength surfaces asymmetry: "frequent shallow buys, rare deep sells" is a different regime than "rare deep buys, frequent shallow sells".*`,
|
|
25738
|
+
`*Pressure Imbalance: buyerStrength − sellerStrength ∈ [−1, +1]. Single signed summary of magnitude bias.*`,
|
|
25739
|
+
`*Median Step Size: median |close[i] − close[i−1]| / close[i−1] across closed trades, in %. Robust to outliers — describes the typical close-to-close jump. Used as the scale that normalises the slope-vs-step trend gate. NOTE: this is NOT classical (candle-based) volatility — there are no candles between trade closes in the report data; it measures step-distribution at the rate trades close.*`,
|
|
25326
25740
|
`*Negative values for Sharpe / Sortino / Calmar / Recovery / Expected Yearly Returns indicate a losing strategy (avgPnl < 0 or totalPnl < 0). "Higher is better" still applies — closer to zero is less bad, positive is profitable.*`,
|
|
25327
25741
|
].join("\n");
|
|
25328
25742
|
}
|
|
@@ -27605,6 +28019,12 @@ class HeatmapStorage {
|
|
|
27605
28019
|
calmarRatio = null;
|
|
27606
28020
|
if (isUnsafe(recoveryFactor))
|
|
27607
28021
|
recoveryFactor = null;
|
|
28022
|
+
// Price profile — buyer/seller pressure, trend classification. Built from
|
|
28023
|
+
// the chronological close-price series of this symbol's closed signals.
|
|
28024
|
+
// N-gated internally by the helper (MIN_SIGNALS = 10).
|
|
28025
|
+
const priceProfile = getPriceProfile([...signals]
|
|
28026
|
+
.sort((a, b) => a.closeTimestamp - b.closeTimestamp)
|
|
28027
|
+
.map((s) => ({ closeAt: s.closeTimestamp, close: s.currentPrice })));
|
|
27608
28028
|
return {
|
|
27609
28029
|
symbol,
|
|
27610
28030
|
totalPnl,
|
|
@@ -27639,6 +28059,15 @@ class HeatmapStorage {
|
|
|
27639
28059
|
certaintyRatio,
|
|
27640
28060
|
expectedYearlyReturns,
|
|
27641
28061
|
tradesPerYear,
|
|
28062
|
+
medianStepSize: priceProfile.medianStepSize,
|
|
28063
|
+
buyerPressure: priceProfile.buyerPressure,
|
|
28064
|
+
sellerPressure: priceProfile.sellerPressure,
|
|
28065
|
+
buyerStrength: priceProfile.buyerStrength,
|
|
28066
|
+
sellerStrength: priceProfile.sellerStrength,
|
|
28067
|
+
pressureImbalance: priceProfile.pressureImbalance,
|
|
28068
|
+
trend: priceProfile.trend,
|
|
28069
|
+
trendStrength: priceProfile.trendStrength,
|
|
28070
|
+
trendConfidence: priceProfile.trendConfidence,
|
|
27642
28071
|
};
|
|
27643
28072
|
}
|
|
27644
28073
|
/**
|
|
@@ -28102,6 +28531,9 @@ class HeatmapStorage {
|
|
|
28102
28531
|
`*Max Drawdown: mark-to-market — both the per-symbol and pooled equity curves apply each trade's worst intra-trade excursion (the lowest unrealized point while the position was open) before booking its realized close, so deep round-trip dips count. It is NOT realized-only (close-to-close); a realized-only curve would understate drawdown and inflate Calmar/Recovery. The pooled curve walks trades chronologically by closeTimestamp; simultaneous cross-symbol drawdowns within the same minute are still serialised (one trade applied at a time), so genuine same-instant tail correlation is not modelled.*`,
|
|
28103
28532
|
`*All metrics require 100+ signals per symbol to be statistically reliable. Annualized metrics assume the observed trading frequency persists year-round.*`,
|
|
28104
28533
|
`*IMPORTANT: Per-symbol equity curve, Expected Yearly Returns, Calmar, Recovery and Max Drawdown all assume **100% capital allocation per position** (no portfolio fraction). These metrics ignore the position-sizing subsystem (PositionSize / Kelly / ATR): pnlPercentage is a return on the position's own invested capital, never scaled by account balance. With DCA (commitAverageBuy) the cost basis is the sum of all entries and the entry price is dollar-cost-weighted, so per-trade % is measured against the averaged position, not a fixed stake. If your strategy risks X% of capital per trade, the realized return / drawdown will be roughly X/100 of the reported figures — these metrics represent a theoretical upper bound under full allocation.*`,
|
|
28534
|
+
`*Trend / Trend %/d / Trend R² (per-symbol only): linear regression of log(close) vs days across that symbol's closed-trade prices. Classification gates on R² ≥ 0.30 AND |slope| ≥ 0.25 × medianStepSize — self-tunes to the instrument. Not aggregated portfolio-wide (price series across symbols are not comparable).*`,
|
|
28535
|
+
`*Buyer / Seller Pres (per-symbol only): fraction of up-moves (resp. down-moves) among decisive close-to-close changes for that symbol. Buyer / Seller Str: magnitude share. Pres Imb: buyerStrength − sellerStrength ∈ [−1, +1].*`,
|
|
28536
|
+
`*Median Step (per-symbol only): typical |close[i] − close[i−1]| / close[i−1] across closed trades, in %. Robust to outliers. NOT classical volatility — there are no candles between closes in the report data.*`,
|
|
28105
28537
|
`*Negative values for Sharpe / Annualized Sharpe / Sortino / Calmar / Recovery / Expectancy / Expected Yearly Returns indicate a losing symbol (avgPnl < 0 or totalPnl < 0). "Higher is better" still applies — closer to zero is less bad, positive is profitable.*`,
|
|
28106
28538
|
].join("\n");
|
|
28107
28539
|
}
|
|
@@ -28704,6 +29136,36 @@ class WalkerValidationService {
|
|
|
28704
29136
|
* Injected logger service instance
|
|
28705
29137
|
*/
|
|
28706
29138
|
this.loggerService = inject(TYPES.loggerService);
|
|
29139
|
+
/**
|
|
29140
|
+
* @private
|
|
29141
|
+
* @readonly
|
|
29142
|
+
* Injected walker schema service instance
|
|
29143
|
+
*/
|
|
29144
|
+
this.walkerSchemaService = inject(TYPES.walkerSchemaService);
|
|
29145
|
+
/**
|
|
29146
|
+
* @private
|
|
29147
|
+
* @readonly
|
|
29148
|
+
* Injected strategy validation service instance
|
|
29149
|
+
*/
|
|
29150
|
+
this.strategyValidationService = inject(TYPES.strategyValidationService);
|
|
29151
|
+
/**
|
|
29152
|
+
* @private
|
|
29153
|
+
* @readonly
|
|
29154
|
+
* Injected strategy schema service instance
|
|
29155
|
+
*/
|
|
29156
|
+
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
29157
|
+
/**
|
|
29158
|
+
* @private
|
|
29159
|
+
* @readonly
|
|
29160
|
+
* Injected risk validation service instance
|
|
29161
|
+
*/
|
|
29162
|
+
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
29163
|
+
/**
|
|
29164
|
+
* @private
|
|
29165
|
+
* @readonly
|
|
29166
|
+
* Injected action validation service instance
|
|
29167
|
+
*/
|
|
29168
|
+
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
28707
29169
|
/**
|
|
28708
29170
|
* @private
|
|
28709
29171
|
* Map storing walker schemas by walker name
|
|
@@ -28725,9 +29187,12 @@ class WalkerValidationService {
|
|
|
28725
29187
|
this._walkerMap.set(walkerName, walkerSchema);
|
|
28726
29188
|
};
|
|
28727
29189
|
/**
|
|
28728
|
-
* Validates the existence of a walker
|
|
29190
|
+
* Validates the existence of a walker and its associated strategy configurations.
|
|
29191
|
+
* Each strategy referenced by the walker is validated via StrategyValidationService,
|
|
29192
|
+
* which in turn validates the strategy's risk profiles and actions.
|
|
28729
29193
|
* @public
|
|
28730
29194
|
* @throws {Error} If walkerName is not found
|
|
29195
|
+
* @throws {Error} If any referenced strategy (or its risk/actions) is invalid
|
|
28731
29196
|
* Memoized function to cache validation results
|
|
28732
29197
|
*/
|
|
28733
29198
|
this.validate = functoolsKit.memoize(([walkerName]) => walkerName, (walkerName, source) => {
|
|
@@ -28739,6 +29204,14 @@ class WalkerValidationService {
|
|
|
28739
29204
|
if (!walker) {
|
|
28740
29205
|
throw new Error(`walker ${walkerName} not found source=${source}`);
|
|
28741
29206
|
}
|
|
29207
|
+
const walkerSchema = this.walkerSchemaService.get(walkerName);
|
|
29208
|
+
for (const strategyName of walkerSchema.strategies) {
|
|
29209
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(strategyName);
|
|
29210
|
+
this.strategyValidationService.validate(strategyName, source);
|
|
29211
|
+
riskName && this.riskValidationService.validate(riskName, source);
|
|
29212
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, source));
|
|
29213
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, source));
|
|
29214
|
+
}
|
|
28742
29215
|
return true;
|
|
28743
29216
|
});
|
|
28744
29217
|
/**
|
|
@@ -30175,6 +30648,10 @@ class PartialGlobalService {
|
|
|
30175
30648
|
* Frame validation service for validating frame existence.
|
|
30176
30649
|
*/
|
|
30177
30650
|
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
30651
|
+
/**
|
|
30652
|
+
* Action validation service for validating action existence.
|
|
30653
|
+
*/
|
|
30654
|
+
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
30178
30655
|
/**
|
|
30179
30656
|
* Validates strategy and associated risk configuration.
|
|
30180
30657
|
* Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
|
|
@@ -30190,9 +30667,10 @@ class PartialGlobalService {
|
|
|
30190
30667
|
this.strategyValidationService.validate(context.strategyName, methodName);
|
|
30191
30668
|
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
30192
30669
|
context.frameName && this.frameValidationService.validate(context.frameName, methodName);
|
|
30193
|
-
const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
|
|
30670
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
30194
30671
|
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
30195
30672
|
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
30673
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, methodName));
|
|
30196
30674
|
});
|
|
30197
30675
|
/**
|
|
30198
30676
|
* Processes profit state and emits events for newly reached profit levels.
|
|
@@ -31259,6 +31737,10 @@ class BreakevenGlobalService {
|
|
|
31259
31737
|
* Frame validation service for validating frame existence.
|
|
31260
31738
|
*/
|
|
31261
31739
|
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
31740
|
+
/**
|
|
31741
|
+
* Action validation service for validating frame existence.
|
|
31742
|
+
*/
|
|
31743
|
+
this.actionValidationService = inject(TYPES.actionValidationService);
|
|
31262
31744
|
/**
|
|
31263
31745
|
* Validates strategy and associated risk configuration.
|
|
31264
31746
|
* Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
|
|
@@ -31274,9 +31756,10 @@ class BreakevenGlobalService {
|
|
|
31274
31756
|
this.strategyValidationService.validate(context.strategyName, methodName);
|
|
31275
31757
|
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
31276
31758
|
context.frameName && this.frameValidationService.validate(context.frameName, methodName);
|
|
31277
|
-
const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
|
|
31759
|
+
const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
|
|
31278
31760
|
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
31279
31761
|
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
31762
|
+
actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, methodName));
|
|
31280
31763
|
});
|
|
31281
31764
|
/**
|
|
31282
31765
|
* Checks if breakeven should be triggered and emits event if conditions met.
|
|
@@ -50618,6 +51101,7 @@ const GET_TIMESTAMP_METHOD_NAME = "meta.getTimestamp";
|
|
|
50618
51101
|
const GET_MODE_METHOD_NAME = "meta.getMode";
|
|
50619
51102
|
const GET_SYMBOL_METHOD_NAME = "meta.getSymbol";
|
|
50620
51103
|
const GET_CONTEXT_METHOD_NAME = "meta.getContext";
|
|
51104
|
+
const GET_RUNTIME_INFO_METHOD_NAME = "meta.getRuntimeInfo";
|
|
50621
51105
|
/**
|
|
50622
51106
|
* Gets the current date from execution context.
|
|
50623
51107
|
*
|
|
@@ -50734,6 +51218,45 @@ async function getContext() {
|
|
|
50734
51218
|
}
|
|
50735
51219
|
return backtest.methodContextService.context;
|
|
50736
51220
|
}
|
|
51221
|
+
/**
|
|
51222
|
+
* Gets runtime information about the current execution environment.
|
|
51223
|
+
*
|
|
51224
|
+
* This includes details such as the current symbol, exchange, timeframe, strategy, and whether it's a backtest or live run.
|
|
51225
|
+
*
|
|
51226
|
+
* @returns Promise resolving to an object containing runtime information
|
|
51227
|
+
* @throws Error if method context or execution context is not active
|
|
51228
|
+
*
|
|
51229
|
+
* @example
|
|
51230
|
+
* ```typescript
|
|
51231
|
+
* const runtimeInfo = await getRuntimeInfo();
|
|
51232
|
+
* console.log(runtimeInfo);
|
|
51233
|
+
* // {
|
|
51234
|
+
* // symbol: "BTCUSDT",
|
|
51235
|
+
* // context: {,
|
|
51236
|
+
* // exchangeName: "Binance",
|
|
51237
|
+
* // frameName: "1m",
|
|
51238
|
+
* // strategyName: "MyStrategy",
|
|
51239
|
+
* // },
|
|
51240
|
+
* // backtest: false
|
|
51241
|
+
* // }
|
|
51242
|
+
* ```
|
|
51243
|
+
*/
|
|
51244
|
+
async function getRuntimeInfo() {
|
|
51245
|
+
backtest.loggerService.info(GET_RUNTIME_INFO_METHOD_NAME);
|
|
51246
|
+
if (!MethodContextService.hasContext()) {
|
|
51247
|
+
throw new Error("getRuntimeInfo requires a method context");
|
|
51248
|
+
}
|
|
51249
|
+
if (!ExecutionContextService.hasContext()) {
|
|
51250
|
+
throw new Error("getRuntimeInfo requires an execution context");
|
|
51251
|
+
}
|
|
51252
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
51253
|
+
const { symbol, backtest: isBacktest } = backtest.executionContextService.context;
|
|
51254
|
+
return await backtest.runtimeMetaService.getRuntimeInfo(symbol, {
|
|
51255
|
+
exchangeName,
|
|
51256
|
+
frameName,
|
|
51257
|
+
strategyName,
|
|
51258
|
+
}, isBacktest);
|
|
51259
|
+
}
|
|
50737
51260
|
|
|
50738
51261
|
const RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistBacktestUtils.handleActivePing";
|
|
50739
51262
|
const RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistBacktestUtils.getLatestSignal";
|
|
@@ -66811,8 +67334,10 @@ exports.getPositionPartials = getPositionPartials;
|
|
|
66811
67334
|
exports.getPositionPnlCost = getPositionPnlCost;
|
|
66812
67335
|
exports.getPositionPnlPercent = getPositionPnlPercent;
|
|
66813
67336
|
exports.getPositionWaitingMinutes = getPositionWaitingMinutes;
|
|
67337
|
+
exports.getPriceScale = getPriceScale;
|
|
66814
67338
|
exports.getRawCandles = getRawCandles;
|
|
66815
67339
|
exports.getRiskSchema = getRiskSchema;
|
|
67340
|
+
exports.getRuntimeInfo = getRuntimeInfo;
|
|
66816
67341
|
exports.getScheduledSignal = getScheduledSignal;
|
|
66817
67342
|
exports.getSessionData = getSessionData;
|
|
66818
67343
|
exports.getSignalState = getSignalState;
|