backtest-kit 1.5.11 → 1.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ var functoolsKit = require('functools-kit');
3
4
  var diKit = require('di-kit');
4
5
  var diScoped = require('di-scoped');
5
- var functoolsKit = require('functools-kit');
6
6
  var fs = require('fs/promises');
7
7
  var path = require('path');
8
8
  var crypto = require('crypto');
@@ -20,12 +20,32 @@ const GLOBAL_CONFIG = {
20
20
  * Default: 5 candles (last 5 minutes when using 1m interval)
21
21
  */
22
22
  CC_AVG_PRICE_CANDLES_COUNT: 5,
23
+ /**
24
+ * Slippage percentage applied to entry and exit prices.
25
+ * Simulates market impact and order book depth.
26
+ * Applied twice (entry and exit) for realistic execution simulation.
27
+ * Default: 0.1% per transaction
28
+ */
29
+ CC_PERCENT_SLIPPAGE: 0.1,
30
+ /**
31
+ * Fee percentage charged per transaction.
32
+ * Applied twice (entry and exit) for total fee calculation.
33
+ * Default: 0.1% per transaction (total 0.2%)
34
+ */
35
+ CC_PERCENT_FEE: 0.1,
23
36
  /**
24
37
  * Minimum TakeProfit distance from priceOpen (percentage)
25
- * Must be greater than trading fees to ensure profitable trades
26
- * Default: 0.3% (covers 2×0.1% fees + minimum profit margin)
38
+ * Must be greater than (slippage + fees) to ensure profitable trades
39
+ *
40
+ * Calculation:
41
+ * - Slippage effect: ~0.2% (0.1% × 2 transactions)
42
+ * - Fees: 0.2% (0.1% × 2 transactions)
43
+ * - Minimum profit buffer: 0.1%
44
+ * - Total: 0.5%
45
+ *
46
+ * Default: 0.5% (covers all costs + minimum profit margin)
27
47
  */
28
- CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: 0.3,
48
+ CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: 0.5,
29
49
  /**
30
50
  * Minimum StopLoss distance from priceOpen (percentage)
31
51
  * Prevents signals from being immediately stopped out due to price volatility
@@ -92,6 +112,7 @@ const GLOBAL_CONFIG = {
92
112
  */
93
113
  CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN: 5,
94
114
  };
115
+ const DEFAULT_CONFIG = Object.freeze({ ...GLOBAL_CONFIG });
95
116
 
96
117
  const { init, inject, provide } = diKit.createActivator("backtest");
97
118
 
@@ -188,6 +209,7 @@ const validationServices$1 = {
188
209
  sizingValidationService: Symbol('sizingValidationService'),
189
210
  riskValidationService: Symbol('riskValidationService'),
190
211
  optimizerValidationService: Symbol('optimizerValidationService'),
212
+ configValidationService: Symbol('configValidationService'),
191
213
  };
192
214
  const templateServices$1 = {
193
215
  optimizerTemplateService: Symbol('optimizerTemplateService'),
@@ -717,16 +739,6 @@ class ExchangeConnectionService {
717
739
  }
718
740
  }
719
741
 
720
- /**
721
- * Slippage percentage applied to entry and exit prices.
722
- * Simulates market impact and order book depth.
723
- */
724
- const PERCENT_SLIPPAGE = 0.1;
725
- /**
726
- * Fee percentage charged per transaction.
727
- * Applied twice (entry and exit) for total fee calculation.
728
- */
729
- const PERCENT_FEE = 0.1;
730
742
  /**
731
743
  * Calculates profit/loss for a closed signal with slippage and fees.
732
744
  *
@@ -762,16 +774,16 @@ const toProfitLossDto = (signal, priceClose) => {
762
774
  let priceCloseWithSlippage;
763
775
  if (signal.position === "long") {
764
776
  // LONG: покупаем дороже, продаем дешевле
765
- priceOpenWithSlippage = priceOpen * (1 + PERCENT_SLIPPAGE / 100);
766
- priceCloseWithSlippage = priceClose * (1 - PERCENT_SLIPPAGE / 100);
777
+ priceOpenWithSlippage = priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
778
+ priceCloseWithSlippage = priceClose * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
767
779
  }
768
780
  else {
769
781
  // SHORT: продаем дешевле, покупаем дороже
770
- priceOpenWithSlippage = priceOpen * (1 - PERCENT_SLIPPAGE / 100);
771
- priceCloseWithSlippage = priceClose * (1 + PERCENT_SLIPPAGE / 100);
782
+ priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
783
+ priceCloseWithSlippage = priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
772
784
  }
773
785
  // Применяем комиссию дважды (при открытии и закрытии)
774
- const totalFee = PERCENT_FEE * 2;
786
+ const totalFee = GLOBAL_CONFIG.CC_PERCENT_FEE * 2;
775
787
  let pnlPercentage;
776
788
  if (signal.position === "long") {
777
789
  // LONG: прибыль при росте цены
@@ -8684,8 +8696,27 @@ class HeatMarkdownService {
8684
8696
  }
8685
8697
 
8686
8698
  /**
8687
- * @class ExchangeValidationService
8688
- * Service for managing and validating exchange configurations
8699
+ * Service for managing and validating exchange configurations.
8700
+ *
8701
+ * Maintains a registry of all configured exchanges and validates
8702
+ * their existence before operations. Uses memoization for performance.
8703
+ *
8704
+ * Key features:
8705
+ * - Registry management: addExchange() to register new exchanges
8706
+ * - Validation: validate() ensures exchange exists before use
8707
+ * - Memoization: validation results are cached for performance
8708
+ * - Listing: list() returns all registered exchanges
8709
+ *
8710
+ * @throws {Error} If duplicate exchange name is added
8711
+ * @throws {Error} If unknown exchange is referenced
8712
+ *
8713
+ * @example
8714
+ * ```typescript
8715
+ * const exchangeValidation = new ExchangeValidationService();
8716
+ * exchangeValidation.addExchange("binance", binanceSchema);
8717
+ * exchangeValidation.validate("binance", "backtest"); // OK
8718
+ * exchangeValidation.validate("unknown", "live"); // Throws error
8719
+ * ```
8689
8720
  */
8690
8721
  class ExchangeValidationService {
8691
8722
  constructor() {
@@ -8745,8 +8776,29 @@ class ExchangeValidationService {
8745
8776
  }
8746
8777
 
8747
8778
  /**
8748
- * @class StrategyValidationService
8749
- * Service for managing and validating strategy configurations
8779
+ * Service for managing and validating trading strategy configurations.
8780
+ *
8781
+ * Maintains a registry of all configured strategies, validates their existence
8782
+ * before operations, and ensures associated risk profiles are valid.
8783
+ * Uses memoization for performance.
8784
+ *
8785
+ * Key features:
8786
+ * - Registry management: addStrategy() to register new strategies
8787
+ * - Dual validation: validates both strategy existence and risk profile (if configured)
8788
+ * - Memoization: validation results are cached for performance
8789
+ * - Listing: list() returns all registered strategies
8790
+ *
8791
+ * @throws {Error} If duplicate strategy name is added
8792
+ * @throws {Error} If unknown strategy is referenced
8793
+ * @throws {Error} If strategy's risk profile doesn't exist
8794
+ *
8795
+ * @example
8796
+ * ```typescript
8797
+ * const strategyValidation = new StrategyValidationService();
8798
+ * strategyValidation.addStrategy("momentum-btc", { ...schema, riskName: "conservative" });
8799
+ * strategyValidation.validate("momentum-btc", "backtest"); // Validates strategy + risk
8800
+ * strategyValidation.validate("unknown", "live"); // Throws error
8801
+ * ```
8750
8802
  */
8751
8803
  class StrategyValidationService {
8752
8804
  constructor() {
@@ -8817,8 +8869,27 @@ class StrategyValidationService {
8817
8869
  }
8818
8870
 
8819
8871
  /**
8820
- * @class FrameValidationService
8821
- * Service for managing and validating frame configurations
8872
+ * Service for managing and validating frame (timeframe) configurations.
8873
+ *
8874
+ * Maintains a registry of all configured frames and validates
8875
+ * their existence before operations. Uses memoization for performance.
8876
+ *
8877
+ * Key features:
8878
+ * - Registry management: addFrame() to register new timeframes
8879
+ * - Validation: validate() ensures frame exists before use
8880
+ * - Memoization: validation results are cached for performance
8881
+ * - Listing: list() returns all registered frames
8882
+ *
8883
+ * @throws {Error} If duplicate frame name is added
8884
+ * @throws {Error} If unknown frame is referenced
8885
+ *
8886
+ * @example
8887
+ * ```typescript
8888
+ * const frameValidation = new FrameValidationService();
8889
+ * frameValidation.addFrame("2024-Q1", frameSchema);
8890
+ * frameValidation.validate("2024-Q1", "backtest"); // OK
8891
+ * frameValidation.validate("unknown", "live"); // Throws error
8892
+ * ```
8822
8893
  */
8823
8894
  class FrameValidationService {
8824
8895
  constructor() {
@@ -8878,8 +8949,29 @@ class FrameValidationService {
8878
8949
  }
8879
8950
 
8880
8951
  /**
8881
- * @class WalkerValidationService
8882
- * Service for managing and validating walker configurations
8952
+ * Service for managing and validating walker (parameter sweep) configurations.
8953
+ *
8954
+ * Maintains a registry of all configured walkers and validates
8955
+ * their existence before operations. Uses memoization for performance.
8956
+ *
8957
+ * Walkers define parameter ranges for optimization and hyperparameter tuning.
8958
+ *
8959
+ * Key features:
8960
+ * - Registry management: addWalker() to register new walker configurations
8961
+ * - Validation: validate() ensures walker exists before use
8962
+ * - Memoization: validation results are cached for performance
8963
+ * - Listing: list() returns all registered walkers
8964
+ *
8965
+ * @throws {Error} If duplicate walker name is added
8966
+ * @throws {Error} If unknown walker is referenced
8967
+ *
8968
+ * @example
8969
+ * ```typescript
8970
+ * const walkerValidation = new WalkerValidationService();
8971
+ * walkerValidation.addWalker("rsi-sweep", walkerSchema);
8972
+ * walkerValidation.validate("rsi-sweep", "optimizer"); // OK
8973
+ * walkerValidation.validate("unknown", "optimizer"); // Throws error
8974
+ * ```
8883
8975
  */
8884
8976
  class WalkerValidationService {
8885
8977
  constructor() {
@@ -8939,8 +9031,27 @@ class WalkerValidationService {
8939
9031
  }
8940
9032
 
8941
9033
  /**
8942
- * @class SizingValidationService
8943
- * Service for managing and validating sizing configurations
9034
+ * Service for managing and validating position sizing configurations.
9035
+ *
9036
+ * Maintains a registry of all configured sizing strategies and validates
9037
+ * their existence before operations. Uses memoization for performance.
9038
+ *
9039
+ * Key features:
9040
+ * - Registry management: addSizing() to register new sizing strategies
9041
+ * - Validation: validate() ensures sizing strategy exists before use
9042
+ * - Memoization: validation results are cached for performance
9043
+ * - Listing: list() returns all registered sizing strategies
9044
+ *
9045
+ * @throws {Error} If duplicate sizing name is added
9046
+ * @throws {Error} If unknown sizing strategy is referenced
9047
+ *
9048
+ * @example
9049
+ * ```typescript
9050
+ * const sizingValidation = new SizingValidationService();
9051
+ * sizingValidation.addSizing("fixed-1000", fixedSizingSchema);
9052
+ * sizingValidation.validate("fixed-1000", "strategy-1"); // OK
9053
+ * sizingValidation.validate("unknown", "strategy-2"); // Throws error
9054
+ * ```
8944
9055
  */
8945
9056
  class SizingValidationService {
8946
9057
  constructor() {
@@ -9005,8 +9116,27 @@ class SizingValidationService {
9005
9116
  }
9006
9117
 
9007
9118
  /**
9008
- * @class RiskValidationService
9009
- * Service for managing and validating risk configurations
9119
+ * Service for managing and validating risk management configurations.
9120
+ *
9121
+ * Maintains a registry of all configured risk profiles and validates
9122
+ * their existence before operations. Uses memoization for performance.
9123
+ *
9124
+ * Key features:
9125
+ * - Registry management: addRisk() to register new risk profiles
9126
+ * - Validation: validate() ensures risk profile exists before use
9127
+ * - Memoization: validation results are cached by riskName:source for performance
9128
+ * - Listing: list() returns all registered risk profiles
9129
+ *
9130
+ * @throws {Error} If duplicate risk name is added
9131
+ * @throws {Error} If unknown risk profile is referenced
9132
+ *
9133
+ * @example
9134
+ * ```typescript
9135
+ * const riskValidation = new RiskValidationService();
9136
+ * riskValidation.addRisk("conservative", conservativeSchema);
9137
+ * riskValidation.validate("conservative", "strategy-1"); // OK
9138
+ * riskValidation.validate("unknown", "strategy-2"); // Throws error
9139
+ * ```
9010
9140
  */
9011
9141
  class RiskValidationService {
9012
9142
  constructor() {
@@ -11558,6 +11688,133 @@ class OutlineMarkdownService {
11558
11688
  }
11559
11689
  }
11560
11690
 
11691
+ /**
11692
+ * Service for validating GLOBAL_CONFIG parameters to ensure mathematical correctness
11693
+ * and prevent unprofitable trading configurations.
11694
+ *
11695
+ * Performs comprehensive validation on:
11696
+ * - **Percentage parameters**: Slippage, fees, and profit margins must be non-negative
11697
+ * - **Economic viability**: Ensures CC_MIN_TAKEPROFIT_DISTANCE_PERCENT covers all trading costs
11698
+ * (slippage + fees) to guarantee profitable trades when TakeProfit is hit
11699
+ * - **Range constraints**: Validates MIN < MAX relationships (e.g., StopLoss distances)
11700
+ * - **Time-based parameters**: Ensures positive integer values for timeouts and lifetimes
11701
+ * - **Candle parameters**: Validates retry counts, delays, and anomaly detection thresholds
11702
+ *
11703
+ * @throws {Error} If any validation fails, throws with detailed breakdown of all errors
11704
+ *
11705
+ * @example
11706
+ * ```typescript
11707
+ * const validator = new ConfigValidationService();
11708
+ * validator.validate(); // Throws if config is invalid
11709
+ * ```
11710
+ *
11711
+ * @example Validation failure output:
11712
+ * ```
11713
+ * GLOBAL_CONFIG validation failed:
11714
+ * 1. CC_MIN_TAKEPROFIT_DISTANCE_PERCENT (0.3%) is too low to cover trading costs.
11715
+ * Required minimum: 0.40%
11716
+ * Breakdown:
11717
+ * - Slippage effect: 0.20% (0.1% × 2 transactions)
11718
+ * - Fees: 0.20% (0.1% × 2 transactions)
11719
+ * All TakeProfit signals will be unprofitable with current settings!
11720
+ * ```
11721
+ */
11722
+ class ConfigValidationService {
11723
+ constructor() {
11724
+ /**
11725
+ * @private
11726
+ * @readonly
11727
+ * Injected logger service instance
11728
+ */
11729
+ this.loggerService = inject(TYPES.loggerService);
11730
+ /**
11731
+ * Validates GLOBAL_CONFIG parameters for mathematical correctness.
11732
+ *
11733
+ * Checks:
11734
+ * 1. CC_MIN_TAKEPROFIT_DISTANCE_PERCENT must cover slippage + fees
11735
+ * 2. All percentage values must be positive
11736
+ * 3. Time/count values must be positive integers
11737
+ *
11738
+ * @throws Error if configuration is invalid
11739
+ */
11740
+ this.validate = () => {
11741
+ this.loggerService.log("configValidationService validate");
11742
+ const errors = [];
11743
+ // Validate slippage and fee percentages
11744
+ if (!Number.isFinite(GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE) || GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE < 0) {
11745
+ errors.push(`CC_PERCENT_SLIPPAGE must be a non-negative number, got ${GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE}`);
11746
+ }
11747
+ if (!Number.isFinite(GLOBAL_CONFIG.CC_PERCENT_FEE) || GLOBAL_CONFIG.CC_PERCENT_FEE < 0) {
11748
+ errors.push(`CC_PERCENT_FEE must be a non-negative number, got ${GLOBAL_CONFIG.CC_PERCENT_FEE}`);
11749
+ }
11750
+ // Calculate minimum required TP distance to cover costs
11751
+ const slippageEffect = GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE * 2; // Applied twice (entry + exit)
11752
+ const feesTotal = GLOBAL_CONFIG.CC_PERCENT_FEE * 2; // Applied twice (entry + exit)
11753
+ const minRequiredTpDistance = slippageEffect + feesTotal;
11754
+ // Validate CC_MIN_TAKEPROFIT_DISTANCE_PERCENT
11755
+ if (!Number.isFinite(GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT <= 0) {
11756
+ errors.push(`CC_MIN_TAKEPROFIT_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT}`);
11757
+ }
11758
+ else if (GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT < minRequiredTpDistance) {
11759
+ errors.push(`CC_MIN_TAKEPROFIT_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT}%) is too low to cover trading costs.\n` +
11760
+ ` Required minimum: ${minRequiredTpDistance.toFixed(2)}%\n` +
11761
+ ` Breakdown:\n` +
11762
+ ` - Slippage effect: ${slippageEffect.toFixed(2)}% (${GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE}% × 2 transactions)\n` +
11763
+ ` - Fees: ${feesTotal.toFixed(2)}% (${GLOBAL_CONFIG.CC_PERCENT_FEE}% × 2 transactions)\n` +
11764
+ ` All TakeProfit signals will be unprofitable with current settings!`);
11765
+ }
11766
+ // Validate CC_MIN_STOPLOSS_DISTANCE_PERCENT
11767
+ if (!Number.isFinite(GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT <= 0) {
11768
+ errors.push(`CC_MIN_STOPLOSS_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT}`);
11769
+ }
11770
+ // Validate CC_MAX_STOPLOSS_DISTANCE_PERCENT
11771
+ if (!Number.isFinite(GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT <= 0) {
11772
+ errors.push(`CC_MAX_STOPLOSS_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT}`);
11773
+ }
11774
+ // Validate that MIN < MAX for StopLoss
11775
+ if (Number.isFinite(GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT) &&
11776
+ Number.isFinite(GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) &&
11777
+ GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT >= GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) {
11778
+ errors.push(`CC_MIN_STOPLOSS_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT}%) must be less than ` +
11779
+ `CC_MAX_STOPLOSS_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT}%)`);
11780
+ }
11781
+ // Validate time-based parameters
11782
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES) || GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES <= 0) {
11783
+ errors.push(`CC_SCHEDULE_AWAIT_MINUTES must be a positive integer, got ${GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES}`);
11784
+ }
11785
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES) || GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES <= 0) {
11786
+ errors.push(`CC_MAX_SIGNAL_LIFETIME_MINUTES must be a positive integer, got ${GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES}`);
11787
+ }
11788
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS) || GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS <= 0) {
11789
+ errors.push(`CC_MAX_SIGNAL_GENERATION_SECONDS must be a positive integer, got ${GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS}`);
11790
+ }
11791
+ // Validate candle-based parameters
11792
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT) || GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT <= 0) {
11793
+ errors.push(`CC_AVG_PRICE_CANDLES_COUNT must be a positive integer, got ${GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT}`);
11794
+ }
11795
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT) || GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT < 0) {
11796
+ errors.push(`CC_GET_CANDLES_RETRY_COUNT must be a non-negative integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT}`);
11797
+ }
11798
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS) || GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS < 0) {
11799
+ errors.push(`CC_GET_CANDLES_RETRY_DELAY_MS must be a non-negative integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS}`);
11800
+ }
11801
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR) || GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR <= 0) {
11802
+ errors.push(`CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR must be a positive integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR}`);
11803
+ }
11804
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN) || GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN <= 0) {
11805
+ errors.push(`CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN must be a positive integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN}`);
11806
+ }
11807
+ // Throw aggregated errors if any
11808
+ if (errors.length > 0) {
11809
+ const errorMessage = `GLOBAL_CONFIG validation failed:\n${errors.map((e, i) => ` ${i + 1}. ${e}`).join('\n')}`;
11810
+ this.loggerService.warn(errorMessage);
11811
+ throw new Error(errorMessage);
11812
+ }
11813
+ this.loggerService.log("configValidationService validation passed");
11814
+ };
11815
+ }
11816
+ }
11817
+
11561
11818
  {
11562
11819
  provide(TYPES.loggerService, () => new LoggerService());
11563
11820
  }
@@ -11625,6 +11882,7 @@ class OutlineMarkdownService {
11625
11882
  provide(TYPES.sizingValidationService, () => new SizingValidationService());
11626
11883
  provide(TYPES.riskValidationService, () => new RiskValidationService());
11627
11884
  provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
11885
+ provide(TYPES.configValidationService, () => new ConfigValidationService());
11628
11886
  }
11629
11887
  {
11630
11888
  provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
@@ -11697,6 +11955,7 @@ const validationServices = {
11697
11955
  sizingValidationService: inject(TYPES.sizingValidationService),
11698
11956
  riskValidationService: inject(TYPES.riskValidationService),
11699
11957
  optimizerValidationService: inject(TYPES.optimizerValidationService),
11958
+ configValidationService: inject(TYPES.configValidationService),
11700
11959
  };
11701
11960
  const templateServices = {
11702
11961
  optimizerTemplateService: inject(TYPES.optimizerTemplateService),
@@ -11734,7 +11993,7 @@ var backtest$1 = backtest;
11734
11993
  * });
11735
11994
  * ```
11736
11995
  */
11737
- async function setLogger(logger) {
11996
+ function setLogger(logger) {
11738
11997
  backtest$1.loggerService.setLogger(logger);
11739
11998
  }
11740
11999
  /**
@@ -11748,8 +12007,51 @@ async function setLogger(logger) {
11748
12007
  * });
11749
12008
  * ```
11750
12009
  */
11751
- async function setConfig(config) {
11752
- Object.assign(GLOBAL_CONFIG, config);
12010
+ function setConfig(config, _unsafe) {
12011
+ const prevConfig = Object.assign({}, GLOBAL_CONFIG);
12012
+ try {
12013
+ Object.assign(GLOBAL_CONFIG, config);
12014
+ !_unsafe && backtest$1.configValidationService.validate();
12015
+ }
12016
+ catch (error) {
12017
+ console.warn(`backtest-kit setConfig failed: ${functoolsKit.getErrorMessage(error)}`, config);
12018
+ Object.assign(GLOBAL_CONFIG, prevConfig);
12019
+ throw error;
12020
+ }
12021
+ }
12022
+ /**
12023
+ * Retrieves a copy of the current global configuration.
12024
+ *
12025
+ * Returns a shallow copy of the current GLOBAL_CONFIG to prevent accidental mutations.
12026
+ * Use this to inspect the current configuration state without modifying it.
12027
+ *
12028
+ * @returns {GlobalConfig} A copy of the current global configuration object
12029
+ *
12030
+ * @example
12031
+ * ```typescript
12032
+ * const currentConfig = getConfig();
12033
+ * console.log(currentConfig.CC_SCHEDULE_AWAIT_MINUTES);
12034
+ * ```
12035
+ */
12036
+ function getConfig() {
12037
+ return Object.assign({}, GLOBAL_CONFIG);
12038
+ }
12039
+ /**
12040
+ * Retrieves the default configuration object for the framework.
12041
+ *
12042
+ * Returns a reference to the default configuration with all preset values.
12043
+ * Use this to see what configuration options are available and their default values.
12044
+ *
12045
+ * @returns {GlobalConfig} The default configuration object
12046
+ *
12047
+ * @example
12048
+ * ```typescript
12049
+ * const defaultConfig = getDefaultConfig();
12050
+ * console.log(defaultConfig.CC_SCHEDULE_AWAIT_MINUTES);
12051
+ * ```
12052
+ */
12053
+ function getDefaultConfig() {
12054
+ return DEFAULT_CONFIG;
11753
12055
  }
11754
12056
 
11755
12057
  const ADD_STRATEGY_METHOD_NAME = "add.addStrategy";
@@ -13498,6 +13800,8 @@ class BacktestInstance {
13498
13800
  constructor(symbol, strategyName) {
13499
13801
  this.symbol = symbol;
13500
13802
  this.strategyName = strategyName;
13803
+ /** A randomly generated string. */
13804
+ this.id = functoolsKit.randomString();
13501
13805
  /** Internal flag indicating if backtest was stopped manually */
13502
13806
  this._isStopped = false;
13503
13807
  /** Internal flag indicating if backtest task completed */
@@ -13534,6 +13838,7 @@ class BacktestInstance {
13534
13838
  this.getStatus = async () => {
13535
13839
  backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_STATUS);
13536
13840
  return {
13841
+ id: this.id,
13537
13842
  symbol: this.symbol,
13538
13843
  strategyName: this.strategyName,
13539
13844
  status: this.task.getStatus(),
@@ -13984,6 +14289,8 @@ class LiveInstance {
13984
14289
  constructor(symbol, strategyName) {
13985
14290
  this.symbol = symbol;
13986
14291
  this.strategyName = strategyName;
14292
+ /** A randomly generated string. */
14293
+ this.id = functoolsKit.randomString();
13987
14294
  /** Internal flag indicating if live trading was stopped manually */
13988
14295
  this._isStopped = false;
13989
14296
  /** Internal flag indicating if live trading task completed */
@@ -14020,6 +14327,7 @@ class LiveInstance {
14020
14327
  this.getStatus = async () => {
14021
14328
  backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_STATUS);
14022
14329
  return {
14330
+ id: this.id,
14023
14331
  symbol: this.symbol,
14024
14332
  strategyName: this.strategyName,
14025
14333
  status: this.task.getStatus(),
@@ -14732,6 +15040,8 @@ class WalkerInstance {
14732
15040
  constructor(symbol, walkerName) {
14733
15041
  this.symbol = symbol;
14734
15042
  this.walkerName = walkerName;
15043
+ /** A randomly generated string. */
15044
+ this.id = functoolsKit.randomString();
14735
15045
  /** Internal flag indicating if walker was stopped manually */
14736
15046
  this._isStopped = false;
14737
15047
  /** Internal flag indicating if walker task completed */
@@ -14768,6 +15078,7 @@ class WalkerInstance {
14768
15078
  this.getStatus = async () => {
14769
15079
  backtest$1.loggerService.info(WALKER_METHOD_NAME_GET_STATUS);
14770
15080
  return {
15081
+ id: this.id,
14771
15082
  symbol: this.symbol,
14772
15083
  walkerName: this.walkerName,
14773
15084
  status: this.task.getStatus(),
@@ -15859,7 +16170,9 @@ exports.formatPrice = formatPrice;
15859
16170
  exports.formatQuantity = formatQuantity;
15860
16171
  exports.getAveragePrice = getAveragePrice;
15861
16172
  exports.getCandles = getCandles;
16173
+ exports.getConfig = getConfig;
15862
16174
  exports.getDate = getDate;
16175
+ exports.getDefaultConfig = getDefaultConfig;
15863
16176
  exports.getMode = getMode;
15864
16177
  exports.lib = backtest;
15865
16178
  exports.listExchanges = listExchanges;