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.mjs CHANGED
@@ -1,6 +1,6 @@
1
+ import { errorData, getErrorMessage, sleep, memoize, makeExtendable, singleshot, not, trycatch, retry, Subject, randomString, ToolRegistry, isObject, and, resolveDocuments, str, iterateDocuments, distinctDocuments, queued, singlerun } from 'functools-kit';
1
2
  import { createActivator } from 'di-kit';
2
3
  import { scoped } from 'di-scoped';
3
- import { errorData, getErrorMessage, sleep, memoize, makeExtendable, singleshot, not, trycatch, retry, Subject, randomString, ToolRegistry, isObject, and, resolveDocuments, str, iterateDocuments, distinctDocuments, queued, singlerun } from 'functools-kit';
4
4
  import fs, { mkdir, writeFile } from 'fs/promises';
5
5
  import path, { join } from 'path';
6
6
  import crypto from 'crypto';
@@ -18,12 +18,32 @@ const GLOBAL_CONFIG = {
18
18
  * Default: 5 candles (last 5 minutes when using 1m interval)
19
19
  */
20
20
  CC_AVG_PRICE_CANDLES_COUNT: 5,
21
+ /**
22
+ * Slippage percentage applied to entry and exit prices.
23
+ * Simulates market impact and order book depth.
24
+ * Applied twice (entry and exit) for realistic execution simulation.
25
+ * Default: 0.1% per transaction
26
+ */
27
+ CC_PERCENT_SLIPPAGE: 0.1,
28
+ /**
29
+ * Fee percentage charged per transaction.
30
+ * Applied twice (entry and exit) for total fee calculation.
31
+ * Default: 0.1% per transaction (total 0.2%)
32
+ */
33
+ CC_PERCENT_FEE: 0.1,
21
34
  /**
22
35
  * Minimum TakeProfit distance from priceOpen (percentage)
23
- * Must be greater than trading fees to ensure profitable trades
24
- * Default: 0.3% (covers 2×0.1% fees + minimum profit margin)
36
+ * Must be greater than (slippage + fees) to ensure profitable trades
37
+ *
38
+ * Calculation:
39
+ * - Slippage effect: ~0.2% (0.1% × 2 transactions)
40
+ * - Fees: 0.2% (0.1% × 2 transactions)
41
+ * - Minimum profit buffer: 0.1%
42
+ * - Total: 0.5%
43
+ *
44
+ * Default: 0.5% (covers all costs + minimum profit margin)
25
45
  */
26
- CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: 0.3,
46
+ CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: 0.5,
27
47
  /**
28
48
  * Minimum StopLoss distance from priceOpen (percentage)
29
49
  * Prevents signals from being immediately stopped out due to price volatility
@@ -90,6 +110,7 @@ const GLOBAL_CONFIG = {
90
110
  */
91
111
  CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN: 5,
92
112
  };
113
+ const DEFAULT_CONFIG = Object.freeze({ ...GLOBAL_CONFIG });
93
114
 
94
115
  const { init, inject, provide } = createActivator("backtest");
95
116
 
@@ -186,6 +207,7 @@ const validationServices$1 = {
186
207
  sizingValidationService: Symbol('sizingValidationService'),
187
208
  riskValidationService: Symbol('riskValidationService'),
188
209
  optimizerValidationService: Symbol('optimizerValidationService'),
210
+ configValidationService: Symbol('configValidationService'),
189
211
  };
190
212
  const templateServices$1 = {
191
213
  optimizerTemplateService: Symbol('optimizerTemplateService'),
@@ -715,16 +737,6 @@ class ExchangeConnectionService {
715
737
  }
716
738
  }
717
739
 
718
- /**
719
- * Slippage percentage applied to entry and exit prices.
720
- * Simulates market impact and order book depth.
721
- */
722
- const PERCENT_SLIPPAGE = 0.1;
723
- /**
724
- * Fee percentage charged per transaction.
725
- * Applied twice (entry and exit) for total fee calculation.
726
- */
727
- const PERCENT_FEE = 0.1;
728
740
  /**
729
741
  * Calculates profit/loss for a closed signal with slippage and fees.
730
742
  *
@@ -760,16 +772,16 @@ const toProfitLossDto = (signal, priceClose) => {
760
772
  let priceCloseWithSlippage;
761
773
  if (signal.position === "long") {
762
774
  // LONG: покупаем дороже, продаем дешевле
763
- priceOpenWithSlippage = priceOpen * (1 + PERCENT_SLIPPAGE / 100);
764
- priceCloseWithSlippage = priceClose * (1 - PERCENT_SLIPPAGE / 100);
775
+ priceOpenWithSlippage = priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
776
+ priceCloseWithSlippage = priceClose * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
765
777
  }
766
778
  else {
767
779
  // SHORT: продаем дешевле, покупаем дороже
768
- priceOpenWithSlippage = priceOpen * (1 - PERCENT_SLIPPAGE / 100);
769
- priceCloseWithSlippage = priceClose * (1 + PERCENT_SLIPPAGE / 100);
780
+ priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
781
+ priceCloseWithSlippage = priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
770
782
  }
771
783
  // Применяем комиссию дважды (при открытии и закрытии)
772
- const totalFee = PERCENT_FEE * 2;
784
+ const totalFee = GLOBAL_CONFIG.CC_PERCENT_FEE * 2;
773
785
  let pnlPercentage;
774
786
  if (signal.position === "long") {
775
787
  // LONG: прибыль при росте цены
@@ -8682,8 +8694,27 @@ class HeatMarkdownService {
8682
8694
  }
8683
8695
 
8684
8696
  /**
8685
- * @class ExchangeValidationService
8686
- * Service for managing and validating exchange configurations
8697
+ * Service for managing and validating exchange configurations.
8698
+ *
8699
+ * Maintains a registry of all configured exchanges and validates
8700
+ * their existence before operations. Uses memoization for performance.
8701
+ *
8702
+ * Key features:
8703
+ * - Registry management: addExchange() to register new exchanges
8704
+ * - Validation: validate() ensures exchange exists before use
8705
+ * - Memoization: validation results are cached for performance
8706
+ * - Listing: list() returns all registered exchanges
8707
+ *
8708
+ * @throws {Error} If duplicate exchange name is added
8709
+ * @throws {Error} If unknown exchange is referenced
8710
+ *
8711
+ * @example
8712
+ * ```typescript
8713
+ * const exchangeValidation = new ExchangeValidationService();
8714
+ * exchangeValidation.addExchange("binance", binanceSchema);
8715
+ * exchangeValidation.validate("binance", "backtest"); // OK
8716
+ * exchangeValidation.validate("unknown", "live"); // Throws error
8717
+ * ```
8687
8718
  */
8688
8719
  class ExchangeValidationService {
8689
8720
  constructor() {
@@ -8743,8 +8774,29 @@ class ExchangeValidationService {
8743
8774
  }
8744
8775
 
8745
8776
  /**
8746
- * @class StrategyValidationService
8747
- * Service for managing and validating strategy configurations
8777
+ * Service for managing and validating trading strategy configurations.
8778
+ *
8779
+ * Maintains a registry of all configured strategies, validates their existence
8780
+ * before operations, and ensures associated risk profiles are valid.
8781
+ * Uses memoization for performance.
8782
+ *
8783
+ * Key features:
8784
+ * - Registry management: addStrategy() to register new strategies
8785
+ * - Dual validation: validates both strategy existence and risk profile (if configured)
8786
+ * - Memoization: validation results are cached for performance
8787
+ * - Listing: list() returns all registered strategies
8788
+ *
8789
+ * @throws {Error} If duplicate strategy name is added
8790
+ * @throws {Error} If unknown strategy is referenced
8791
+ * @throws {Error} If strategy's risk profile doesn't exist
8792
+ *
8793
+ * @example
8794
+ * ```typescript
8795
+ * const strategyValidation = new StrategyValidationService();
8796
+ * strategyValidation.addStrategy("momentum-btc", { ...schema, riskName: "conservative" });
8797
+ * strategyValidation.validate("momentum-btc", "backtest"); // Validates strategy + risk
8798
+ * strategyValidation.validate("unknown", "live"); // Throws error
8799
+ * ```
8748
8800
  */
8749
8801
  class StrategyValidationService {
8750
8802
  constructor() {
@@ -8815,8 +8867,27 @@ class StrategyValidationService {
8815
8867
  }
8816
8868
 
8817
8869
  /**
8818
- * @class FrameValidationService
8819
- * Service for managing and validating frame configurations
8870
+ * Service for managing and validating frame (timeframe) configurations.
8871
+ *
8872
+ * Maintains a registry of all configured frames and validates
8873
+ * their existence before operations. Uses memoization for performance.
8874
+ *
8875
+ * Key features:
8876
+ * - Registry management: addFrame() to register new timeframes
8877
+ * - Validation: validate() ensures frame exists before use
8878
+ * - Memoization: validation results are cached for performance
8879
+ * - Listing: list() returns all registered frames
8880
+ *
8881
+ * @throws {Error} If duplicate frame name is added
8882
+ * @throws {Error} If unknown frame is referenced
8883
+ *
8884
+ * @example
8885
+ * ```typescript
8886
+ * const frameValidation = new FrameValidationService();
8887
+ * frameValidation.addFrame("2024-Q1", frameSchema);
8888
+ * frameValidation.validate("2024-Q1", "backtest"); // OK
8889
+ * frameValidation.validate("unknown", "live"); // Throws error
8890
+ * ```
8820
8891
  */
8821
8892
  class FrameValidationService {
8822
8893
  constructor() {
@@ -8876,8 +8947,29 @@ class FrameValidationService {
8876
8947
  }
8877
8948
 
8878
8949
  /**
8879
- * @class WalkerValidationService
8880
- * Service for managing and validating walker configurations
8950
+ * Service for managing and validating walker (parameter sweep) configurations.
8951
+ *
8952
+ * Maintains a registry of all configured walkers and validates
8953
+ * their existence before operations. Uses memoization for performance.
8954
+ *
8955
+ * Walkers define parameter ranges for optimization and hyperparameter tuning.
8956
+ *
8957
+ * Key features:
8958
+ * - Registry management: addWalker() to register new walker configurations
8959
+ * - Validation: validate() ensures walker exists before use
8960
+ * - Memoization: validation results are cached for performance
8961
+ * - Listing: list() returns all registered walkers
8962
+ *
8963
+ * @throws {Error} If duplicate walker name is added
8964
+ * @throws {Error} If unknown walker is referenced
8965
+ *
8966
+ * @example
8967
+ * ```typescript
8968
+ * const walkerValidation = new WalkerValidationService();
8969
+ * walkerValidation.addWalker("rsi-sweep", walkerSchema);
8970
+ * walkerValidation.validate("rsi-sweep", "optimizer"); // OK
8971
+ * walkerValidation.validate("unknown", "optimizer"); // Throws error
8972
+ * ```
8881
8973
  */
8882
8974
  class WalkerValidationService {
8883
8975
  constructor() {
@@ -8937,8 +9029,27 @@ class WalkerValidationService {
8937
9029
  }
8938
9030
 
8939
9031
  /**
8940
- * @class SizingValidationService
8941
- * Service for managing and validating sizing configurations
9032
+ * Service for managing and validating position sizing configurations.
9033
+ *
9034
+ * Maintains a registry of all configured sizing strategies and validates
9035
+ * their existence before operations. Uses memoization for performance.
9036
+ *
9037
+ * Key features:
9038
+ * - Registry management: addSizing() to register new sizing strategies
9039
+ * - Validation: validate() ensures sizing strategy exists before use
9040
+ * - Memoization: validation results are cached for performance
9041
+ * - Listing: list() returns all registered sizing strategies
9042
+ *
9043
+ * @throws {Error} If duplicate sizing name is added
9044
+ * @throws {Error} If unknown sizing strategy is referenced
9045
+ *
9046
+ * @example
9047
+ * ```typescript
9048
+ * const sizingValidation = new SizingValidationService();
9049
+ * sizingValidation.addSizing("fixed-1000", fixedSizingSchema);
9050
+ * sizingValidation.validate("fixed-1000", "strategy-1"); // OK
9051
+ * sizingValidation.validate("unknown", "strategy-2"); // Throws error
9052
+ * ```
8942
9053
  */
8943
9054
  class SizingValidationService {
8944
9055
  constructor() {
@@ -9003,8 +9114,27 @@ class SizingValidationService {
9003
9114
  }
9004
9115
 
9005
9116
  /**
9006
- * @class RiskValidationService
9007
- * Service for managing and validating risk configurations
9117
+ * Service for managing and validating risk management configurations.
9118
+ *
9119
+ * Maintains a registry of all configured risk profiles and validates
9120
+ * their existence before operations. Uses memoization for performance.
9121
+ *
9122
+ * Key features:
9123
+ * - Registry management: addRisk() to register new risk profiles
9124
+ * - Validation: validate() ensures risk profile exists before use
9125
+ * - Memoization: validation results are cached by riskName:source for performance
9126
+ * - Listing: list() returns all registered risk profiles
9127
+ *
9128
+ * @throws {Error} If duplicate risk name is added
9129
+ * @throws {Error} If unknown risk profile is referenced
9130
+ *
9131
+ * @example
9132
+ * ```typescript
9133
+ * const riskValidation = new RiskValidationService();
9134
+ * riskValidation.addRisk("conservative", conservativeSchema);
9135
+ * riskValidation.validate("conservative", "strategy-1"); // OK
9136
+ * riskValidation.validate("unknown", "strategy-2"); // Throws error
9137
+ * ```
9008
9138
  */
9009
9139
  class RiskValidationService {
9010
9140
  constructor() {
@@ -11556,6 +11686,133 @@ class OutlineMarkdownService {
11556
11686
  }
11557
11687
  }
11558
11688
 
11689
+ /**
11690
+ * Service for validating GLOBAL_CONFIG parameters to ensure mathematical correctness
11691
+ * and prevent unprofitable trading configurations.
11692
+ *
11693
+ * Performs comprehensive validation on:
11694
+ * - **Percentage parameters**: Slippage, fees, and profit margins must be non-negative
11695
+ * - **Economic viability**: Ensures CC_MIN_TAKEPROFIT_DISTANCE_PERCENT covers all trading costs
11696
+ * (slippage + fees) to guarantee profitable trades when TakeProfit is hit
11697
+ * - **Range constraints**: Validates MIN < MAX relationships (e.g., StopLoss distances)
11698
+ * - **Time-based parameters**: Ensures positive integer values for timeouts and lifetimes
11699
+ * - **Candle parameters**: Validates retry counts, delays, and anomaly detection thresholds
11700
+ *
11701
+ * @throws {Error} If any validation fails, throws with detailed breakdown of all errors
11702
+ *
11703
+ * @example
11704
+ * ```typescript
11705
+ * const validator = new ConfigValidationService();
11706
+ * validator.validate(); // Throws if config is invalid
11707
+ * ```
11708
+ *
11709
+ * @example Validation failure output:
11710
+ * ```
11711
+ * GLOBAL_CONFIG validation failed:
11712
+ * 1. CC_MIN_TAKEPROFIT_DISTANCE_PERCENT (0.3%) is too low to cover trading costs.
11713
+ * Required minimum: 0.40%
11714
+ * Breakdown:
11715
+ * - Slippage effect: 0.20% (0.1% × 2 transactions)
11716
+ * - Fees: 0.20% (0.1% × 2 transactions)
11717
+ * All TakeProfit signals will be unprofitable with current settings!
11718
+ * ```
11719
+ */
11720
+ class ConfigValidationService {
11721
+ constructor() {
11722
+ /**
11723
+ * @private
11724
+ * @readonly
11725
+ * Injected logger service instance
11726
+ */
11727
+ this.loggerService = inject(TYPES.loggerService);
11728
+ /**
11729
+ * Validates GLOBAL_CONFIG parameters for mathematical correctness.
11730
+ *
11731
+ * Checks:
11732
+ * 1. CC_MIN_TAKEPROFIT_DISTANCE_PERCENT must cover slippage + fees
11733
+ * 2. All percentage values must be positive
11734
+ * 3. Time/count values must be positive integers
11735
+ *
11736
+ * @throws Error if configuration is invalid
11737
+ */
11738
+ this.validate = () => {
11739
+ this.loggerService.log("configValidationService validate");
11740
+ const errors = [];
11741
+ // Validate slippage and fee percentages
11742
+ if (!Number.isFinite(GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE) || GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE < 0) {
11743
+ errors.push(`CC_PERCENT_SLIPPAGE must be a non-negative number, got ${GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE}`);
11744
+ }
11745
+ if (!Number.isFinite(GLOBAL_CONFIG.CC_PERCENT_FEE) || GLOBAL_CONFIG.CC_PERCENT_FEE < 0) {
11746
+ errors.push(`CC_PERCENT_FEE must be a non-negative number, got ${GLOBAL_CONFIG.CC_PERCENT_FEE}`);
11747
+ }
11748
+ // Calculate minimum required TP distance to cover costs
11749
+ const slippageEffect = GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE * 2; // Applied twice (entry + exit)
11750
+ const feesTotal = GLOBAL_CONFIG.CC_PERCENT_FEE * 2; // Applied twice (entry + exit)
11751
+ const minRequiredTpDistance = slippageEffect + feesTotal;
11752
+ // Validate CC_MIN_TAKEPROFIT_DISTANCE_PERCENT
11753
+ if (!Number.isFinite(GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT <= 0) {
11754
+ errors.push(`CC_MIN_TAKEPROFIT_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT}`);
11755
+ }
11756
+ else if (GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT < minRequiredTpDistance) {
11757
+ errors.push(`CC_MIN_TAKEPROFIT_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT}%) is too low to cover trading costs.\n` +
11758
+ ` Required minimum: ${minRequiredTpDistance.toFixed(2)}%\n` +
11759
+ ` Breakdown:\n` +
11760
+ ` - Slippage effect: ${slippageEffect.toFixed(2)}% (${GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE}% × 2 transactions)\n` +
11761
+ ` - Fees: ${feesTotal.toFixed(2)}% (${GLOBAL_CONFIG.CC_PERCENT_FEE}% × 2 transactions)\n` +
11762
+ ` All TakeProfit signals will be unprofitable with current settings!`);
11763
+ }
11764
+ // Validate CC_MIN_STOPLOSS_DISTANCE_PERCENT
11765
+ if (!Number.isFinite(GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT <= 0) {
11766
+ errors.push(`CC_MIN_STOPLOSS_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT}`);
11767
+ }
11768
+ // Validate CC_MAX_STOPLOSS_DISTANCE_PERCENT
11769
+ if (!Number.isFinite(GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT <= 0) {
11770
+ errors.push(`CC_MAX_STOPLOSS_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT}`);
11771
+ }
11772
+ // Validate that MIN < MAX for StopLoss
11773
+ if (Number.isFinite(GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT) &&
11774
+ Number.isFinite(GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) &&
11775
+ GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT >= GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) {
11776
+ errors.push(`CC_MIN_STOPLOSS_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT}%) must be less than ` +
11777
+ `CC_MAX_STOPLOSS_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT}%)`);
11778
+ }
11779
+ // Validate time-based parameters
11780
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES) || GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES <= 0) {
11781
+ errors.push(`CC_SCHEDULE_AWAIT_MINUTES must be a positive integer, got ${GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES}`);
11782
+ }
11783
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES) || GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES <= 0) {
11784
+ errors.push(`CC_MAX_SIGNAL_LIFETIME_MINUTES must be a positive integer, got ${GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES}`);
11785
+ }
11786
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS) || GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS <= 0) {
11787
+ errors.push(`CC_MAX_SIGNAL_GENERATION_SECONDS must be a positive integer, got ${GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS}`);
11788
+ }
11789
+ // Validate candle-based parameters
11790
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT) || GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT <= 0) {
11791
+ errors.push(`CC_AVG_PRICE_CANDLES_COUNT must be a positive integer, got ${GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT}`);
11792
+ }
11793
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT) || GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT < 0) {
11794
+ errors.push(`CC_GET_CANDLES_RETRY_COUNT must be a non-negative integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT}`);
11795
+ }
11796
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS) || GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS < 0) {
11797
+ errors.push(`CC_GET_CANDLES_RETRY_DELAY_MS must be a non-negative integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS}`);
11798
+ }
11799
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR) || GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR <= 0) {
11800
+ errors.push(`CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR must be a positive integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR}`);
11801
+ }
11802
+ if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN) || GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN <= 0) {
11803
+ errors.push(`CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN must be a positive integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN}`);
11804
+ }
11805
+ // Throw aggregated errors if any
11806
+ if (errors.length > 0) {
11807
+ const errorMessage = `GLOBAL_CONFIG validation failed:\n${errors.map((e, i) => ` ${i + 1}. ${e}`).join('\n')}`;
11808
+ this.loggerService.warn(errorMessage);
11809
+ throw new Error(errorMessage);
11810
+ }
11811
+ this.loggerService.log("configValidationService validation passed");
11812
+ };
11813
+ }
11814
+ }
11815
+
11559
11816
  {
11560
11817
  provide(TYPES.loggerService, () => new LoggerService());
11561
11818
  }
@@ -11623,6 +11880,7 @@ class OutlineMarkdownService {
11623
11880
  provide(TYPES.sizingValidationService, () => new SizingValidationService());
11624
11881
  provide(TYPES.riskValidationService, () => new RiskValidationService());
11625
11882
  provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
11883
+ provide(TYPES.configValidationService, () => new ConfigValidationService());
11626
11884
  }
11627
11885
  {
11628
11886
  provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
@@ -11695,6 +11953,7 @@ const validationServices = {
11695
11953
  sizingValidationService: inject(TYPES.sizingValidationService),
11696
11954
  riskValidationService: inject(TYPES.riskValidationService),
11697
11955
  optimizerValidationService: inject(TYPES.optimizerValidationService),
11956
+ configValidationService: inject(TYPES.configValidationService),
11698
11957
  };
11699
11958
  const templateServices = {
11700
11959
  optimizerTemplateService: inject(TYPES.optimizerTemplateService),
@@ -11732,7 +11991,7 @@ var backtest$1 = backtest;
11732
11991
  * });
11733
11992
  * ```
11734
11993
  */
11735
- async function setLogger(logger) {
11994
+ function setLogger(logger) {
11736
11995
  backtest$1.loggerService.setLogger(logger);
11737
11996
  }
11738
11997
  /**
@@ -11746,8 +12005,51 @@ async function setLogger(logger) {
11746
12005
  * });
11747
12006
  * ```
11748
12007
  */
11749
- async function setConfig(config) {
11750
- Object.assign(GLOBAL_CONFIG, config);
12008
+ function setConfig(config, _unsafe) {
12009
+ const prevConfig = Object.assign({}, GLOBAL_CONFIG);
12010
+ try {
12011
+ Object.assign(GLOBAL_CONFIG, config);
12012
+ !_unsafe && backtest$1.configValidationService.validate();
12013
+ }
12014
+ catch (error) {
12015
+ console.warn(`backtest-kit setConfig failed: ${getErrorMessage(error)}`, config);
12016
+ Object.assign(GLOBAL_CONFIG, prevConfig);
12017
+ throw error;
12018
+ }
12019
+ }
12020
+ /**
12021
+ * Retrieves a copy of the current global configuration.
12022
+ *
12023
+ * Returns a shallow copy of the current GLOBAL_CONFIG to prevent accidental mutations.
12024
+ * Use this to inspect the current configuration state without modifying it.
12025
+ *
12026
+ * @returns {GlobalConfig} A copy of the current global configuration object
12027
+ *
12028
+ * @example
12029
+ * ```typescript
12030
+ * const currentConfig = getConfig();
12031
+ * console.log(currentConfig.CC_SCHEDULE_AWAIT_MINUTES);
12032
+ * ```
12033
+ */
12034
+ function getConfig() {
12035
+ return Object.assign({}, GLOBAL_CONFIG);
12036
+ }
12037
+ /**
12038
+ * Retrieves the default configuration object for the framework.
12039
+ *
12040
+ * Returns a reference to the default configuration with all preset values.
12041
+ * Use this to see what configuration options are available and their default values.
12042
+ *
12043
+ * @returns {GlobalConfig} The default configuration object
12044
+ *
12045
+ * @example
12046
+ * ```typescript
12047
+ * const defaultConfig = getDefaultConfig();
12048
+ * console.log(defaultConfig.CC_SCHEDULE_AWAIT_MINUTES);
12049
+ * ```
12050
+ */
12051
+ function getDefaultConfig() {
12052
+ return DEFAULT_CONFIG;
11751
12053
  }
11752
12054
 
11753
12055
  const ADD_STRATEGY_METHOD_NAME = "add.addStrategy";
@@ -13496,6 +13798,8 @@ class BacktestInstance {
13496
13798
  constructor(symbol, strategyName) {
13497
13799
  this.symbol = symbol;
13498
13800
  this.strategyName = strategyName;
13801
+ /** A randomly generated string. */
13802
+ this.id = randomString();
13499
13803
  /** Internal flag indicating if backtest was stopped manually */
13500
13804
  this._isStopped = false;
13501
13805
  /** Internal flag indicating if backtest task completed */
@@ -13532,6 +13836,7 @@ class BacktestInstance {
13532
13836
  this.getStatus = async () => {
13533
13837
  backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_STATUS);
13534
13838
  return {
13839
+ id: this.id,
13535
13840
  symbol: this.symbol,
13536
13841
  strategyName: this.strategyName,
13537
13842
  status: this.task.getStatus(),
@@ -13982,6 +14287,8 @@ class LiveInstance {
13982
14287
  constructor(symbol, strategyName) {
13983
14288
  this.symbol = symbol;
13984
14289
  this.strategyName = strategyName;
14290
+ /** A randomly generated string. */
14291
+ this.id = randomString();
13985
14292
  /** Internal flag indicating if live trading was stopped manually */
13986
14293
  this._isStopped = false;
13987
14294
  /** Internal flag indicating if live trading task completed */
@@ -14018,6 +14325,7 @@ class LiveInstance {
14018
14325
  this.getStatus = async () => {
14019
14326
  backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_STATUS);
14020
14327
  return {
14328
+ id: this.id,
14021
14329
  symbol: this.symbol,
14022
14330
  strategyName: this.strategyName,
14023
14331
  status: this.task.getStatus(),
@@ -14730,6 +15038,8 @@ class WalkerInstance {
14730
15038
  constructor(symbol, walkerName) {
14731
15039
  this.symbol = symbol;
14732
15040
  this.walkerName = walkerName;
15041
+ /** A randomly generated string. */
15042
+ this.id = randomString();
14733
15043
  /** Internal flag indicating if walker was stopped manually */
14734
15044
  this._isStopped = false;
14735
15045
  /** Internal flag indicating if walker task completed */
@@ -14766,6 +15076,7 @@ class WalkerInstance {
14766
15076
  this.getStatus = async () => {
14767
15077
  backtest$1.loggerService.info(WALKER_METHOD_NAME_GET_STATUS);
14768
15078
  return {
15079
+ id: this.id,
14769
15080
  symbol: this.symbol,
14770
15081
  walkerName: this.walkerName,
14771
15082
  status: this.task.getStatus(),
@@ -15827,4 +16138,4 @@ class ConstantUtils {
15827
16138
  */
15828
16139
  const Constant = new ConstantUtils();
15829
16140
 
15830
- export { Backtest, Constant, ExecutionContextService, Heat, Live, MethodContextService, Optimizer, Partial, Performance, PersistBase, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Schedule, Walker, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, dumpSignal, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, setConfig, setLogger };
16141
+ export { Backtest, Constant, ExecutionContextService, Heat, Live, MethodContextService, Optimizer, Partial, Performance, PersistBase, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Schedule, Walker, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, dumpSignal, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getConfig, getDate, getDefaultConfig, getMode, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, setConfig, setLogger };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "1.5.11",
3
+ "version": "1.5.13",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",