backtest-kit 1.5.30 → 1.5.31
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 +115 -19
- package/build/index.mjs +115 -19
- package/package.json +1 -1
- package/types.d.ts +24 -5
package/build/index.cjs
CHANGED
|
@@ -1438,7 +1438,7 @@ class LoggerService {
|
|
|
1438
1438
|
}
|
|
1439
1439
|
}
|
|
1440
1440
|
|
|
1441
|
-
const INTERVAL_MINUTES$
|
|
1441
|
+
const INTERVAL_MINUTES$3 = {
|
|
1442
1442
|
"1m": 1,
|
|
1443
1443
|
"3m": 3,
|
|
1444
1444
|
"5m": 5,
|
|
@@ -1583,7 +1583,7 @@ class ClientExchange {
|
|
|
1583
1583
|
interval,
|
|
1584
1584
|
limit,
|
|
1585
1585
|
});
|
|
1586
|
-
const step = INTERVAL_MINUTES$
|
|
1586
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
1587
1587
|
const adjust = step * limit - step;
|
|
1588
1588
|
if (!adjust) {
|
|
1589
1589
|
throw new Error(`ClientExchange unknown time adjust for interval=${interval}`);
|
|
@@ -1621,7 +1621,7 @@ class ClientExchange {
|
|
|
1621
1621
|
const since = new Date(this.params.execution.context.when.getTime());
|
|
1622
1622
|
const now = Date.now();
|
|
1623
1623
|
// Вычисляем конечное время запроса
|
|
1624
|
-
const step = INTERVAL_MINUTES$
|
|
1624
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
1625
1625
|
const endTime = since.getTime() + limit * step * 60 * 1000;
|
|
1626
1626
|
// Проверяем что запрошенный период не заходит за Date.now()
|
|
1627
1627
|
if (endTime > now) {
|
|
@@ -2845,7 +2845,7 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
2845
2845
|
walkerStopSubject: walkerStopSubject
|
|
2846
2846
|
});
|
|
2847
2847
|
|
|
2848
|
-
const INTERVAL_MINUTES$
|
|
2848
|
+
const INTERVAL_MINUTES$2 = {
|
|
2849
2849
|
"1m": 1,
|
|
2850
2850
|
"3m": 3,
|
|
2851
2851
|
"5m": 5,
|
|
@@ -3063,7 +3063,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
3063
3063
|
}
|
|
3064
3064
|
const currentTime = self.params.execution.context.when.getTime();
|
|
3065
3065
|
{
|
|
3066
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
3066
|
+
const intervalMinutes = INTERVAL_MINUTES$2[self.params.interval];
|
|
3067
3067
|
const intervalMs = intervalMinutes * 60 * 1000;
|
|
3068
3068
|
// Проверяем что прошел нужный интервал с последнего getSignal
|
|
3069
3069
|
if (self._lastSignalTimestamp !== null &&
|
|
@@ -4811,7 +4811,7 @@ class StrategyConnectionService {
|
|
|
4811
4811
|
* Maps FrameInterval to minutes for timestamp calculation.
|
|
4812
4812
|
* Used to generate timeframe arrays with proper spacing.
|
|
4813
4813
|
*/
|
|
4814
|
-
const INTERVAL_MINUTES = {
|
|
4814
|
+
const INTERVAL_MINUTES$1 = {
|
|
4815
4815
|
"1m": 1,
|
|
4816
4816
|
"3m": 3,
|
|
4817
4817
|
"5m": 5,
|
|
@@ -4840,7 +4840,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
4840
4840
|
symbol,
|
|
4841
4841
|
});
|
|
4842
4842
|
const { interval, startDate, endDate } = self.params;
|
|
4843
|
-
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
4843
|
+
const intervalMinutes = INTERVAL_MINUTES$1[interval];
|
|
4844
4844
|
if (!intervalMinutes) {
|
|
4845
4845
|
throw new Error(`ClientFrame unknown interval: ${interval}`);
|
|
4846
4846
|
}
|
|
@@ -18134,8 +18134,21 @@ class ConstantUtils {
|
|
|
18134
18134
|
const Constant = new ConstantUtils();
|
|
18135
18135
|
|
|
18136
18136
|
const EXCHANGE_METHOD_NAME_GET_CANDLES = "ExchangeUtils.getCandles";
|
|
18137
|
+
const EXCHANGE_METHOD_NAME_GET_AVERAGE_PRICE = "ExchangeUtils.getAveragePrice";
|
|
18137
18138
|
const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
|
|
18138
18139
|
const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
18140
|
+
const INTERVAL_MINUTES = {
|
|
18141
|
+
"1m": 1,
|
|
18142
|
+
"3m": 3,
|
|
18143
|
+
"5m": 5,
|
|
18144
|
+
"15m": 15,
|
|
18145
|
+
"30m": 30,
|
|
18146
|
+
"1h": 60,
|
|
18147
|
+
"2h": 120,
|
|
18148
|
+
"4h": 240,
|
|
18149
|
+
"6h": 360,
|
|
18150
|
+
"8h": 480,
|
|
18151
|
+
};
|
|
18139
18152
|
/**
|
|
18140
18153
|
* Instance class for exchange operations on a specific exchange.
|
|
18141
18154
|
*
|
|
@@ -18147,7 +18160,8 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
|
18147
18160
|
* ```typescript
|
|
18148
18161
|
* const instance = new ExchangeInstance("binance");
|
|
18149
18162
|
*
|
|
18150
|
-
* const candles = await instance.getCandles("BTCUSDT", "1m",
|
|
18163
|
+
* const candles = await instance.getCandles("BTCUSDT", "1m", 100);
|
|
18164
|
+
* const vwap = await instance.getAveragePrice("BTCUSDT");
|
|
18151
18165
|
* const formattedQty = await instance.formatQuantity("BTCUSDT", 0.001);
|
|
18152
18166
|
* const formattedPrice = await instance.formatPrice("BTCUSDT", 50000.123);
|
|
18153
18167
|
* ```
|
|
@@ -18163,27 +18177,88 @@ class ExchangeInstance {
|
|
|
18163
18177
|
/**
|
|
18164
18178
|
* Fetch candles from data source (API or database).
|
|
18165
18179
|
*
|
|
18180
|
+
* Automatically calculates the start date based on Date.now() and the requested interval/limit.
|
|
18181
|
+
* Uses the same logic as ClientExchange to ensure backwards compatibility.
|
|
18182
|
+
*
|
|
18166
18183
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
18167
18184
|
* @param interval - Candle time interval (e.g., "1m", "1h")
|
|
18168
|
-
* @param since - Start date for candle fetching
|
|
18169
18185
|
* @param limit - Maximum number of candles to fetch
|
|
18170
18186
|
* @returns Promise resolving to array of OHLCV candle data
|
|
18171
18187
|
*
|
|
18172
18188
|
* @example
|
|
18173
18189
|
* ```typescript
|
|
18174
18190
|
* const instance = new ExchangeInstance("binance");
|
|
18175
|
-
* const candles = await instance.getCandles("BTCUSDT", "1m",
|
|
18191
|
+
* const candles = await instance.getCandles("BTCUSDT", "1m", 100);
|
|
18176
18192
|
* ```
|
|
18177
18193
|
*/
|
|
18178
|
-
this.getCandles = async (symbol, interval,
|
|
18194
|
+
this.getCandles = async (symbol, interval, limit) => {
|
|
18179
18195
|
backtest$1.loggerService.info(EXCHANGE_METHOD_NAME_GET_CANDLES, {
|
|
18180
18196
|
exchangeName: this.exchangeName,
|
|
18181
18197
|
symbol,
|
|
18182
18198
|
interval,
|
|
18183
|
-
since,
|
|
18184
18199
|
limit,
|
|
18185
18200
|
});
|
|
18186
|
-
|
|
18201
|
+
const step = INTERVAL_MINUTES[interval];
|
|
18202
|
+
const adjust = step * limit - step;
|
|
18203
|
+
if (!adjust) {
|
|
18204
|
+
throw new Error(`ExchangeInstance unknown time adjust for interval=${interval}`);
|
|
18205
|
+
}
|
|
18206
|
+
const when = new Date(Date.now());
|
|
18207
|
+
const since = new Date(when.getTime() - adjust * 60 * 1000);
|
|
18208
|
+
const data = await this._schema.getCandles(symbol, interval, since, limit);
|
|
18209
|
+
// Filter candles to strictly match the requested range
|
|
18210
|
+
const whenTimestamp = when.getTime();
|
|
18211
|
+
const sinceTimestamp = since.getTime();
|
|
18212
|
+
const filteredData = data.filter((candle) => candle.timestamp >= sinceTimestamp && candle.timestamp <= whenTimestamp);
|
|
18213
|
+
if (filteredData.length < limit) {
|
|
18214
|
+
backtest$1.loggerService.warn(`ExchangeInstance Expected ${limit} candles, got ${filteredData.length}`);
|
|
18215
|
+
}
|
|
18216
|
+
return filteredData;
|
|
18217
|
+
};
|
|
18218
|
+
/**
|
|
18219
|
+
* Calculates VWAP (Volume Weighted Average Price) from last N 1m candles.
|
|
18220
|
+
* The number of candles is configurable via GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT.
|
|
18221
|
+
*
|
|
18222
|
+
* Formula:
|
|
18223
|
+
* - Typical Price = (high + low + close) / 3
|
|
18224
|
+
* - VWAP = sum(typical_price * volume) / sum(volume)
|
|
18225
|
+
*
|
|
18226
|
+
* If volume is zero, returns simple average of close prices.
|
|
18227
|
+
*
|
|
18228
|
+
* @param symbol - Trading pair symbol
|
|
18229
|
+
* @returns Promise resolving to VWAP price
|
|
18230
|
+
* @throws Error if no candles available
|
|
18231
|
+
*
|
|
18232
|
+
* @example
|
|
18233
|
+
* ```typescript
|
|
18234
|
+
* const instance = new ExchangeInstance("binance");
|
|
18235
|
+
* const vwap = await instance.getAveragePrice("BTCUSDT");
|
|
18236
|
+
* console.log(vwap); // 50125.43
|
|
18237
|
+
* ```
|
|
18238
|
+
*/
|
|
18239
|
+
this.getAveragePrice = async (symbol) => {
|
|
18240
|
+
backtest$1.loggerService.debug(`ExchangeInstance getAveragePrice`, {
|
|
18241
|
+
exchangeName: this.exchangeName,
|
|
18242
|
+
symbol,
|
|
18243
|
+
});
|
|
18244
|
+
const candles = await this.getCandles(symbol, "1m", GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT);
|
|
18245
|
+
if (candles.length === 0) {
|
|
18246
|
+
throw new Error(`ExchangeInstance getAveragePrice: no candles data for symbol=${symbol}`);
|
|
18247
|
+
}
|
|
18248
|
+
// VWAP (Volume Weighted Average Price)
|
|
18249
|
+
// Используем типичную цену (typical price) = (high + low + close) / 3
|
|
18250
|
+
const sumPriceVolume = candles.reduce((acc, candle) => {
|
|
18251
|
+
const typicalPrice = (candle.high + candle.low + candle.close) / 3;
|
|
18252
|
+
return acc + typicalPrice * candle.volume;
|
|
18253
|
+
}, 0);
|
|
18254
|
+
const totalVolume = candles.reduce((acc, candle) => acc + candle.volume, 0);
|
|
18255
|
+
if (totalVolume === 0) {
|
|
18256
|
+
// Если объем нулевой, возвращаем простое среднее close цен
|
|
18257
|
+
const sum = candles.reduce((acc, candle) => acc + candle.close, 0);
|
|
18258
|
+
return sum / candles.length;
|
|
18259
|
+
}
|
|
18260
|
+
const vwap = sumPriceVolume / totalVolume;
|
|
18261
|
+
return vwap;
|
|
18187
18262
|
};
|
|
18188
18263
|
/**
|
|
18189
18264
|
* Format quantity according to exchange precision rules.
|
|
@@ -18242,7 +18317,10 @@ class ExchangeInstance {
|
|
|
18242
18317
|
* ```typescript
|
|
18243
18318
|
* import { Exchange } from "./classes/Exchange";
|
|
18244
18319
|
*
|
|
18245
|
-
* const candles = await Exchange.getCandles("BTCUSDT", "1m",
|
|
18320
|
+
* const candles = await Exchange.getCandles("BTCUSDT", "1m", 100, {
|
|
18321
|
+
* exchangeName: "binance"
|
|
18322
|
+
* });
|
|
18323
|
+
* const vwap = await Exchange.getAveragePrice("BTCUSDT", {
|
|
18246
18324
|
* exchangeName: "binance"
|
|
18247
18325
|
* });
|
|
18248
18326
|
* const formatted = await Exchange.formatQuantity("BTCUSDT", 0.001, {
|
|
@@ -18260,17 +18338,31 @@ class ExchangeUtils {
|
|
|
18260
18338
|
/**
|
|
18261
18339
|
* Fetch candles from data source (API or database).
|
|
18262
18340
|
*
|
|
18341
|
+
* Automatically calculates the start date based on Date.now() and the requested interval/limit.
|
|
18342
|
+
* Uses the same logic as ClientExchange to ensure backwards compatibility.
|
|
18343
|
+
*
|
|
18263
18344
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
18264
18345
|
* @param interval - Candle time interval (e.g., "1m", "1h")
|
|
18265
|
-
* @param since - Start date for candle fetching
|
|
18266
18346
|
* @param limit - Maximum number of candles to fetch
|
|
18267
18347
|
* @param context - Execution context with exchange name
|
|
18268
18348
|
* @returns Promise resolving to array of OHLCV candle data
|
|
18269
18349
|
*/
|
|
18270
|
-
this.getCandles = async (symbol, interval,
|
|
18350
|
+
this.getCandles = async (symbol, interval, limit, context) => {
|
|
18271
18351
|
backtest$1.exchangeValidationService.validate(context.exchangeName, EXCHANGE_METHOD_NAME_GET_CANDLES);
|
|
18272
18352
|
const instance = this._getInstance(context.exchangeName);
|
|
18273
|
-
return await instance.getCandles(symbol, interval,
|
|
18353
|
+
return await instance.getCandles(symbol, interval, limit);
|
|
18354
|
+
};
|
|
18355
|
+
/**
|
|
18356
|
+
* Calculates VWAP (Volume Weighted Average Price) from last N 1m candles.
|
|
18357
|
+
*
|
|
18358
|
+
* @param symbol - Trading pair symbol
|
|
18359
|
+
* @param context - Execution context with exchange name
|
|
18360
|
+
* @returns Promise resolving to VWAP price
|
|
18361
|
+
*/
|
|
18362
|
+
this.getAveragePrice = async (symbol, context) => {
|
|
18363
|
+
backtest$1.exchangeValidationService.validate(context.exchangeName, EXCHANGE_METHOD_NAME_GET_AVERAGE_PRICE);
|
|
18364
|
+
const instance = this._getInstance(context.exchangeName);
|
|
18365
|
+
return await instance.getAveragePrice(symbol);
|
|
18274
18366
|
};
|
|
18275
18367
|
/**
|
|
18276
18368
|
* Format quantity according to exchange precision rules.
|
|
@@ -18308,7 +18400,10 @@ class ExchangeUtils {
|
|
|
18308
18400
|
* import { Exchange } from "./classes/Exchange";
|
|
18309
18401
|
*
|
|
18310
18402
|
* // Using static-like API with context
|
|
18311
|
-
* const candles = await Exchange.getCandles("BTCUSDT", "1m",
|
|
18403
|
+
* const candles = await Exchange.getCandles("BTCUSDT", "1m", 100, {
|
|
18404
|
+
* exchangeName: "binance"
|
|
18405
|
+
* });
|
|
18406
|
+
* const vwap = await Exchange.getAveragePrice("BTCUSDT", {
|
|
18312
18407
|
* exchangeName: "binance"
|
|
18313
18408
|
* });
|
|
18314
18409
|
* const qty = await Exchange.formatQuantity("BTCUSDT", 0.001, {
|
|
@@ -18320,7 +18415,8 @@ class ExchangeUtils {
|
|
|
18320
18415
|
*
|
|
18321
18416
|
* // Using instance API (no context needed, exchange set in constructor)
|
|
18322
18417
|
* const binance = new ExchangeInstance("binance");
|
|
18323
|
-
* const candles2 = await binance.getCandles("BTCUSDT", "1m",
|
|
18418
|
+
* const candles2 = await binance.getCandles("BTCUSDT", "1m", 100);
|
|
18419
|
+
* const vwap2 = await binance.getAveragePrice("BTCUSDT");
|
|
18324
18420
|
* ```
|
|
18325
18421
|
*/
|
|
18326
18422
|
const Exchange = new ExchangeUtils();
|
package/build/index.mjs
CHANGED
|
@@ -1436,7 +1436,7 @@ class LoggerService {
|
|
|
1436
1436
|
}
|
|
1437
1437
|
}
|
|
1438
1438
|
|
|
1439
|
-
const INTERVAL_MINUTES$
|
|
1439
|
+
const INTERVAL_MINUTES$3 = {
|
|
1440
1440
|
"1m": 1,
|
|
1441
1441
|
"3m": 3,
|
|
1442
1442
|
"5m": 5,
|
|
@@ -1581,7 +1581,7 @@ class ClientExchange {
|
|
|
1581
1581
|
interval,
|
|
1582
1582
|
limit,
|
|
1583
1583
|
});
|
|
1584
|
-
const step = INTERVAL_MINUTES$
|
|
1584
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
1585
1585
|
const adjust = step * limit - step;
|
|
1586
1586
|
if (!adjust) {
|
|
1587
1587
|
throw new Error(`ClientExchange unknown time adjust for interval=${interval}`);
|
|
@@ -1619,7 +1619,7 @@ class ClientExchange {
|
|
|
1619
1619
|
const since = new Date(this.params.execution.context.when.getTime());
|
|
1620
1620
|
const now = Date.now();
|
|
1621
1621
|
// Вычисляем конечное время запроса
|
|
1622
|
-
const step = INTERVAL_MINUTES$
|
|
1622
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
1623
1623
|
const endTime = since.getTime() + limit * step * 60 * 1000;
|
|
1624
1624
|
// Проверяем что запрошенный период не заходит за Date.now()
|
|
1625
1625
|
if (endTime > now) {
|
|
@@ -2843,7 +2843,7 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
2843
2843
|
walkerStopSubject: walkerStopSubject
|
|
2844
2844
|
});
|
|
2845
2845
|
|
|
2846
|
-
const INTERVAL_MINUTES$
|
|
2846
|
+
const INTERVAL_MINUTES$2 = {
|
|
2847
2847
|
"1m": 1,
|
|
2848
2848
|
"3m": 3,
|
|
2849
2849
|
"5m": 5,
|
|
@@ -3061,7 +3061,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
3061
3061
|
}
|
|
3062
3062
|
const currentTime = self.params.execution.context.when.getTime();
|
|
3063
3063
|
{
|
|
3064
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
3064
|
+
const intervalMinutes = INTERVAL_MINUTES$2[self.params.interval];
|
|
3065
3065
|
const intervalMs = intervalMinutes * 60 * 1000;
|
|
3066
3066
|
// Проверяем что прошел нужный интервал с последнего getSignal
|
|
3067
3067
|
if (self._lastSignalTimestamp !== null &&
|
|
@@ -4809,7 +4809,7 @@ class StrategyConnectionService {
|
|
|
4809
4809
|
* Maps FrameInterval to minutes for timestamp calculation.
|
|
4810
4810
|
* Used to generate timeframe arrays with proper spacing.
|
|
4811
4811
|
*/
|
|
4812
|
-
const INTERVAL_MINUTES = {
|
|
4812
|
+
const INTERVAL_MINUTES$1 = {
|
|
4813
4813
|
"1m": 1,
|
|
4814
4814
|
"3m": 3,
|
|
4815
4815
|
"5m": 5,
|
|
@@ -4838,7 +4838,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
4838
4838
|
symbol,
|
|
4839
4839
|
});
|
|
4840
4840
|
const { interval, startDate, endDate } = self.params;
|
|
4841
|
-
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
4841
|
+
const intervalMinutes = INTERVAL_MINUTES$1[interval];
|
|
4842
4842
|
if (!intervalMinutes) {
|
|
4843
4843
|
throw new Error(`ClientFrame unknown interval: ${interval}`);
|
|
4844
4844
|
}
|
|
@@ -18132,8 +18132,21 @@ class ConstantUtils {
|
|
|
18132
18132
|
const Constant = new ConstantUtils();
|
|
18133
18133
|
|
|
18134
18134
|
const EXCHANGE_METHOD_NAME_GET_CANDLES = "ExchangeUtils.getCandles";
|
|
18135
|
+
const EXCHANGE_METHOD_NAME_GET_AVERAGE_PRICE = "ExchangeUtils.getAveragePrice";
|
|
18135
18136
|
const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
|
|
18136
18137
|
const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
18138
|
+
const INTERVAL_MINUTES = {
|
|
18139
|
+
"1m": 1,
|
|
18140
|
+
"3m": 3,
|
|
18141
|
+
"5m": 5,
|
|
18142
|
+
"15m": 15,
|
|
18143
|
+
"30m": 30,
|
|
18144
|
+
"1h": 60,
|
|
18145
|
+
"2h": 120,
|
|
18146
|
+
"4h": 240,
|
|
18147
|
+
"6h": 360,
|
|
18148
|
+
"8h": 480,
|
|
18149
|
+
};
|
|
18137
18150
|
/**
|
|
18138
18151
|
* Instance class for exchange operations on a specific exchange.
|
|
18139
18152
|
*
|
|
@@ -18145,7 +18158,8 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
|
18145
18158
|
* ```typescript
|
|
18146
18159
|
* const instance = new ExchangeInstance("binance");
|
|
18147
18160
|
*
|
|
18148
|
-
* const candles = await instance.getCandles("BTCUSDT", "1m",
|
|
18161
|
+
* const candles = await instance.getCandles("BTCUSDT", "1m", 100);
|
|
18162
|
+
* const vwap = await instance.getAveragePrice("BTCUSDT");
|
|
18149
18163
|
* const formattedQty = await instance.formatQuantity("BTCUSDT", 0.001);
|
|
18150
18164
|
* const formattedPrice = await instance.formatPrice("BTCUSDT", 50000.123);
|
|
18151
18165
|
* ```
|
|
@@ -18161,27 +18175,88 @@ class ExchangeInstance {
|
|
|
18161
18175
|
/**
|
|
18162
18176
|
* Fetch candles from data source (API or database).
|
|
18163
18177
|
*
|
|
18178
|
+
* Automatically calculates the start date based on Date.now() and the requested interval/limit.
|
|
18179
|
+
* Uses the same logic as ClientExchange to ensure backwards compatibility.
|
|
18180
|
+
*
|
|
18164
18181
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
18165
18182
|
* @param interval - Candle time interval (e.g., "1m", "1h")
|
|
18166
|
-
* @param since - Start date for candle fetching
|
|
18167
18183
|
* @param limit - Maximum number of candles to fetch
|
|
18168
18184
|
* @returns Promise resolving to array of OHLCV candle data
|
|
18169
18185
|
*
|
|
18170
18186
|
* @example
|
|
18171
18187
|
* ```typescript
|
|
18172
18188
|
* const instance = new ExchangeInstance("binance");
|
|
18173
|
-
* const candles = await instance.getCandles("BTCUSDT", "1m",
|
|
18189
|
+
* const candles = await instance.getCandles("BTCUSDT", "1m", 100);
|
|
18174
18190
|
* ```
|
|
18175
18191
|
*/
|
|
18176
|
-
this.getCandles = async (symbol, interval,
|
|
18192
|
+
this.getCandles = async (symbol, interval, limit) => {
|
|
18177
18193
|
backtest$1.loggerService.info(EXCHANGE_METHOD_NAME_GET_CANDLES, {
|
|
18178
18194
|
exchangeName: this.exchangeName,
|
|
18179
18195
|
symbol,
|
|
18180
18196
|
interval,
|
|
18181
|
-
since,
|
|
18182
18197
|
limit,
|
|
18183
18198
|
});
|
|
18184
|
-
|
|
18199
|
+
const step = INTERVAL_MINUTES[interval];
|
|
18200
|
+
const adjust = step * limit - step;
|
|
18201
|
+
if (!adjust) {
|
|
18202
|
+
throw new Error(`ExchangeInstance unknown time adjust for interval=${interval}`);
|
|
18203
|
+
}
|
|
18204
|
+
const when = new Date(Date.now());
|
|
18205
|
+
const since = new Date(when.getTime() - adjust * 60 * 1000);
|
|
18206
|
+
const data = await this._schema.getCandles(symbol, interval, since, limit);
|
|
18207
|
+
// Filter candles to strictly match the requested range
|
|
18208
|
+
const whenTimestamp = when.getTime();
|
|
18209
|
+
const sinceTimestamp = since.getTime();
|
|
18210
|
+
const filteredData = data.filter((candle) => candle.timestamp >= sinceTimestamp && candle.timestamp <= whenTimestamp);
|
|
18211
|
+
if (filteredData.length < limit) {
|
|
18212
|
+
backtest$1.loggerService.warn(`ExchangeInstance Expected ${limit} candles, got ${filteredData.length}`);
|
|
18213
|
+
}
|
|
18214
|
+
return filteredData;
|
|
18215
|
+
};
|
|
18216
|
+
/**
|
|
18217
|
+
* Calculates VWAP (Volume Weighted Average Price) from last N 1m candles.
|
|
18218
|
+
* The number of candles is configurable via GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT.
|
|
18219
|
+
*
|
|
18220
|
+
* Formula:
|
|
18221
|
+
* - Typical Price = (high + low + close) / 3
|
|
18222
|
+
* - VWAP = sum(typical_price * volume) / sum(volume)
|
|
18223
|
+
*
|
|
18224
|
+
* If volume is zero, returns simple average of close prices.
|
|
18225
|
+
*
|
|
18226
|
+
* @param symbol - Trading pair symbol
|
|
18227
|
+
* @returns Promise resolving to VWAP price
|
|
18228
|
+
* @throws Error if no candles available
|
|
18229
|
+
*
|
|
18230
|
+
* @example
|
|
18231
|
+
* ```typescript
|
|
18232
|
+
* const instance = new ExchangeInstance("binance");
|
|
18233
|
+
* const vwap = await instance.getAveragePrice("BTCUSDT");
|
|
18234
|
+
* console.log(vwap); // 50125.43
|
|
18235
|
+
* ```
|
|
18236
|
+
*/
|
|
18237
|
+
this.getAveragePrice = async (symbol) => {
|
|
18238
|
+
backtest$1.loggerService.debug(`ExchangeInstance getAveragePrice`, {
|
|
18239
|
+
exchangeName: this.exchangeName,
|
|
18240
|
+
symbol,
|
|
18241
|
+
});
|
|
18242
|
+
const candles = await this.getCandles(symbol, "1m", GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT);
|
|
18243
|
+
if (candles.length === 0) {
|
|
18244
|
+
throw new Error(`ExchangeInstance getAveragePrice: no candles data for symbol=${symbol}`);
|
|
18245
|
+
}
|
|
18246
|
+
// VWAP (Volume Weighted Average Price)
|
|
18247
|
+
// Используем типичную цену (typical price) = (high + low + close) / 3
|
|
18248
|
+
const sumPriceVolume = candles.reduce((acc, candle) => {
|
|
18249
|
+
const typicalPrice = (candle.high + candle.low + candle.close) / 3;
|
|
18250
|
+
return acc + typicalPrice * candle.volume;
|
|
18251
|
+
}, 0);
|
|
18252
|
+
const totalVolume = candles.reduce((acc, candle) => acc + candle.volume, 0);
|
|
18253
|
+
if (totalVolume === 0) {
|
|
18254
|
+
// Если объем нулевой, возвращаем простое среднее close цен
|
|
18255
|
+
const sum = candles.reduce((acc, candle) => acc + candle.close, 0);
|
|
18256
|
+
return sum / candles.length;
|
|
18257
|
+
}
|
|
18258
|
+
const vwap = sumPriceVolume / totalVolume;
|
|
18259
|
+
return vwap;
|
|
18185
18260
|
};
|
|
18186
18261
|
/**
|
|
18187
18262
|
* Format quantity according to exchange precision rules.
|
|
@@ -18240,7 +18315,10 @@ class ExchangeInstance {
|
|
|
18240
18315
|
* ```typescript
|
|
18241
18316
|
* import { Exchange } from "./classes/Exchange";
|
|
18242
18317
|
*
|
|
18243
|
-
* const candles = await Exchange.getCandles("BTCUSDT", "1m",
|
|
18318
|
+
* const candles = await Exchange.getCandles("BTCUSDT", "1m", 100, {
|
|
18319
|
+
* exchangeName: "binance"
|
|
18320
|
+
* });
|
|
18321
|
+
* const vwap = await Exchange.getAveragePrice("BTCUSDT", {
|
|
18244
18322
|
* exchangeName: "binance"
|
|
18245
18323
|
* });
|
|
18246
18324
|
* const formatted = await Exchange.formatQuantity("BTCUSDT", 0.001, {
|
|
@@ -18258,17 +18336,31 @@ class ExchangeUtils {
|
|
|
18258
18336
|
/**
|
|
18259
18337
|
* Fetch candles from data source (API or database).
|
|
18260
18338
|
*
|
|
18339
|
+
* Automatically calculates the start date based on Date.now() and the requested interval/limit.
|
|
18340
|
+
* Uses the same logic as ClientExchange to ensure backwards compatibility.
|
|
18341
|
+
*
|
|
18261
18342
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
18262
18343
|
* @param interval - Candle time interval (e.g., "1m", "1h")
|
|
18263
|
-
* @param since - Start date for candle fetching
|
|
18264
18344
|
* @param limit - Maximum number of candles to fetch
|
|
18265
18345
|
* @param context - Execution context with exchange name
|
|
18266
18346
|
* @returns Promise resolving to array of OHLCV candle data
|
|
18267
18347
|
*/
|
|
18268
|
-
this.getCandles = async (symbol, interval,
|
|
18348
|
+
this.getCandles = async (symbol, interval, limit, context) => {
|
|
18269
18349
|
backtest$1.exchangeValidationService.validate(context.exchangeName, EXCHANGE_METHOD_NAME_GET_CANDLES);
|
|
18270
18350
|
const instance = this._getInstance(context.exchangeName);
|
|
18271
|
-
return await instance.getCandles(symbol, interval,
|
|
18351
|
+
return await instance.getCandles(symbol, interval, limit);
|
|
18352
|
+
};
|
|
18353
|
+
/**
|
|
18354
|
+
* Calculates VWAP (Volume Weighted Average Price) from last N 1m candles.
|
|
18355
|
+
*
|
|
18356
|
+
* @param symbol - Trading pair symbol
|
|
18357
|
+
* @param context - Execution context with exchange name
|
|
18358
|
+
* @returns Promise resolving to VWAP price
|
|
18359
|
+
*/
|
|
18360
|
+
this.getAveragePrice = async (symbol, context) => {
|
|
18361
|
+
backtest$1.exchangeValidationService.validate(context.exchangeName, EXCHANGE_METHOD_NAME_GET_AVERAGE_PRICE);
|
|
18362
|
+
const instance = this._getInstance(context.exchangeName);
|
|
18363
|
+
return await instance.getAveragePrice(symbol);
|
|
18272
18364
|
};
|
|
18273
18365
|
/**
|
|
18274
18366
|
* Format quantity according to exchange precision rules.
|
|
@@ -18306,7 +18398,10 @@ class ExchangeUtils {
|
|
|
18306
18398
|
* import { Exchange } from "./classes/Exchange";
|
|
18307
18399
|
*
|
|
18308
18400
|
* // Using static-like API with context
|
|
18309
|
-
* const candles = await Exchange.getCandles("BTCUSDT", "1m",
|
|
18401
|
+
* const candles = await Exchange.getCandles("BTCUSDT", "1m", 100, {
|
|
18402
|
+
* exchangeName: "binance"
|
|
18403
|
+
* });
|
|
18404
|
+
* const vwap = await Exchange.getAveragePrice("BTCUSDT", {
|
|
18310
18405
|
* exchangeName: "binance"
|
|
18311
18406
|
* });
|
|
18312
18407
|
* const qty = await Exchange.formatQuantity("BTCUSDT", 0.001, {
|
|
@@ -18318,7 +18413,8 @@ class ExchangeUtils {
|
|
|
18318
18413
|
*
|
|
18319
18414
|
* // Using instance API (no context needed, exchange set in constructor)
|
|
18320
18415
|
* const binance = new ExchangeInstance("binance");
|
|
18321
|
-
* const candles2 = await binance.getCandles("BTCUSDT", "1m",
|
|
18416
|
+
* const candles2 = await binance.getCandles("BTCUSDT", "1m", 100);
|
|
18417
|
+
* const vwap2 = await binance.getAveragePrice("BTCUSDT");
|
|
18322
18418
|
* ```
|
|
18323
18419
|
*/
|
|
18324
18420
|
const Exchange = new ExchangeUtils();
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -7720,7 +7720,10 @@ declare const Risk: RiskUtils;
|
|
|
7720
7720
|
* ```typescript
|
|
7721
7721
|
* import { Exchange } from "./classes/Exchange";
|
|
7722
7722
|
*
|
|
7723
|
-
* const candles = await Exchange.getCandles("BTCUSDT", "1m",
|
|
7723
|
+
* const candles = await Exchange.getCandles("BTCUSDT", "1m", 100, {
|
|
7724
|
+
* exchangeName: "binance"
|
|
7725
|
+
* });
|
|
7726
|
+
* const vwap = await Exchange.getAveragePrice("BTCUSDT", {
|
|
7724
7727
|
* exchangeName: "binance"
|
|
7725
7728
|
* });
|
|
7726
7729
|
* const formatted = await Exchange.formatQuantity("BTCUSDT", 0.001, {
|
|
@@ -7737,16 +7740,28 @@ declare class ExchangeUtils {
|
|
|
7737
7740
|
/**
|
|
7738
7741
|
* Fetch candles from data source (API or database).
|
|
7739
7742
|
*
|
|
7743
|
+
* Automatically calculates the start date based on Date.now() and the requested interval/limit.
|
|
7744
|
+
* Uses the same logic as ClientExchange to ensure backwards compatibility.
|
|
7745
|
+
*
|
|
7740
7746
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
7741
7747
|
* @param interval - Candle time interval (e.g., "1m", "1h")
|
|
7742
|
-
* @param since - Start date for candle fetching
|
|
7743
7748
|
* @param limit - Maximum number of candles to fetch
|
|
7744
7749
|
* @param context - Execution context with exchange name
|
|
7745
7750
|
* @returns Promise resolving to array of OHLCV candle data
|
|
7746
7751
|
*/
|
|
7747
|
-
getCandles: (symbol: string, interval: CandleInterval,
|
|
7752
|
+
getCandles: (symbol: string, interval: CandleInterval, limit: number, context: {
|
|
7748
7753
|
exchangeName: ExchangeName;
|
|
7749
7754
|
}) => Promise<ICandleData[]>;
|
|
7755
|
+
/**
|
|
7756
|
+
* Calculates VWAP (Volume Weighted Average Price) from last N 1m candles.
|
|
7757
|
+
*
|
|
7758
|
+
* @param symbol - Trading pair symbol
|
|
7759
|
+
* @param context - Execution context with exchange name
|
|
7760
|
+
* @returns Promise resolving to VWAP price
|
|
7761
|
+
*/
|
|
7762
|
+
getAveragePrice: (symbol: string, context: {
|
|
7763
|
+
exchangeName: ExchangeName;
|
|
7764
|
+
}) => Promise<number>;
|
|
7750
7765
|
/**
|
|
7751
7766
|
* Format quantity according to exchange precision rules.
|
|
7752
7767
|
*
|
|
@@ -7778,7 +7793,10 @@ declare class ExchangeUtils {
|
|
|
7778
7793
|
* import { Exchange } from "./classes/Exchange";
|
|
7779
7794
|
*
|
|
7780
7795
|
* // Using static-like API with context
|
|
7781
|
-
* const candles = await Exchange.getCandles("BTCUSDT", "1m",
|
|
7796
|
+
* const candles = await Exchange.getCandles("BTCUSDT", "1m", 100, {
|
|
7797
|
+
* exchangeName: "binance"
|
|
7798
|
+
* });
|
|
7799
|
+
* const vwap = await Exchange.getAveragePrice("BTCUSDT", {
|
|
7782
7800
|
* exchangeName: "binance"
|
|
7783
7801
|
* });
|
|
7784
7802
|
* const qty = await Exchange.formatQuantity("BTCUSDT", 0.001, {
|
|
@@ -7790,7 +7808,8 @@ declare class ExchangeUtils {
|
|
|
7790
7808
|
*
|
|
7791
7809
|
* // Using instance API (no context needed, exchange set in constructor)
|
|
7792
7810
|
* const binance = new ExchangeInstance("binance");
|
|
7793
|
-
* const candles2 = await binance.getCandles("BTCUSDT", "1m",
|
|
7811
|
+
* const candles2 = await binance.getCandles("BTCUSDT", "1m", 100);
|
|
7812
|
+
* const vwap2 = await binance.getAveragePrice("BTCUSDT");
|
|
7794
7813
|
* ```
|
|
7795
7814
|
*/
|
|
7796
7815
|
declare const Exchange: ExchangeUtils;
|