backtest-kit 1.13.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/build/index.cjs +1063 -368
- package/build/index.mjs +1014 -332
- package/package.json +1 -1
- package/types.d.ts +3266 -2843
package/build/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createActivator } from 'di-kit';
|
|
2
2
|
import { scoped } from 'di-scoped';
|
|
3
|
-
import { Subject, trycatch, errorData, getErrorMessage, sleep, memoize, makeExtendable, singleshot, not, retry, randomString, isObject, ToolRegistry, and, resolveDocuments,
|
|
3
|
+
import { Subject, trycatch, errorData, getErrorMessage, sleep, memoize, makeExtendable, singleshot, not, retry, randomString, str, isObject, ToolRegistry, and, resolveDocuments, timeout, TIMEOUT_SYMBOL as TIMEOUT_SYMBOL$1, compose, iterateDocuments, distinctDocuments, queued, singlerun } from 'functools-kit';
|
|
4
4
|
import * as fs from 'fs/promises';
|
|
5
5
|
import fs__default, { mkdir, writeFile } from 'fs/promises';
|
|
6
6
|
import path, { join, dirname } from 'path';
|
|
@@ -521,14 +521,21 @@ const breakevenSubject = new Subject();
|
|
|
521
521
|
*/
|
|
522
522
|
const riskSubject = new Subject();
|
|
523
523
|
/**
|
|
524
|
-
*
|
|
524
|
+
* Schedule ping emitter for scheduled signal monitoring events.
|
|
525
525
|
* Emits every minute when a scheduled signal is being monitored (waiting for activation).
|
|
526
526
|
* Allows users to track scheduled signal lifecycle and implement custom cancellation logic.
|
|
527
527
|
*/
|
|
528
|
-
const
|
|
528
|
+
const schedulePingSubject = new Subject();
|
|
529
|
+
/**
|
|
530
|
+
* Active ping emitter for active pending signal monitoring events.
|
|
531
|
+
* Emits every minute when an active pending signal is being monitored.
|
|
532
|
+
* Allows users to track active signal lifecycle and implement custom dynamic management logic.
|
|
533
|
+
*/
|
|
534
|
+
const activePingSubject = new Subject();
|
|
529
535
|
|
|
530
536
|
var emitters = /*#__PURE__*/Object.freeze({
|
|
531
537
|
__proto__: null,
|
|
538
|
+
activePingSubject: activePingSubject,
|
|
532
539
|
breakevenSubject: breakevenSubject,
|
|
533
540
|
doneBacktestSubject: doneBacktestSubject,
|
|
534
541
|
doneLiveSubject: doneLiveSubject,
|
|
@@ -538,11 +545,11 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
538
545
|
partialLossSubject: partialLossSubject,
|
|
539
546
|
partialProfitSubject: partialProfitSubject,
|
|
540
547
|
performanceEmitter: performanceEmitter,
|
|
541
|
-
pingSubject: pingSubject,
|
|
542
548
|
progressBacktestEmitter: progressBacktestEmitter,
|
|
543
549
|
progressOptimizerEmitter: progressOptimizerEmitter,
|
|
544
550
|
progressWalkerEmitter: progressWalkerEmitter,
|
|
545
551
|
riskSubject: riskSubject,
|
|
552
|
+
schedulePingSubject: schedulePingSubject,
|
|
546
553
|
signalBacktestEmitter: signalBacktestEmitter,
|
|
547
554
|
signalEmitter: signalEmitter,
|
|
548
555
|
signalLiveEmitter: signalLiveEmitter,
|
|
@@ -753,11 +760,16 @@ class ClientExchange {
|
|
|
753
760
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
754
761
|
const sinceTimestamp = since.getTime();
|
|
755
762
|
const filteredData = allData.filter((candle) => candle.timestamp >= sinceTimestamp && candle.timestamp <= whenTimestamp);
|
|
756
|
-
|
|
757
|
-
|
|
763
|
+
// Apply distinct by timestamp to remove duplicates
|
|
764
|
+
const uniqueData = Array.from(new Map(filteredData.map((candle) => [candle.timestamp, candle])).values());
|
|
765
|
+
if (filteredData.length !== uniqueData.length) {
|
|
766
|
+
this.params.logger.warn(`ClientExchange Removed ${filteredData.length - uniqueData.length} duplicate candles by timestamp`);
|
|
758
767
|
}
|
|
759
|
-
|
|
760
|
-
|
|
768
|
+
if (uniqueData.length < limit) {
|
|
769
|
+
this.params.logger.warn(`ClientExchange Expected ${limit} candles, got ${uniqueData.length}`);
|
|
770
|
+
}
|
|
771
|
+
await CALL_CANDLE_DATA_CALLBACKS_FN(this, symbol, interval, since, limit, uniqueData);
|
|
772
|
+
return uniqueData;
|
|
761
773
|
}
|
|
762
774
|
/**
|
|
763
775
|
* Fetches future candles forwards from execution context time.
|
|
@@ -806,11 +818,16 @@ class ClientExchange {
|
|
|
806
818
|
// Filter candles to strictly match the requested range
|
|
807
819
|
const sinceTimestamp = since.getTime();
|
|
808
820
|
const filteredData = allData.filter((candle) => candle.timestamp >= sinceTimestamp && candle.timestamp <= endTime);
|
|
809
|
-
|
|
810
|
-
|
|
821
|
+
// Apply distinct by timestamp to remove duplicates
|
|
822
|
+
const uniqueData = Array.from(new Map(filteredData.map((candle) => [candle.timestamp, candle])).values());
|
|
823
|
+
if (filteredData.length !== uniqueData.length) {
|
|
824
|
+
this.params.logger.warn(`ClientExchange getNextCandles: Removed ${filteredData.length - uniqueData.length} duplicate candles by timestamp`);
|
|
825
|
+
}
|
|
826
|
+
if (uniqueData.length < limit) {
|
|
827
|
+
this.params.logger.warn(`ClientExchange getNextCandles: Expected ${limit} candles, got ${uniqueData.length}`);
|
|
811
828
|
}
|
|
812
|
-
await CALL_CANDLE_DATA_CALLBACKS_FN(this, symbol, interval, since, limit,
|
|
813
|
-
return
|
|
829
|
+
await CALL_CANDLE_DATA_CALLBACKS_FN(this, symbol, interval, since, limit, uniqueData);
|
|
830
|
+
return uniqueData;
|
|
814
831
|
}
|
|
815
832
|
/**
|
|
816
833
|
* Calculates VWAP (Volume Weighted Average Price) from last N 1m candles.
|
|
@@ -3386,14 +3403,40 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
|
|
|
3386
3403
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, activationTime, self.params.execution.context.backtest);
|
|
3387
3404
|
return result;
|
|
3388
3405
|
};
|
|
3389
|
-
const
|
|
3406
|
+
const CALL_SCHEDULE_PING_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, scheduled, timestamp, backtest) => {
|
|
3390
3407
|
await ExecutionContextService.runInContext(async () => {
|
|
3391
3408
|
const publicSignal = TO_PUBLIC_SIGNAL(scheduled);
|
|
3392
|
-
// Call system
|
|
3393
|
-
await self.params.
|
|
3394
|
-
// Call user
|
|
3395
|
-
if (self.params.callbacks?.
|
|
3396
|
-
await self.params.callbacks.
|
|
3409
|
+
// Call system onSchedulePing callback first (emits to pingSubject)
|
|
3410
|
+
await self.params.onSchedulePing(self.params.execution.context.symbol, self.params.method.context.strategyName, self.params.method.context.exchangeName, publicSignal, self.params.execution.context.backtest, timestamp);
|
|
3411
|
+
// Call user onSchedulePing callback only if signal is still active (not cancelled, not activated)
|
|
3412
|
+
if (self.params.callbacks?.onSchedulePing) {
|
|
3413
|
+
await self.params.callbacks.onSchedulePing(self.params.execution.context.symbol, publicSignal, new Date(timestamp), self.params.execution.context.backtest);
|
|
3414
|
+
}
|
|
3415
|
+
}, {
|
|
3416
|
+
when: new Date(timestamp),
|
|
3417
|
+
symbol: symbol,
|
|
3418
|
+
backtest: backtest,
|
|
3419
|
+
});
|
|
3420
|
+
}), {
|
|
3421
|
+
fallback: (error) => {
|
|
3422
|
+
const message = "ClientStrategy CALL_SCHEDULE_PING_CALLBACKS_FN thrown";
|
|
3423
|
+
const payload = {
|
|
3424
|
+
error: errorData(error),
|
|
3425
|
+
message: getErrorMessage(error),
|
|
3426
|
+
};
|
|
3427
|
+
backtest$1.loggerService.warn(message, payload);
|
|
3428
|
+
console.warn(message, payload);
|
|
3429
|
+
errorEmitter.next(error);
|
|
3430
|
+
},
|
|
3431
|
+
});
|
|
3432
|
+
const CALL_ACTIVE_PING_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, pending, timestamp, backtest) => {
|
|
3433
|
+
await ExecutionContextService.runInContext(async () => {
|
|
3434
|
+
const publicSignal = TO_PUBLIC_SIGNAL(pending);
|
|
3435
|
+
// Call system onActivePing callback first (emits to activePingSubject)
|
|
3436
|
+
await self.params.onActivePing(self.params.execution.context.symbol, self.params.method.context.strategyName, self.params.method.context.exchangeName, publicSignal, self.params.execution.context.backtest, timestamp);
|
|
3437
|
+
// Call user onActivePing callback only if signal is still active (not closed)
|
|
3438
|
+
if (self.params.callbacks?.onActivePing) {
|
|
3439
|
+
await self.params.callbacks.onActivePing(self.params.execution.context.symbol, publicSignal, new Date(timestamp), self.params.execution.context.backtest);
|
|
3397
3440
|
}
|
|
3398
3441
|
}, {
|
|
3399
3442
|
when: new Date(timestamp),
|
|
@@ -3402,7 +3445,7 @@ const CALL_PING_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, scheduled
|
|
|
3402
3445
|
});
|
|
3403
3446
|
}), {
|
|
3404
3447
|
fallback: (error) => {
|
|
3405
|
-
const message = "ClientStrategy
|
|
3448
|
+
const message = "ClientStrategy CALL_ACTIVE_PING_CALLBACKS_FN thrown";
|
|
3406
3449
|
const payload = {
|
|
3407
3450
|
error: errorData(error),
|
|
3408
3451
|
message: getErrorMessage(error),
|
|
@@ -3774,10 +3817,10 @@ const CALL_BREAKEVEN_CLEAR_FN = trycatch(beginTime(async (self, symbol, signal,
|
|
|
3774
3817
|
});
|
|
3775
3818
|
const RETURN_SCHEDULED_SIGNAL_ACTIVE_FN = async (self, scheduled, currentPrice) => {
|
|
3776
3819
|
const currentTime = self.params.execution.context.when.getTime();
|
|
3777
|
-
await
|
|
3820
|
+
await CALL_SCHEDULE_PING_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, currentTime, self.params.execution.context.backtest);
|
|
3778
3821
|
const pnl = toProfitLossDto(scheduled, currentPrice);
|
|
3779
3822
|
const result = {
|
|
3780
|
-
action: "
|
|
3823
|
+
action: "waiting",
|
|
3781
3824
|
signal: TO_PUBLIC_SIGNAL(scheduled),
|
|
3782
3825
|
currentPrice: currentPrice,
|
|
3783
3826
|
strategyName: self.params.method.context.strategyName,
|
|
@@ -3904,6 +3947,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
3904
3947
|
let percentTp = 0;
|
|
3905
3948
|
let percentSl = 0;
|
|
3906
3949
|
const currentTime = self.params.execution.context.when.getTime();
|
|
3950
|
+
await CALL_ACTIVE_PING_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentTime, self.params.execution.context.backtest);
|
|
3907
3951
|
// Calculate percentage of path to TP/SL for partial fill/loss callbacks
|
|
3908
3952
|
{
|
|
3909
3953
|
if (signal.position === "long") {
|
|
@@ -4109,7 +4153,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
|
|
|
4109
4153
|
const averagePrice = GET_AVG_PRICE_FN(recentCandles);
|
|
4110
4154
|
// КРИТИЧНО: Проверяем был ли сигнал отменен пользователем через cancel()
|
|
4111
4155
|
if (self._cancelledSignal) {
|
|
4112
|
-
// Сигнал был отменен через cancel() в
|
|
4156
|
+
// Сигнал был отменен через cancel() в onSchedulePing
|
|
4113
4157
|
const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user");
|
|
4114
4158
|
return { activated: false, cancelled: true, activationIndex: i, result };
|
|
4115
4159
|
}
|
|
@@ -4166,7 +4210,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
|
|
|
4166
4210
|
result: null,
|
|
4167
4211
|
};
|
|
4168
4212
|
}
|
|
4169
|
-
await
|
|
4213
|
+
await CALL_SCHEDULE_PING_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, candle.timestamp, true);
|
|
4170
4214
|
}
|
|
4171
4215
|
return {
|
|
4172
4216
|
activated: false,
|
|
@@ -4192,6 +4236,7 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
4192
4236
|
const startIndex = Math.max(0, i - (candlesCount - 1));
|
|
4193
4237
|
const recentCandles = candles.slice(startIndex, i + 1);
|
|
4194
4238
|
const averagePrice = GET_AVG_PRICE_FN(recentCandles);
|
|
4239
|
+
await CALL_ACTIVE_PING_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentCandleTimestamp, true);
|
|
4195
4240
|
let shouldClose = false;
|
|
4196
4241
|
let closeReason;
|
|
4197
4242
|
// Check time expiration FIRST (КРИТИЧНО!)
|
|
@@ -4737,7 +4782,15 @@ class ClientStrategy {
|
|
|
4737
4782
|
return result;
|
|
4738
4783
|
}
|
|
4739
4784
|
if (activated) {
|
|
4740
|
-
|
|
4785
|
+
// КРИТИЧНО: activationIndex - индекс свечи активации в массиве candles
|
|
4786
|
+
// BacktestLogicPrivateService включил буфер в начало массива, поэтому перед activationIndex достаточно свечей
|
|
4787
|
+
// PROCESS_PENDING_SIGNAL_CANDLES_FN пропустит первые bufferCandlesCount свечей для VWAP
|
|
4788
|
+
// Чтобы обработка началась со СЛЕДУЮЩЕЙ свечи после активации (activationIndex + 1),
|
|
4789
|
+
// нужно взять срез начиная с (activationIndex + 1 - bufferCandlesCount)
|
|
4790
|
+
// Это даст буфер ИЗ scheduled фазы + свеча после активации как первая обрабатываемая
|
|
4791
|
+
const bufferCandlesCount = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT - 1;
|
|
4792
|
+
const sliceStart = Math.max(0, activationIndex + 1 - bufferCandlesCount);
|
|
4793
|
+
const remainingCandles = candles.slice(sliceStart);
|
|
4741
4794
|
if (remainingCandles.length === 0) {
|
|
4742
4795
|
const candlesCount = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT;
|
|
4743
4796
|
const recentCandles = candles.slice(Math.max(0, activationIndex - (candlesCount - 1)), activationIndex + 1);
|
|
@@ -4753,35 +4806,23 @@ class ClientStrategy {
|
|
|
4753
4806
|
const lastCandleTimestamp = candles[candles.length - 1].timestamp;
|
|
4754
4807
|
const elapsedTime = lastCandleTimestamp - scheduled.scheduledAt;
|
|
4755
4808
|
if (elapsedTime < maxTimeToWait) {
|
|
4756
|
-
//
|
|
4757
|
-
//
|
|
4758
|
-
|
|
4759
|
-
const
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
signal: TO_PUBLIC_SIGNAL(scheduled),
|
|
4774
|
-
currentPrice: lastPrice,
|
|
4775
|
-
percentSl: 0,
|
|
4776
|
-
percentTp: 0,
|
|
4777
|
-
pnl,
|
|
4778
|
-
strategyName: this.params.method.context.strategyName,
|
|
4779
|
-
exchangeName: this.params.method.context.exchangeName,
|
|
4780
|
-
frameName: this.params.method.context.frameName,
|
|
4781
|
-
symbol: this.params.execution.context.symbol,
|
|
4782
|
-
backtest: this.params.execution.context.backtest,
|
|
4783
|
-
};
|
|
4784
|
-
return result; // Cast to IStrategyBacktestResult (which includes Active)
|
|
4809
|
+
// EDGE CASE: backtest() called with partial candle data (should never happen in production)
|
|
4810
|
+
// In real backtest flow this won't happen as we process all candles at once
|
|
4811
|
+
// This indicates incorrect usage of backtest() - throw error instead of returning partial result
|
|
4812
|
+
const bufferCandlesCount = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT - 1;
|
|
4813
|
+
// For scheduled signal that has NOT activated: buffer + wait time only (no lifetime yet)
|
|
4814
|
+
const requiredCandlesCount = bufferCandlesCount + GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + 1;
|
|
4815
|
+
throw new Error(str.newline(`ClientStrategy backtest: Insufficient candle data for scheduled signal (not yet activated). ` +
|
|
4816
|
+
`Signal scheduled at ${new Date(scheduled.scheduledAt).toISOString()}, ` +
|
|
4817
|
+
`last candle at ${new Date(lastCandleTimestamp).toISOString()}. ` +
|
|
4818
|
+
`Elapsed: ${Math.floor(elapsedTime / 60000)}min of ${GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES}min wait time. ` +
|
|
4819
|
+
`Provided ${candles.length} candles, but need at least ${requiredCandlesCount} candles. ` +
|
|
4820
|
+
`\nBreakdown: ` +
|
|
4821
|
+
`${bufferCandlesCount} buffer (VWAP) + ` +
|
|
4822
|
+
`${GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES} wait (for activation) = ${requiredCandlesCount} total. ` +
|
|
4823
|
+
`\nBuffer explanation: VWAP calculation requires ${GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT} candles, ` +
|
|
4824
|
+
`so first ${bufferCandlesCount} candles are skipped during scheduled phase processing. ` +
|
|
4825
|
+
`Provide complete candle range: [scheduledAt - ${bufferCandlesCount}min, scheduledAt + ${GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES}min].`));
|
|
4785
4826
|
}
|
|
4786
4827
|
// Timeout reached - cancel the scheduled signal
|
|
4787
4828
|
const candlesCount = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT;
|
|
@@ -4811,9 +4852,29 @@ class ClientStrategy {
|
|
|
4811
4852
|
if (closedResult) {
|
|
4812
4853
|
return closedResult;
|
|
4813
4854
|
}
|
|
4855
|
+
// Signal didn't close during candle processing - check if we have enough data
|
|
4814
4856
|
const lastCandles = candles.slice(-GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT);
|
|
4815
4857
|
const lastPrice = GET_AVG_PRICE_FN(lastCandles);
|
|
4816
4858
|
const closeTimestamp = lastCandles[lastCandles.length - 1].timestamp;
|
|
4859
|
+
const signalTime = signal.pendingAt;
|
|
4860
|
+
const maxTimeToWait = signal.minuteEstimatedTime * 60 * 1000;
|
|
4861
|
+
const elapsedTime = closeTimestamp - signalTime;
|
|
4862
|
+
// Check if we actually reached time expiration or just ran out of candles
|
|
4863
|
+
if (elapsedTime < maxTimeToWait) {
|
|
4864
|
+
// EDGE CASE: backtest() called with insufficient candle data
|
|
4865
|
+
const bufferCandlesCount = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT - 1;
|
|
4866
|
+
const requiredCandlesCount = signal.minuteEstimatedTime + bufferCandlesCount + 1;
|
|
4867
|
+
throw new Error(str.newline(`ClientStrategy backtest: Insufficient candle data for pending signal. ` +
|
|
4868
|
+
`Signal opened at ${new Date(signal.pendingAt).toISOString()}, ` +
|
|
4869
|
+
`last candle at ${new Date(closeTimestamp).toISOString()}. ` +
|
|
4870
|
+
`Elapsed: ${Math.floor(elapsedTime / 60000)}min of ${signal.minuteEstimatedTime}min required. ` +
|
|
4871
|
+
`Provided ${candles.length} candles, but need at least ${requiredCandlesCount} candles. ` +
|
|
4872
|
+
`\nBreakdown: ${signal.minuteEstimatedTime} candles for signal lifetime + ${bufferCandlesCount} buffer candles. ` +
|
|
4873
|
+
`\nBuffer explanation: VWAP calculation requires ${GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT} candles, ` +
|
|
4874
|
+
`so first ${bufferCandlesCount} candles are skipped to ensure accurate price averaging. ` +
|
|
4875
|
+
`Provide complete candle range: [pendingAt - ${bufferCandlesCount}min, pendingAt + ${signal.minuteEstimatedTime}min].`));
|
|
4876
|
+
}
|
|
4877
|
+
// Time actually expired - close with time_expired
|
|
4817
4878
|
return await CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN(this, signal, lastPrice, "time_expired", closeTimestamp);
|
|
4818
4879
|
}
|
|
4819
4880
|
/**
|
|
@@ -5602,6 +5663,9 @@ class ClientStrategy {
|
|
|
5602
5663
|
const RISK_METHOD_NAME_GET_DATA = "RiskUtils.getData";
|
|
5603
5664
|
const RISK_METHOD_NAME_GET_REPORT = "RiskUtils.getReport";
|
|
5604
5665
|
const RISK_METHOD_NAME_DUMP = "RiskUtils.dump";
|
|
5666
|
+
const RISK_METHOD_NAME_CHECK_SIGNAL = "MergeRisk.checkSignal";
|
|
5667
|
+
const RISK_METHOD_NAME_ADD_SIGNAL = "MergeRisk.addSignal";
|
|
5668
|
+
const RISK_METHOD_NAME_REMOVE_SIGNAL = "MergeRisk.removeSignal";
|
|
5605
5669
|
/**
|
|
5606
5670
|
* Composite risk management class that combines multiple risk profiles.
|
|
5607
5671
|
*
|
|
@@ -5657,7 +5721,7 @@ class MergeRisk {
|
|
|
5657
5721
|
* @returns Promise resolving to true if all risks approve, false if any risk rejects
|
|
5658
5722
|
*/
|
|
5659
5723
|
async checkSignal(params) {
|
|
5660
|
-
backtest$1.loggerService.info(
|
|
5724
|
+
backtest$1.loggerService.info(RISK_METHOD_NAME_CHECK_SIGNAL, {
|
|
5661
5725
|
params,
|
|
5662
5726
|
});
|
|
5663
5727
|
for (const [riskName, risk] of Object.entries(this._riskMap)) {
|
|
@@ -5681,7 +5745,7 @@ class MergeRisk {
|
|
|
5681
5745
|
* @returns Promise that resolves when all risks have registered the signal
|
|
5682
5746
|
*/
|
|
5683
5747
|
async addSignal(symbol, context, positionData) {
|
|
5684
|
-
backtest$1.loggerService.info(
|
|
5748
|
+
backtest$1.loggerService.info(RISK_METHOD_NAME_ADD_SIGNAL, {
|
|
5685
5749
|
symbol,
|
|
5686
5750
|
context,
|
|
5687
5751
|
});
|
|
@@ -5698,7 +5762,7 @@ class MergeRisk {
|
|
|
5698
5762
|
* @returns Promise that resolves when all risks have removed the signal
|
|
5699
5763
|
*/
|
|
5700
5764
|
async removeSignal(symbol, context) {
|
|
5701
|
-
backtest$1.loggerService.info(
|
|
5765
|
+
backtest$1.loggerService.info(RISK_METHOD_NAME_REMOVE_SIGNAL, {
|
|
5702
5766
|
symbol,
|
|
5703
5767
|
context,
|
|
5704
5768
|
});
|
|
@@ -5981,15 +6045,36 @@ const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
5981
6045
|
return parts.join(":");
|
|
5982
6046
|
};
|
|
5983
6047
|
/**
|
|
5984
|
-
* Creates a callback function for emitting ping events to pingSubject.
|
|
6048
|
+
* Creates a callback function for emitting schedule ping events to pingSubject.
|
|
5985
6049
|
*
|
|
5986
6050
|
* Called by ClientStrategy when a scheduled signal is being monitored every minute.
|
|
5987
6051
|
* Emits PingContract event to all subscribers and calls ActionCoreService.
|
|
5988
6052
|
*
|
|
5989
6053
|
* @param self - Reference to StrategyConnectionService instance
|
|
5990
|
-
* @returns Callback function for ping events
|
|
6054
|
+
* @returns Callback function for schedule ping events
|
|
6055
|
+
*/
|
|
6056
|
+
const CREATE_COMMIT_SCHEDULE_PING_FN = (self) => async (symbol, strategyName, exchangeName, data, backtest, timestamp) => {
|
|
6057
|
+
const event = {
|
|
6058
|
+
symbol,
|
|
6059
|
+
strategyName,
|
|
6060
|
+
exchangeName,
|
|
6061
|
+
data,
|
|
6062
|
+
backtest,
|
|
6063
|
+
timestamp,
|
|
6064
|
+
};
|
|
6065
|
+
await schedulePingSubject.next(event);
|
|
6066
|
+
await self.actionCoreService.pingScheduled(backtest, event, { strategyName, exchangeName, frameName: data.frameName });
|
|
6067
|
+
};
|
|
6068
|
+
/**
|
|
6069
|
+
* Creates a callback function for emitting active ping events.
|
|
6070
|
+
*
|
|
6071
|
+
* Called by ClientStrategy when an active pending signal is being monitored every minute.
|
|
6072
|
+
* Placeholder for future activePingSubject implementation.
|
|
6073
|
+
*
|
|
6074
|
+
* @param self - Reference to StrategyConnectionService instance
|
|
6075
|
+
* @returns Callback function for active ping events
|
|
5991
6076
|
*/
|
|
5992
|
-
const
|
|
6077
|
+
const CREATE_COMMIT_ACTIVE_PING_FN = (self) => async (symbol, strategyName, exchangeName, data, backtest, timestamp) => {
|
|
5993
6078
|
const event = {
|
|
5994
6079
|
symbol,
|
|
5995
6080
|
strategyName,
|
|
@@ -5998,8 +6083,8 @@ const CREATE_COMMIT_PING_FN = (self) => async (symbol, strategyName, exchangeNam
|
|
|
5998
6083
|
backtest,
|
|
5999
6084
|
timestamp,
|
|
6000
6085
|
};
|
|
6001
|
-
await
|
|
6002
|
-
await self.actionCoreService.
|
|
6086
|
+
await activePingSubject.next(event);
|
|
6087
|
+
await self.actionCoreService.pingActive(backtest, event, { strategyName, exchangeName, frameName: data.frameName });
|
|
6003
6088
|
};
|
|
6004
6089
|
/**
|
|
6005
6090
|
* Creates a callback function for emitting init events.
|
|
@@ -6096,7 +6181,8 @@ class StrategyConnectionService {
|
|
|
6096
6181
|
getSignal,
|
|
6097
6182
|
callbacks,
|
|
6098
6183
|
onInit: CREATE_COMMIT_INIT_FN(this),
|
|
6099
|
-
|
|
6184
|
+
onSchedulePing: CREATE_COMMIT_SCHEDULE_PING_FN(this),
|
|
6185
|
+
onActivePing: CREATE_COMMIT_ACTIVE_PING_FN(this),
|
|
6100
6186
|
onDispose: CREATE_COMMIT_DISPOSE_FN(this),
|
|
6101
6187
|
});
|
|
6102
6188
|
});
|
|
@@ -7489,8 +7575,8 @@ const CALL_SIGNAL_BACKTEST_CALLBACK_FN = trycatch(async (self, event, strategyNa
|
|
|
7489
7575
|
});
|
|
7490
7576
|
/** Wrapper to call breakeven callback with error handling */
|
|
7491
7577
|
const CALL_BREAKEVEN_CALLBACK_FN = trycatch(async (self, event, strategyName, frameName, backtest) => {
|
|
7492
|
-
if (self.params.callbacks?.
|
|
7493
|
-
await self.params.callbacks.
|
|
7578
|
+
if (self.params.callbacks?.onBreakevenAvailable) {
|
|
7579
|
+
await self.params.callbacks.onBreakevenAvailable(event, self.params.actionName, strategyName, frameName, backtest);
|
|
7494
7580
|
}
|
|
7495
7581
|
}, {
|
|
7496
7582
|
fallback: (error) => {
|
|
@@ -7506,8 +7592,8 @@ const CALL_BREAKEVEN_CALLBACK_FN = trycatch(async (self, event, strategyName, fr
|
|
|
7506
7592
|
});
|
|
7507
7593
|
/** Wrapper to call partialProfit callback with error handling */
|
|
7508
7594
|
const CALL_PARTIAL_PROFIT_CALLBACK_FN = trycatch(async (self, event, strategyName, frameName, backtest) => {
|
|
7509
|
-
if (self.params.callbacks?.
|
|
7510
|
-
await self.params.callbacks.
|
|
7595
|
+
if (self.params.callbacks?.onPartialProfitAvailable) {
|
|
7596
|
+
await self.params.callbacks.onPartialProfitAvailable(event, self.params.actionName, strategyName, frameName, backtest);
|
|
7511
7597
|
}
|
|
7512
7598
|
}, {
|
|
7513
7599
|
fallback: (error) => {
|
|
@@ -7523,8 +7609,8 @@ const CALL_PARTIAL_PROFIT_CALLBACK_FN = trycatch(async (self, event, strategyNam
|
|
|
7523
7609
|
});
|
|
7524
7610
|
/** Wrapper to call partialLoss callback with error handling */
|
|
7525
7611
|
const CALL_PARTIAL_LOSS_CALLBACK_FN = trycatch(async (self, event, strategyName, frameName, backtest) => {
|
|
7526
|
-
if (self.params.callbacks?.
|
|
7527
|
-
await self.params.callbacks.
|
|
7612
|
+
if (self.params.callbacks?.onPartialLossAvailable) {
|
|
7613
|
+
await self.params.callbacks.onPartialLossAvailable(event, self.params.actionName, strategyName, frameName, backtest);
|
|
7528
7614
|
}
|
|
7529
7615
|
}, {
|
|
7530
7616
|
fallback: (error) => {
|
|
@@ -7538,14 +7624,31 @@ const CALL_PARTIAL_LOSS_CALLBACK_FN = trycatch(async (self, event, strategyName,
|
|
|
7538
7624
|
errorEmitter.next(error);
|
|
7539
7625
|
},
|
|
7540
7626
|
});
|
|
7541
|
-
/** Wrapper to call ping callback with error handling */
|
|
7542
|
-
const
|
|
7543
|
-
if (self.params.callbacks?.
|
|
7544
|
-
await self.params.callbacks.
|
|
7627
|
+
/** Wrapper to call scheduled ping callback with error handling */
|
|
7628
|
+
const CALL_PING_SCHEDULED_CALLBACK_FN = trycatch(async (self, event, strategyName, frameName, backtest) => {
|
|
7629
|
+
if (self.params.callbacks?.onPingScheduled) {
|
|
7630
|
+
await self.params.callbacks.onPingScheduled(event, self.params.actionName, strategyName, frameName, backtest);
|
|
7631
|
+
}
|
|
7632
|
+
}, {
|
|
7633
|
+
fallback: (error) => {
|
|
7634
|
+
const message = "ClientAction CALL_PING_SCHEDULED_CALLBACK_FN thrown";
|
|
7635
|
+
const payload = {
|
|
7636
|
+
error: errorData(error),
|
|
7637
|
+
message: getErrorMessage(error),
|
|
7638
|
+
};
|
|
7639
|
+
backtest$1.loggerService.warn(message, payload);
|
|
7640
|
+
console.warn(message, payload);
|
|
7641
|
+
errorEmitter.next(error);
|
|
7642
|
+
},
|
|
7643
|
+
});
|
|
7644
|
+
/** Wrapper to call active ping callback with error handling */
|
|
7645
|
+
const CALL_PING_ACTIVE_CALLBACK_FN = trycatch(async (self, event, strategyName, frameName, backtest) => {
|
|
7646
|
+
if (self.params.callbacks?.onPingActive) {
|
|
7647
|
+
await self.params.callbacks.onPingActive(event, self.params.actionName, strategyName, frameName, backtest);
|
|
7545
7648
|
}
|
|
7546
7649
|
}, {
|
|
7547
7650
|
fallback: (error) => {
|
|
7548
|
-
const message = "ClientAction
|
|
7651
|
+
const message = "ClientAction CALL_PING_ACTIVE_CALLBACK_FN thrown";
|
|
7549
7652
|
const payload = {
|
|
7550
7653
|
error: errorData(error),
|
|
7551
7654
|
message: getErrorMessage(error),
|
|
@@ -7620,6 +7723,7 @@ const CREATE_HANDLER_FN = (self) => {
|
|
|
7620
7723
|
self.params.strategyName,
|
|
7621
7724
|
self.params.frameName,
|
|
7622
7725
|
self.params.actionName,
|
|
7726
|
+
self.params.backtest,
|
|
7623
7727
|
]);
|
|
7624
7728
|
}
|
|
7625
7729
|
return self.params.handler;
|
|
@@ -7811,8 +7915,8 @@ class ClientAction {
|
|
|
7811
7915
|
/**
|
|
7812
7916
|
* Handles breakeven events when stop-loss is moved to entry price.
|
|
7813
7917
|
*/
|
|
7814
|
-
async
|
|
7815
|
-
this.params.logger.debug("ClientAction
|
|
7918
|
+
async breakevenAvailable(event) {
|
|
7919
|
+
this.params.logger.debug("ClientAction breakevenAvailable", {
|
|
7816
7920
|
actionName: this.params.actionName,
|
|
7817
7921
|
strategyName: this.params.strategyName,
|
|
7818
7922
|
frameName: this.params.frameName,
|
|
@@ -7821,8 +7925,8 @@ class ClientAction {
|
|
|
7821
7925
|
await this.waitForInit();
|
|
7822
7926
|
}
|
|
7823
7927
|
// Call handler method if defined
|
|
7824
|
-
if (this._handlerInstance?.
|
|
7825
|
-
await this._handlerInstance.
|
|
7928
|
+
if (this._handlerInstance?.breakevenAvailable) {
|
|
7929
|
+
await this._handlerInstance.breakevenAvailable(event);
|
|
7826
7930
|
}
|
|
7827
7931
|
// Call callback if defined
|
|
7828
7932
|
await CALL_BREAKEVEN_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
|
|
@@ -7831,8 +7935,8 @@ class ClientAction {
|
|
|
7831
7935
|
/**
|
|
7832
7936
|
* Handles partial profit level events (10%, 20%, 30%, etc).
|
|
7833
7937
|
*/
|
|
7834
|
-
async
|
|
7835
|
-
this.params.logger.debug("ClientAction
|
|
7938
|
+
async partialProfitAvailable(event) {
|
|
7939
|
+
this.params.logger.debug("ClientAction partialProfitAvailable", {
|
|
7836
7940
|
actionName: this.params.actionName,
|
|
7837
7941
|
strategyName: this.params.strategyName,
|
|
7838
7942
|
frameName: this.params.frameName,
|
|
@@ -7841,8 +7945,8 @@ class ClientAction {
|
|
|
7841
7945
|
await this.waitForInit();
|
|
7842
7946
|
}
|
|
7843
7947
|
// Call handler method if defined
|
|
7844
|
-
if (this._handlerInstance?.
|
|
7845
|
-
await this._handlerInstance.
|
|
7948
|
+
if (this._handlerInstance?.partialProfitAvailable) {
|
|
7949
|
+
await this._handlerInstance.partialProfitAvailable(event);
|
|
7846
7950
|
}
|
|
7847
7951
|
// Call callback if defined
|
|
7848
7952
|
await CALL_PARTIAL_PROFIT_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
|
|
@@ -7851,8 +7955,8 @@ class ClientAction {
|
|
|
7851
7955
|
/**
|
|
7852
7956
|
* Handles partial loss level events (-10%, -20%, -30%, etc).
|
|
7853
7957
|
*/
|
|
7854
|
-
async
|
|
7855
|
-
this.params.logger.debug("ClientAction
|
|
7958
|
+
async partialLossAvailable(event) {
|
|
7959
|
+
this.params.logger.debug("ClientAction partialLossAvailable", {
|
|
7856
7960
|
actionName: this.params.actionName,
|
|
7857
7961
|
strategyName: this.params.strategyName,
|
|
7858
7962
|
frameName: this.params.frameName,
|
|
@@ -7861,18 +7965,38 @@ class ClientAction {
|
|
|
7861
7965
|
await this.waitForInit();
|
|
7862
7966
|
}
|
|
7863
7967
|
// Call handler method if defined
|
|
7864
|
-
if (this._handlerInstance?.
|
|
7865
|
-
await this._handlerInstance.
|
|
7968
|
+
if (this._handlerInstance?.partialLossAvailable) {
|
|
7969
|
+
await this._handlerInstance.partialLossAvailable(event);
|
|
7866
7970
|
}
|
|
7867
7971
|
// Call callback if defined
|
|
7868
7972
|
await CALL_PARTIAL_LOSS_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
|
|
7869
7973
|
}
|
|
7870
7974
|
;
|
|
7871
7975
|
/**
|
|
7872
|
-
* Handles ping events during scheduled signal monitoring.
|
|
7976
|
+
* Handles scheduled ping events during scheduled signal monitoring.
|
|
7977
|
+
*/
|
|
7978
|
+
async pingScheduled(event) {
|
|
7979
|
+
this.params.logger.debug("ClientAction pingScheduled", {
|
|
7980
|
+
actionName: this.params.actionName,
|
|
7981
|
+
strategyName: this.params.strategyName,
|
|
7982
|
+
frameName: this.params.frameName,
|
|
7983
|
+
});
|
|
7984
|
+
if (!this._handlerInstance) {
|
|
7985
|
+
await this.waitForInit();
|
|
7986
|
+
}
|
|
7987
|
+
// Call handler method if defined
|
|
7988
|
+
if (this._handlerInstance?.pingScheduled) {
|
|
7989
|
+
await this._handlerInstance.pingScheduled(event);
|
|
7990
|
+
}
|
|
7991
|
+
// Call callback if defined
|
|
7992
|
+
await CALL_PING_SCHEDULED_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
|
|
7993
|
+
}
|
|
7994
|
+
;
|
|
7995
|
+
/**
|
|
7996
|
+
* Handles active ping events during active pending signal monitoring.
|
|
7873
7997
|
*/
|
|
7874
|
-
async
|
|
7875
|
-
this.params.logger.debug("ClientAction
|
|
7998
|
+
async pingActive(event) {
|
|
7999
|
+
this.params.logger.debug("ClientAction pingActive", {
|
|
7876
8000
|
actionName: this.params.actionName,
|
|
7877
8001
|
strategyName: this.params.strategyName,
|
|
7878
8002
|
frameName: this.params.frameName,
|
|
@@ -7881,11 +8005,11 @@ class ClientAction {
|
|
|
7881
8005
|
await this.waitForInit();
|
|
7882
8006
|
}
|
|
7883
8007
|
// Call handler method if defined
|
|
7884
|
-
if (this._handlerInstance?.
|
|
7885
|
-
await this._handlerInstance.
|
|
8008
|
+
if (this._handlerInstance?.pingActive) {
|
|
8009
|
+
await this._handlerInstance.pingActive(event);
|
|
7886
8010
|
}
|
|
7887
8011
|
// Call callback if defined
|
|
7888
|
-
await
|
|
8012
|
+
await CALL_PING_ACTIVE_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
|
|
7889
8013
|
}
|
|
7890
8014
|
;
|
|
7891
8015
|
/**
|
|
@@ -8053,13 +8177,13 @@ class ActionConnectionService {
|
|
|
8053
8177
|
* @param backtest - Whether running in backtest mode
|
|
8054
8178
|
* @param context - Execution context with action name, strategy name, exchange name, frame name
|
|
8055
8179
|
*/
|
|
8056
|
-
this.
|
|
8057
|
-
this.loggerService.log("actionConnectionService
|
|
8180
|
+
this.breakevenAvailable = async (event, backtest, context) => {
|
|
8181
|
+
this.loggerService.log("actionConnectionService breakevenAvailable", {
|
|
8058
8182
|
backtest,
|
|
8059
8183
|
context,
|
|
8060
8184
|
});
|
|
8061
8185
|
const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8062
|
-
await action.
|
|
8186
|
+
await action.breakevenAvailable(event);
|
|
8063
8187
|
};
|
|
8064
8188
|
/**
|
|
8065
8189
|
* Routes partialProfit event to appropriate ClientAction instance.
|
|
@@ -8068,13 +8192,13 @@ class ActionConnectionService {
|
|
|
8068
8192
|
* @param backtest - Whether running in backtest mode
|
|
8069
8193
|
* @param context - Execution context with action name, strategy name, exchange name, frame name
|
|
8070
8194
|
*/
|
|
8071
|
-
this.
|
|
8072
|
-
this.loggerService.log("actionConnectionService
|
|
8195
|
+
this.partialProfitAvailable = async (event, backtest, context) => {
|
|
8196
|
+
this.loggerService.log("actionConnectionService partialProfitAvailable", {
|
|
8073
8197
|
backtest,
|
|
8074
8198
|
context,
|
|
8075
8199
|
});
|
|
8076
8200
|
const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8077
|
-
await action.
|
|
8201
|
+
await action.partialProfitAvailable(event);
|
|
8078
8202
|
};
|
|
8079
8203
|
/**
|
|
8080
8204
|
* Routes partialLoss event to appropriate ClientAction instance.
|
|
@@ -8083,28 +8207,43 @@ class ActionConnectionService {
|
|
|
8083
8207
|
* @param backtest - Whether running in backtest mode
|
|
8084
8208
|
* @param context - Execution context with action name, strategy name, exchange name, frame name
|
|
8085
8209
|
*/
|
|
8086
|
-
this.
|
|
8087
|
-
this.loggerService.log("actionConnectionService
|
|
8210
|
+
this.partialLossAvailable = async (event, backtest, context) => {
|
|
8211
|
+
this.loggerService.log("actionConnectionService partialLossAvailable", {
|
|
8212
|
+
backtest,
|
|
8213
|
+
context,
|
|
8214
|
+
});
|
|
8215
|
+
const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8216
|
+
await action.partialLossAvailable(event);
|
|
8217
|
+
};
|
|
8218
|
+
/**
|
|
8219
|
+
* Routes scheduled ping event to appropriate ClientAction instance.
|
|
8220
|
+
*
|
|
8221
|
+
* @param event - Scheduled ping event data
|
|
8222
|
+
* @param backtest - Whether running in backtest mode
|
|
8223
|
+
* @param context - Execution context with action name, strategy name, exchange name, frame name
|
|
8224
|
+
*/
|
|
8225
|
+
this.pingScheduled = async (event, backtest, context) => {
|
|
8226
|
+
this.loggerService.log("actionConnectionService pingScheduled", {
|
|
8088
8227
|
backtest,
|
|
8089
8228
|
context,
|
|
8090
8229
|
});
|
|
8091
8230
|
const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8092
|
-
await action.
|
|
8231
|
+
await action.pingScheduled(event);
|
|
8093
8232
|
};
|
|
8094
8233
|
/**
|
|
8095
|
-
* Routes ping event to appropriate ClientAction instance.
|
|
8234
|
+
* Routes active ping event to appropriate ClientAction instance.
|
|
8096
8235
|
*
|
|
8097
|
-
* @param event -
|
|
8236
|
+
* @param event - Active ping event data
|
|
8098
8237
|
* @param backtest - Whether running in backtest mode
|
|
8099
8238
|
* @param context - Execution context with action name, strategy name, exchange name, frame name
|
|
8100
8239
|
*/
|
|
8101
|
-
this.
|
|
8102
|
-
this.loggerService.log("actionConnectionService
|
|
8240
|
+
this.pingActive = async (event, backtest, context) => {
|
|
8241
|
+
this.loggerService.log("actionConnectionService pingActive", {
|
|
8103
8242
|
backtest,
|
|
8104
8243
|
context,
|
|
8105
8244
|
});
|
|
8106
8245
|
const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8107
|
-
await action.
|
|
8246
|
+
await action.pingActive(event);
|
|
8108
8247
|
};
|
|
8109
8248
|
/**
|
|
8110
8249
|
* Routes riskRejection event to appropriate ClientAction instance.
|
|
@@ -9168,81 +9307,102 @@ class ActionCoreService {
|
|
|
9168
9307
|
* Routes breakeven event to all registered actions for the strategy.
|
|
9169
9308
|
*
|
|
9170
9309
|
* Retrieves action list from strategy schema (IStrategySchema.actions)
|
|
9171
|
-
* and invokes the
|
|
9310
|
+
* and invokes the breakevenAvailable handler on each ClientAction instance sequentially.
|
|
9172
9311
|
*
|
|
9173
9312
|
* @param backtest - Whether running in backtest mode (true) or live mode (false)
|
|
9174
9313
|
* @param event - Breakeven milestone data (stop-loss moved to entry price)
|
|
9175
9314
|
* @param context - Strategy execution context with strategyName, exchangeName, frameName
|
|
9176
9315
|
*/
|
|
9177
|
-
this.
|
|
9178
|
-
this.loggerService.log("actionCoreService
|
|
9316
|
+
this.breakevenAvailable = async (backtest, event, context) => {
|
|
9317
|
+
this.loggerService.log("actionCoreService breakevenAvailable", {
|
|
9179
9318
|
context,
|
|
9180
9319
|
});
|
|
9181
9320
|
await this.validate(context);
|
|
9182
9321
|
const { actions = [] } = this.strategySchemaService.get(context.strategyName);
|
|
9183
9322
|
for (const actionName of actions) {
|
|
9184
|
-
await this.actionConnectionService.
|
|
9323
|
+
await this.actionConnectionService.breakevenAvailable(event, backtest, { actionName, ...context });
|
|
9185
9324
|
}
|
|
9186
9325
|
};
|
|
9187
9326
|
/**
|
|
9188
9327
|
* Routes partial profit event to all registered actions for the strategy.
|
|
9189
9328
|
*
|
|
9190
9329
|
* Retrieves action list from strategy schema (IStrategySchema.actions)
|
|
9191
|
-
* and invokes the
|
|
9330
|
+
* and invokes the partialProfitAvailable handler on each ClientAction instance sequentially.
|
|
9192
9331
|
*
|
|
9193
9332
|
* @param backtest - Whether running in backtest mode (true) or live mode (false)
|
|
9194
9333
|
* @param event - Profit milestone data with level (10%, 20%, etc.) and price
|
|
9195
9334
|
* @param context - Strategy execution context with strategyName, exchangeName, frameName
|
|
9196
9335
|
*/
|
|
9197
|
-
this.
|
|
9198
|
-
this.loggerService.log("actionCoreService
|
|
9336
|
+
this.partialProfitAvailable = async (backtest, event, context) => {
|
|
9337
|
+
this.loggerService.log("actionCoreService partialProfitAvailable", {
|
|
9199
9338
|
context,
|
|
9200
9339
|
});
|
|
9201
9340
|
await this.validate(context);
|
|
9202
9341
|
const { actions = [] } = this.strategySchemaService.get(context.strategyName);
|
|
9203
9342
|
for (const actionName of actions) {
|
|
9204
|
-
await this.actionConnectionService.
|
|
9343
|
+
await this.actionConnectionService.partialProfitAvailable(event, backtest, { actionName, ...context });
|
|
9205
9344
|
}
|
|
9206
9345
|
};
|
|
9207
9346
|
/**
|
|
9208
9347
|
* Routes partial loss event to all registered actions for the strategy.
|
|
9209
9348
|
*
|
|
9210
9349
|
* Retrieves action list from strategy schema (IStrategySchema.actions)
|
|
9211
|
-
* and invokes the
|
|
9350
|
+
* and invokes the partialLossAvailable handler on each ClientAction instance sequentially.
|
|
9212
9351
|
*
|
|
9213
9352
|
* @param backtest - Whether running in backtest mode (true) or live mode (false)
|
|
9214
9353
|
* @param event - Loss milestone data with level (-10%, -20%, etc.) and price
|
|
9215
9354
|
* @param context - Strategy execution context with strategyName, exchangeName, frameName
|
|
9216
9355
|
*/
|
|
9217
|
-
this.
|
|
9218
|
-
this.loggerService.log("actionCoreService
|
|
9356
|
+
this.partialLossAvailable = async (backtest, event, context) => {
|
|
9357
|
+
this.loggerService.log("actionCoreService partialLossAvailable", {
|
|
9219
9358
|
context,
|
|
9220
9359
|
});
|
|
9221
9360
|
await this.validate(context);
|
|
9222
9361
|
const { actions = [] } = this.strategySchemaService.get(context.strategyName);
|
|
9223
9362
|
for (const actionName of actions) {
|
|
9224
|
-
await this.actionConnectionService.
|
|
9363
|
+
await this.actionConnectionService.partialLossAvailable(event, backtest, { actionName, ...context });
|
|
9225
9364
|
}
|
|
9226
9365
|
};
|
|
9227
9366
|
/**
|
|
9228
|
-
* Routes ping event to all registered actions for the strategy.
|
|
9367
|
+
* Routes scheduled ping event to all registered actions for the strategy.
|
|
9229
9368
|
*
|
|
9230
9369
|
* Retrieves action list from strategy schema (IStrategySchema.actions)
|
|
9231
|
-
* and invokes the
|
|
9370
|
+
* and invokes the pingScheduled handler on each ClientAction instance sequentially.
|
|
9232
9371
|
* Called every minute during scheduled signal monitoring.
|
|
9233
9372
|
*
|
|
9234
9373
|
* @param backtest - Whether running in backtest mode (true) or live mode (false)
|
|
9235
9374
|
* @param event - Scheduled signal monitoring data
|
|
9236
9375
|
* @param context - Strategy execution context with strategyName, exchangeName, frameName
|
|
9237
9376
|
*/
|
|
9238
|
-
this.
|
|
9239
|
-
this.loggerService.log("actionCoreService
|
|
9377
|
+
this.pingScheduled = async (backtest, event, context) => {
|
|
9378
|
+
this.loggerService.log("actionCoreService pingScheduled", {
|
|
9379
|
+
context,
|
|
9380
|
+
});
|
|
9381
|
+
await this.validate(context);
|
|
9382
|
+
const { actions = [] } = this.strategySchemaService.get(context.strategyName);
|
|
9383
|
+
for (const actionName of actions) {
|
|
9384
|
+
await this.actionConnectionService.pingScheduled(event, backtest, { actionName, ...context });
|
|
9385
|
+
}
|
|
9386
|
+
};
|
|
9387
|
+
/**
|
|
9388
|
+
* Routes active ping event to all registered actions for the strategy.
|
|
9389
|
+
*
|
|
9390
|
+
* Retrieves action list from strategy schema (IStrategySchema.actions)
|
|
9391
|
+
* and invokes the pingActive handler on each ClientAction instance sequentially.
|
|
9392
|
+
* Called every minute during active pending signal monitoring.
|
|
9393
|
+
*
|
|
9394
|
+
* @param backtest - Whether running in backtest mode (true) or live mode (false)
|
|
9395
|
+
* @param event - Active pending signal monitoring data
|
|
9396
|
+
* @param context - Strategy execution context with strategyName, exchangeName, frameName
|
|
9397
|
+
*/
|
|
9398
|
+
this.pingActive = async (backtest, event, context) => {
|
|
9399
|
+
this.loggerService.log("actionCoreService pingActive", {
|
|
9240
9400
|
context,
|
|
9241
9401
|
});
|
|
9242
9402
|
await this.validate(context);
|
|
9243
9403
|
const { actions = [] } = this.strategySchemaService.get(context.strategyName);
|
|
9244
9404
|
for (const actionName of actions) {
|
|
9245
|
-
await this.actionConnectionService.
|
|
9405
|
+
await this.actionConnectionService.pingActive(event, backtest, { actionName, ...context });
|
|
9246
9406
|
}
|
|
9247
9407
|
};
|
|
9248
9408
|
/**
|
|
@@ -10361,7 +10521,11 @@ class LiveLogicPrivateService {
|
|
|
10361
10521
|
await sleep(TICK_TTL);
|
|
10362
10522
|
continue;
|
|
10363
10523
|
}
|
|
10364
|
-
|
|
10524
|
+
if (result.action === "waiting") {
|
|
10525
|
+
await sleep(TICK_TTL);
|
|
10526
|
+
continue;
|
|
10527
|
+
}
|
|
10528
|
+
// Yield opened, closed, cancelled results
|
|
10365
10529
|
yield result;
|
|
10366
10530
|
// Check if strategy should stop after signal is closed
|
|
10367
10531
|
if (result.action === "closed") {
|
|
@@ -12296,6 +12460,12 @@ var _a$1, _b$1;
|
|
|
12296
12460
|
const MARKDOWN_METHOD_NAME_ENABLE = "MarkdownUtils.enable";
|
|
12297
12461
|
const MARKDOWN_METHOD_NAME_DISABLE = "MarkdownUtils.disable";
|
|
12298
12462
|
const MARKDOWN_METHOD_NAME_USE_ADAPTER = "MarkdownAdapter.useMarkdownAdapter";
|
|
12463
|
+
const MARKDOWN_METHOD_NAME_FILE_DUMP = "MarkdownFileAdapter.dump";
|
|
12464
|
+
const MARKDOWN_METHOD_NAME_FOLDER_DUMP = "MarkdownFolderAdapter.dump";
|
|
12465
|
+
const MARKDOWN_METHOD_NAME_WRITE_DATA = "MarkdownAdapter.writeData";
|
|
12466
|
+
const MARKDOWN_METHOD_NAME_USE_MD = "MarkdownAdapter.useMd";
|
|
12467
|
+
const MARKDOWN_METHOD_NAME_USE_JSONL = "MarkdownAdapter.useJsonl";
|
|
12468
|
+
const MARKDOWN_METHOD_NAME_USE_DUMMY = "MarkdownAdapter.useDummy";
|
|
12299
12469
|
const WAIT_FOR_INIT_SYMBOL$1 = Symbol("wait-for-init");
|
|
12300
12470
|
const WRITE_SAFE_SYMBOL$1 = Symbol("write-safe");
|
|
12301
12471
|
/**
|
|
@@ -12389,7 +12559,7 @@ class MarkdownFileBase {
|
|
|
12389
12559
|
* @throws Error if stream not initialized or write timeout exceeded
|
|
12390
12560
|
*/
|
|
12391
12561
|
async dump(data, options) {
|
|
12392
|
-
backtest$1.loggerService.debug(
|
|
12562
|
+
backtest$1.loggerService.debug(MARKDOWN_METHOD_NAME_FILE_DUMP, {
|
|
12393
12563
|
markdownName: this.markdownName,
|
|
12394
12564
|
options,
|
|
12395
12565
|
});
|
|
@@ -12471,7 +12641,7 @@ class MarkdownFolderBase {
|
|
|
12471
12641
|
* @throws Error if directory creation or file write fails
|
|
12472
12642
|
*/
|
|
12473
12643
|
async dump(content, options) {
|
|
12474
|
-
backtest$1.loggerService.debug(
|
|
12644
|
+
backtest$1.loggerService.debug(MARKDOWN_METHOD_NAME_FOLDER_DUMP, {
|
|
12475
12645
|
markdownName: this.markdownName,
|
|
12476
12646
|
options,
|
|
12477
12647
|
});
|
|
@@ -12711,7 +12881,7 @@ class MarkdownAdapter extends MarkdownUtils {
|
|
|
12711
12881
|
* @internal - Use service-specific dump methods instead (e.g., Backtest.dump)
|
|
12712
12882
|
*/
|
|
12713
12883
|
async writeData(markdownName, content, options) {
|
|
12714
|
-
backtest$1.loggerService.debug(
|
|
12884
|
+
backtest$1.loggerService.debug(MARKDOWN_METHOD_NAME_WRITE_DATA, {
|
|
12715
12885
|
markdownName,
|
|
12716
12886
|
options,
|
|
12717
12887
|
});
|
|
@@ -12726,7 +12896,7 @@ class MarkdownAdapter extends MarkdownUtils {
|
|
|
12726
12896
|
* Each dump creates a separate .md file.
|
|
12727
12897
|
*/
|
|
12728
12898
|
useMd() {
|
|
12729
|
-
backtest$1.loggerService.debug(
|
|
12899
|
+
backtest$1.loggerService.debug(MARKDOWN_METHOD_NAME_USE_MD);
|
|
12730
12900
|
this.useMarkdownAdapter(MarkdownFolderBase);
|
|
12731
12901
|
}
|
|
12732
12902
|
/**
|
|
@@ -12735,7 +12905,7 @@ class MarkdownAdapter extends MarkdownUtils {
|
|
|
12735
12905
|
* All dumps append to a single .jsonl file per markdown type.
|
|
12736
12906
|
*/
|
|
12737
12907
|
useJsonl() {
|
|
12738
|
-
backtest$1.loggerService.debug(
|
|
12908
|
+
backtest$1.loggerService.debug(MARKDOWN_METHOD_NAME_USE_JSONL);
|
|
12739
12909
|
this.useMarkdownAdapter(MarkdownFileBase);
|
|
12740
12910
|
}
|
|
12741
12911
|
/**
|
|
@@ -12743,7 +12913,7 @@ class MarkdownAdapter extends MarkdownUtils {
|
|
|
12743
12913
|
* All future markdown writes will be no-ops.
|
|
12744
12914
|
*/
|
|
12745
12915
|
useDummy() {
|
|
12746
|
-
backtest$1.loggerService.debug(
|
|
12916
|
+
backtest$1.loggerService.debug(MARKDOWN_METHOD_NAME_USE_DUMMY);
|
|
12747
12917
|
this.useMarkdownAdapter(MarkdownDummy);
|
|
12748
12918
|
}
|
|
12749
12919
|
}
|
|
@@ -13406,6 +13576,98 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
13406
13576
|
this._eventList.pop();
|
|
13407
13577
|
}
|
|
13408
13578
|
}
|
|
13579
|
+
/**
|
|
13580
|
+
* Adds a scheduled event to the storage.
|
|
13581
|
+
*
|
|
13582
|
+
* @param data - Scheduled tick result
|
|
13583
|
+
*/
|
|
13584
|
+
addScheduledEvent(data) {
|
|
13585
|
+
this._eventList.unshift({
|
|
13586
|
+
timestamp: data.signal.scheduledAt,
|
|
13587
|
+
action: "scheduled",
|
|
13588
|
+
symbol: data.signal.symbol,
|
|
13589
|
+
signalId: data.signal.id,
|
|
13590
|
+
position: data.signal.position,
|
|
13591
|
+
note: data.signal.note,
|
|
13592
|
+
currentPrice: data.currentPrice,
|
|
13593
|
+
priceOpen: data.signal.priceOpen,
|
|
13594
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
13595
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
13596
|
+
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
13597
|
+
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
13598
|
+
totalExecuted: data.signal.totalExecuted,
|
|
13599
|
+
});
|
|
13600
|
+
// Trim queue if exceeded MAX_EVENTS
|
|
13601
|
+
if (this._eventList.length > MAX_EVENTS$6) {
|
|
13602
|
+
this._eventList.pop();
|
|
13603
|
+
}
|
|
13604
|
+
}
|
|
13605
|
+
/**
|
|
13606
|
+
* Adds a waiting event to the storage.
|
|
13607
|
+
* Replaces the last waiting event with the same signalId.
|
|
13608
|
+
*
|
|
13609
|
+
* @param data - Waiting tick result
|
|
13610
|
+
*/
|
|
13611
|
+
addWaitingEvent(data) {
|
|
13612
|
+
const newEvent = {
|
|
13613
|
+
timestamp: Date.now(),
|
|
13614
|
+
action: "waiting",
|
|
13615
|
+
symbol: data.signal.symbol,
|
|
13616
|
+
signalId: data.signal.id,
|
|
13617
|
+
position: data.signal.position,
|
|
13618
|
+
note: data.signal.note,
|
|
13619
|
+
currentPrice: data.currentPrice,
|
|
13620
|
+
priceOpen: data.signal.priceOpen,
|
|
13621
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
13622
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
13623
|
+
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
13624
|
+
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
13625
|
+
totalExecuted: data.signal.totalExecuted,
|
|
13626
|
+
percentTp: data.percentTp,
|
|
13627
|
+
percentSl: data.percentSl,
|
|
13628
|
+
pnl: data.pnl.pnlPercentage,
|
|
13629
|
+
};
|
|
13630
|
+
// Find the last waiting event with the same signalId
|
|
13631
|
+
const lastWaitingIndex = this._eventList.findLastIndex((event) => event.action === "waiting" && event.signalId === data.signal.id);
|
|
13632
|
+
// Replace the last waiting event with the same signalId
|
|
13633
|
+
if (lastWaitingIndex !== -1) {
|
|
13634
|
+
this._eventList[lastWaitingIndex] = newEvent;
|
|
13635
|
+
return;
|
|
13636
|
+
}
|
|
13637
|
+
// If no previous waiting event found, add new event
|
|
13638
|
+
this._eventList.unshift(newEvent);
|
|
13639
|
+
// Trim queue if exceeded MAX_EVENTS
|
|
13640
|
+
if (this._eventList.length > MAX_EVENTS$6) {
|
|
13641
|
+
this._eventList.pop();
|
|
13642
|
+
}
|
|
13643
|
+
}
|
|
13644
|
+
/**
|
|
13645
|
+
* Adds a cancelled event to the storage.
|
|
13646
|
+
*
|
|
13647
|
+
* @param data - Cancelled tick result
|
|
13648
|
+
*/
|
|
13649
|
+
addCancelledEvent(data) {
|
|
13650
|
+
this._eventList.unshift({
|
|
13651
|
+
timestamp: data.closeTimestamp,
|
|
13652
|
+
action: "cancelled",
|
|
13653
|
+
symbol: data.signal.symbol,
|
|
13654
|
+
signalId: data.signal.id,
|
|
13655
|
+
position: data.signal.position,
|
|
13656
|
+
note: data.signal.note,
|
|
13657
|
+
currentPrice: data.currentPrice,
|
|
13658
|
+
priceOpen: data.signal.priceOpen,
|
|
13659
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
13660
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
13661
|
+
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
13662
|
+
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
13663
|
+
totalExecuted: data.signal.totalExecuted,
|
|
13664
|
+
cancelReason: data.reason,
|
|
13665
|
+
});
|
|
13666
|
+
// Trim queue if exceeded MAX_EVENTS
|
|
13667
|
+
if (this._eventList.length > MAX_EVENTS$6) {
|
|
13668
|
+
this._eventList.pop();
|
|
13669
|
+
}
|
|
13670
|
+
}
|
|
13409
13671
|
/**
|
|
13410
13672
|
* Calculates statistical data from live trading events (Controller).
|
|
13411
13673
|
* Returns null for any unsafe numeric values (NaN, Infinity, etc).
|
|
@@ -13663,6 +13925,12 @@ class LiveMarkdownService {
|
|
|
13663
13925
|
if (data.action === "idle") {
|
|
13664
13926
|
storage.addIdleEvent(data.currentPrice);
|
|
13665
13927
|
}
|
|
13928
|
+
else if (data.action === "scheduled") {
|
|
13929
|
+
storage.addScheduledEvent(data);
|
|
13930
|
+
}
|
|
13931
|
+
else if (data.action === "waiting") {
|
|
13932
|
+
storage.addWaitingEvent(data);
|
|
13933
|
+
}
|
|
13666
13934
|
else if (data.action === "opened") {
|
|
13667
13935
|
storage.addOpenedEvent(data);
|
|
13668
13936
|
}
|
|
@@ -13672,6 +13940,9 @@ class LiveMarkdownService {
|
|
|
13672
13940
|
else if (data.action === "closed") {
|
|
13673
13941
|
storage.addClosedEvent(data);
|
|
13674
13942
|
}
|
|
13943
|
+
else if (data.action === "cancelled") {
|
|
13944
|
+
storage.addCancelledEvent(data);
|
|
13945
|
+
}
|
|
13675
13946
|
};
|
|
13676
13947
|
/**
|
|
13677
13948
|
* Gets statistical data from all live trading events for a symbol-strategy pair.
|
|
@@ -18098,7 +18369,7 @@ const CREATE_COMMIT_PROFIT_FN = (self) => async (symbol, strategyName, exchangeN
|
|
|
18098
18369
|
timestamp,
|
|
18099
18370
|
};
|
|
18100
18371
|
await partialProfitSubject.next(event);
|
|
18101
|
-
await self.actionCoreService.
|
|
18372
|
+
await self.actionCoreService.partialProfitAvailable(backtest, event, { strategyName, exchangeName, frameName });
|
|
18102
18373
|
};
|
|
18103
18374
|
/**
|
|
18104
18375
|
* Creates a callback function for emitting loss events to partialLossSubject.
|
|
@@ -18122,7 +18393,7 @@ const CREATE_COMMIT_LOSS_FN = (self) => async (symbol, strategyName, exchangeNam
|
|
|
18122
18393
|
timestamp,
|
|
18123
18394
|
};
|
|
18124
18395
|
await partialLossSubject.next(event);
|
|
18125
|
-
await self.actionCoreService.
|
|
18396
|
+
await self.actionCoreService.partialLossAvailable(backtest, event, { strategyName, exchangeName, frameName });
|
|
18126
18397
|
};
|
|
18127
18398
|
/**
|
|
18128
18399
|
* Connection service for partial profit/loss tracking.
|
|
@@ -19271,7 +19542,7 @@ const CREATE_COMMIT_BREAKEVEN_FN = (self) => async (symbol, strategyName, exchan
|
|
|
19271
19542
|
timestamp,
|
|
19272
19543
|
};
|
|
19273
19544
|
await breakevenSubject.next(event);
|
|
19274
|
-
await self.actionCoreService.
|
|
19545
|
+
await self.actionCoreService.breakevenAvailable(backtest, event, { strategyName, exchangeName, frameName });
|
|
19275
19546
|
};
|
|
19276
19547
|
/**
|
|
19277
19548
|
* Connection service for breakeven tracking.
|
|
@@ -21356,6 +21627,43 @@ class LiveReportService {
|
|
|
21356
21627
|
if (data.action === "idle") {
|
|
21357
21628
|
await Report.writeData("live", baseEvent, searchOptions);
|
|
21358
21629
|
}
|
|
21630
|
+
else if (data.action === "scheduled") {
|
|
21631
|
+
await Report.writeData("live", {
|
|
21632
|
+
...baseEvent,
|
|
21633
|
+
signalId: data.signal?.id,
|
|
21634
|
+
position: data.signal?.position,
|
|
21635
|
+
note: data.signal?.note,
|
|
21636
|
+
priceOpen: data.signal?.priceOpen,
|
|
21637
|
+
priceTakeProfit: data.signal?.priceTakeProfit,
|
|
21638
|
+
priceStopLoss: data.signal?.priceStopLoss,
|
|
21639
|
+
originalPriceTakeProfit: data.signal?.originalPriceTakeProfit,
|
|
21640
|
+
originalPriceStopLoss: data.signal?.originalPriceStopLoss,
|
|
21641
|
+
totalExecuted: data.signal?.totalExecuted,
|
|
21642
|
+
scheduledAt: data.signal?.scheduledAt,
|
|
21643
|
+
minuteEstimatedTime: data.signal?.minuteEstimatedTime,
|
|
21644
|
+
}, { ...searchOptions, signalId: data.signal?.id });
|
|
21645
|
+
}
|
|
21646
|
+
else if (data.action === "waiting") {
|
|
21647
|
+
await Report.writeData("live", {
|
|
21648
|
+
...baseEvent,
|
|
21649
|
+
signalId: data.signal?.id,
|
|
21650
|
+
position: data.signal?.position,
|
|
21651
|
+
note: data.signal?.note,
|
|
21652
|
+
priceOpen: data.signal?.priceOpen,
|
|
21653
|
+
priceTakeProfit: data.signal?.priceTakeProfit,
|
|
21654
|
+
priceStopLoss: data.signal?.priceStopLoss,
|
|
21655
|
+
originalPriceTakeProfit: data.signal?.originalPriceTakeProfit,
|
|
21656
|
+
originalPriceStopLoss: data.signal?.originalPriceStopLoss,
|
|
21657
|
+
totalExecuted: data.signal?.totalExecuted,
|
|
21658
|
+
scheduledAt: data.signal?.scheduledAt,
|
|
21659
|
+
minuteEstimatedTime: data.signal?.minuteEstimatedTime,
|
|
21660
|
+
percentTp: data.percentTp,
|
|
21661
|
+
percentSl: data.percentSl,
|
|
21662
|
+
pnl: data.pnl.pnlPercentage,
|
|
21663
|
+
pnlPriceOpen: data.pnl.priceOpen,
|
|
21664
|
+
pnlPriceClose: data.pnl.priceClose,
|
|
21665
|
+
}, { ...searchOptions, signalId: data.signal?.id });
|
|
21666
|
+
}
|
|
21359
21667
|
else if (data.action === "opened") {
|
|
21360
21668
|
await Report.writeData("live", {
|
|
21361
21669
|
...baseEvent,
|
|
@@ -21422,6 +21730,24 @@ class LiveReportService {
|
|
|
21422
21730
|
closeTime: data.closeTimestamp,
|
|
21423
21731
|
}, { ...searchOptions, signalId: data.signal?.id });
|
|
21424
21732
|
}
|
|
21733
|
+
else if (data.action === "cancelled") {
|
|
21734
|
+
await Report.writeData("live", {
|
|
21735
|
+
...baseEvent,
|
|
21736
|
+
signalId: data.signal?.id,
|
|
21737
|
+
position: data.signal?.position,
|
|
21738
|
+
note: data.signal?.note,
|
|
21739
|
+
priceOpen: data.signal?.priceOpen,
|
|
21740
|
+
priceTakeProfit: data.signal?.priceTakeProfit,
|
|
21741
|
+
priceStopLoss: data.signal?.priceStopLoss,
|
|
21742
|
+
originalPriceTakeProfit: data.signal?.originalPriceTakeProfit,
|
|
21743
|
+
originalPriceStopLoss: data.signal?.originalPriceStopLoss,
|
|
21744
|
+
totalExecuted: data.signal?.totalExecuted,
|
|
21745
|
+
scheduledAt: data.signal?.scheduledAt,
|
|
21746
|
+
minuteEstimatedTime: data.signal?.minuteEstimatedTime,
|
|
21747
|
+
cancelReason: data.reason,
|
|
21748
|
+
closeTime: data.closeTimestamp,
|
|
21749
|
+
}, { ...searchOptions, signalId: data.signal?.id });
|
|
21750
|
+
}
|
|
21425
21751
|
};
|
|
21426
21752
|
/**
|
|
21427
21753
|
* Subscribes to live signal emitter to receive tick events.
|
|
@@ -22611,6 +22937,27 @@ const backtest = {
|
|
|
22611
22937
|
init();
|
|
22612
22938
|
var backtest$1 = backtest;
|
|
22613
22939
|
|
|
22940
|
+
const GET_TIMEFRAME_METHOD_NAME = "get.getBacktestTimeframe";
|
|
22941
|
+
/**
|
|
22942
|
+
* Retrieves current backtest timeframe for given symbol.
|
|
22943
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
22944
|
+
* @returns Promise resolving to array of Date objects representing tick timestamps
|
|
22945
|
+
* @throws Error if called outside of backtest execution context
|
|
22946
|
+
*/
|
|
22947
|
+
async function getBacktestTimeframe(symbol) {
|
|
22948
|
+
backtest$1.loggerService.info(GET_TIMEFRAME_METHOD_NAME, { symbol });
|
|
22949
|
+
if (!ExecutionContextService.hasContext()) {
|
|
22950
|
+
throw new Error("getBacktestTimeframe requires an execution context");
|
|
22951
|
+
}
|
|
22952
|
+
if (!MethodContextService.hasContext()) {
|
|
22953
|
+
throw new Error("getBacktestTimeframe requires a method context");
|
|
22954
|
+
}
|
|
22955
|
+
if (!backtest$1.executionContextService.context.backtest) {
|
|
22956
|
+
throw new Error("getBacktestTimeframe can only be used during backtest execution");
|
|
22957
|
+
}
|
|
22958
|
+
return await backtest$1.frameCoreService.getTimeframe(symbol, backtest$1.methodContextService.context.frameName);
|
|
22959
|
+
}
|
|
22960
|
+
|
|
22614
22961
|
const METHOD_NAME = "validate.validate";
|
|
22615
22962
|
/**
|
|
22616
22963
|
* Retrieves all registered exchanges as a map
|
|
@@ -22806,12 +23153,197 @@ async function validate(args = {}) {
|
|
|
22806
23153
|
return await validateInternal(args);
|
|
22807
23154
|
}
|
|
22808
23155
|
|
|
23156
|
+
const GET_STRATEGY_METHOD_NAME = "get.getStrategySchema";
|
|
23157
|
+
const GET_EXCHANGE_METHOD_NAME = "get.getExchangeSchema";
|
|
23158
|
+
const GET_FRAME_METHOD_NAME = "get.getFrameSchema";
|
|
23159
|
+
const GET_WALKER_METHOD_NAME = "get.getWalkerSchema";
|
|
23160
|
+
const GET_SIZING_METHOD_NAME = "get.getSizingSchema";
|
|
23161
|
+
const GET_RISK_METHOD_NAME = "get.getRiskSchema";
|
|
23162
|
+
const GET_OPTIMIZER_METHOD_NAME = "get.getOptimizerSchema";
|
|
23163
|
+
const GET_ACTION_METHOD_NAME = "get.getActionSchema";
|
|
23164
|
+
/**
|
|
23165
|
+
* Retrieves a registered strategy schema by name.
|
|
23166
|
+
*
|
|
23167
|
+
* @param strategyName - Unique strategy identifier
|
|
23168
|
+
* @returns The strategy schema configuration object
|
|
23169
|
+
* @throws Error if strategy is not registered
|
|
23170
|
+
*
|
|
23171
|
+
* @example
|
|
23172
|
+
* ```typescript
|
|
23173
|
+
* const strategy = getStrategy("my-strategy");
|
|
23174
|
+
* console.log(strategy.interval); // "5m"
|
|
23175
|
+
* console.log(strategy.getSignal); // async function
|
|
23176
|
+
* ```
|
|
23177
|
+
*/
|
|
23178
|
+
function getStrategySchema(strategyName) {
|
|
23179
|
+
backtest$1.loggerService.log(GET_STRATEGY_METHOD_NAME, {
|
|
23180
|
+
strategyName,
|
|
23181
|
+
});
|
|
23182
|
+
backtest$1.strategyValidationService.validate(strategyName, GET_STRATEGY_METHOD_NAME);
|
|
23183
|
+
return backtest$1.strategySchemaService.get(strategyName);
|
|
23184
|
+
}
|
|
23185
|
+
/**
|
|
23186
|
+
* Retrieves a registered exchange schema by name.
|
|
23187
|
+
*
|
|
23188
|
+
* @param exchangeName - Unique exchange identifier
|
|
23189
|
+
* @returns The exchange schema configuration object
|
|
23190
|
+
* @throws Error if exchange is not registered
|
|
23191
|
+
*
|
|
23192
|
+
* @example
|
|
23193
|
+
* ```typescript
|
|
23194
|
+
* const exchange = getExchange("binance");
|
|
23195
|
+
* console.log(exchange.getCandles); // async function
|
|
23196
|
+
* console.log(exchange.formatPrice); // async function
|
|
23197
|
+
* ```
|
|
23198
|
+
*/
|
|
23199
|
+
function getExchangeSchema(exchangeName) {
|
|
23200
|
+
backtest$1.loggerService.log(GET_EXCHANGE_METHOD_NAME, {
|
|
23201
|
+
exchangeName,
|
|
23202
|
+
});
|
|
23203
|
+
backtest$1.exchangeValidationService.validate(exchangeName, GET_EXCHANGE_METHOD_NAME);
|
|
23204
|
+
return backtest$1.exchangeSchemaService.get(exchangeName);
|
|
23205
|
+
}
|
|
23206
|
+
/**
|
|
23207
|
+
* Retrieves a registered frame schema by name.
|
|
23208
|
+
*
|
|
23209
|
+
* @param frameName - Unique frame identifier
|
|
23210
|
+
* @returns The frame schema configuration object
|
|
23211
|
+
* @throws Error if frame is not registered
|
|
23212
|
+
*
|
|
23213
|
+
* @example
|
|
23214
|
+
* ```typescript
|
|
23215
|
+
* const frame = getFrame("1d-backtest");
|
|
23216
|
+
* console.log(frame.interval); // "1m"
|
|
23217
|
+
* console.log(frame.startDate); // Date object
|
|
23218
|
+
* console.log(frame.endDate); // Date object
|
|
23219
|
+
* ```
|
|
23220
|
+
*/
|
|
23221
|
+
function getFrameSchema(frameName) {
|
|
23222
|
+
backtest$1.loggerService.log(GET_FRAME_METHOD_NAME, {
|
|
23223
|
+
frameName,
|
|
23224
|
+
});
|
|
23225
|
+
backtest$1.frameValidationService.validate(frameName, GET_FRAME_METHOD_NAME);
|
|
23226
|
+
return backtest$1.frameSchemaService.get(frameName);
|
|
23227
|
+
}
|
|
23228
|
+
/**
|
|
23229
|
+
* Retrieves a registered walker schema by name.
|
|
23230
|
+
*
|
|
23231
|
+
* @param walkerName - Unique walker identifier
|
|
23232
|
+
* @returns The walker schema configuration object
|
|
23233
|
+
* @throws Error if walker is not registered
|
|
23234
|
+
*
|
|
23235
|
+
* @example
|
|
23236
|
+
* ```typescript
|
|
23237
|
+
* const walker = getWalker("llm-prompt-optimizer");
|
|
23238
|
+
* console.log(walker.exchangeName); // "binance"
|
|
23239
|
+
* console.log(walker.frameName); // "1d-backtest"
|
|
23240
|
+
* console.log(walker.strategies); // ["my-strategy-v1", "my-strategy-v2"]
|
|
23241
|
+
* console.log(walker.metric); // "sharpeRatio"
|
|
23242
|
+
* ```
|
|
23243
|
+
*/
|
|
23244
|
+
function getWalkerSchema(walkerName) {
|
|
23245
|
+
backtest$1.loggerService.log(GET_WALKER_METHOD_NAME, {
|
|
23246
|
+
walkerName,
|
|
23247
|
+
});
|
|
23248
|
+
backtest$1.walkerValidationService.validate(walkerName, GET_WALKER_METHOD_NAME);
|
|
23249
|
+
return backtest$1.walkerSchemaService.get(walkerName);
|
|
23250
|
+
}
|
|
23251
|
+
/**
|
|
23252
|
+
* Retrieves a registered sizing schema by name.
|
|
23253
|
+
*
|
|
23254
|
+
* @param sizingName - Unique sizing identifier
|
|
23255
|
+
* @returns The sizing schema configuration object
|
|
23256
|
+
* @throws Error if sizing is not registered
|
|
23257
|
+
*
|
|
23258
|
+
* @example
|
|
23259
|
+
* ```typescript
|
|
23260
|
+
* const sizing = getSizing("conservative");
|
|
23261
|
+
* console.log(sizing.method); // "fixed-percentage"
|
|
23262
|
+
* console.log(sizing.riskPercentage); // 1
|
|
23263
|
+
* console.log(sizing.maxPositionPercentage); // 10
|
|
23264
|
+
* ```
|
|
23265
|
+
*/
|
|
23266
|
+
function getSizingSchema(sizingName) {
|
|
23267
|
+
backtest$1.loggerService.log(GET_SIZING_METHOD_NAME, {
|
|
23268
|
+
sizingName,
|
|
23269
|
+
});
|
|
23270
|
+
backtest$1.sizingValidationService.validate(sizingName, GET_SIZING_METHOD_NAME);
|
|
23271
|
+
return backtest$1.sizingSchemaService.get(sizingName);
|
|
23272
|
+
}
|
|
23273
|
+
/**
|
|
23274
|
+
* Retrieves a registered risk schema by name.
|
|
23275
|
+
*
|
|
23276
|
+
* @param riskName - Unique risk identifier
|
|
23277
|
+
* @returns The risk schema configuration object
|
|
23278
|
+
* @throws Error if risk is not registered
|
|
23279
|
+
*
|
|
23280
|
+
* @example
|
|
23281
|
+
* ```typescript
|
|
23282
|
+
* const risk = getRisk("conservative");
|
|
23283
|
+
* console.log(risk.maxConcurrentPositions); // 5
|
|
23284
|
+
* console.log(risk.validations); // Array of validation functions
|
|
23285
|
+
* ```
|
|
23286
|
+
*/
|
|
23287
|
+
function getRiskSchema(riskName) {
|
|
23288
|
+
backtest$1.loggerService.log(GET_RISK_METHOD_NAME, {
|
|
23289
|
+
riskName,
|
|
23290
|
+
});
|
|
23291
|
+
backtest$1.riskValidationService.validate(riskName, GET_RISK_METHOD_NAME);
|
|
23292
|
+
return backtest$1.riskSchemaService.get(riskName);
|
|
23293
|
+
}
|
|
23294
|
+
/**
|
|
23295
|
+
* Retrieves a registered optimizer schema by name.
|
|
23296
|
+
*
|
|
23297
|
+
* @param optimizerName - Unique optimizer identifier
|
|
23298
|
+
* @returns The optimizer schema configuration object
|
|
23299
|
+
* @throws Error if optimizer is not registered
|
|
23300
|
+
*
|
|
23301
|
+
* @example
|
|
23302
|
+
* ```typescript
|
|
23303
|
+
* const optimizer = getOptimizer("llm-strategy-generator");
|
|
23304
|
+
* console.log(optimizer.rangeTrain); // Array of training ranges
|
|
23305
|
+
* console.log(optimizer.rangeTest); // Testing range
|
|
23306
|
+
* console.log(optimizer.source); // Array of data sources
|
|
23307
|
+
* console.log(optimizer.getPrompt); // async function
|
|
23308
|
+
* ```
|
|
23309
|
+
*/
|
|
23310
|
+
function getOptimizerSchema(optimizerName) {
|
|
23311
|
+
backtest$1.loggerService.log(GET_OPTIMIZER_METHOD_NAME, {
|
|
23312
|
+
optimizerName,
|
|
23313
|
+
});
|
|
23314
|
+
backtest$1.optimizerValidationService.validate(optimizerName, GET_OPTIMIZER_METHOD_NAME);
|
|
23315
|
+
return backtest$1.optimizerSchemaService.get(optimizerName);
|
|
23316
|
+
}
|
|
23317
|
+
/**
|
|
23318
|
+
* Retrieves a registered action schema by name.
|
|
23319
|
+
*
|
|
23320
|
+
* @param actionName - Unique action identifier
|
|
23321
|
+
* @returns The action schema configuration object
|
|
23322
|
+
* @throws Error if action is not registered
|
|
23323
|
+
*
|
|
23324
|
+
* @example
|
|
23325
|
+
* ```typescript
|
|
23326
|
+
* const action = getAction("telegram-notifier");
|
|
23327
|
+
* console.log(action.handler); // Class constructor or object
|
|
23328
|
+
* console.log(action.callbacks); // Optional lifecycle callbacks
|
|
23329
|
+
* ```
|
|
23330
|
+
*/
|
|
23331
|
+
function getActionSchema(actionName) {
|
|
23332
|
+
backtest$1.loggerService.log(GET_ACTION_METHOD_NAME, {
|
|
23333
|
+
actionName,
|
|
23334
|
+
});
|
|
23335
|
+
backtest$1.actionValidationService.validate(actionName, GET_ACTION_METHOD_NAME);
|
|
23336
|
+
return backtest$1.actionSchemaService.get(actionName);
|
|
23337
|
+
}
|
|
23338
|
+
|
|
22809
23339
|
const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
|
|
22810
23340
|
const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
|
|
22811
23341
|
const FORMAT_PRICE_METHOD_NAME = "exchange.formatPrice";
|
|
22812
23342
|
const FORMAT_QUANTITY_METHOD_NAME = "exchange.formatQuantity";
|
|
22813
23343
|
const GET_DATE_METHOD_NAME = "exchange.getDate";
|
|
22814
23344
|
const GET_MODE_METHOD_NAME = "exchange.getMode";
|
|
23345
|
+
const GET_SYMBOL_METHOD_NAME = "exchange.getSymbol";
|
|
23346
|
+
const GET_CONTEXT_METHOD_NAME = "exchange.getContext";
|
|
22815
23347
|
const HAS_TRADE_CONTEXT_METHOD_NAME = "exchange.hasTradeContext";
|
|
22816
23348
|
const GET_ORDER_BOOK_METHOD_NAME = "exchange.getOrderBook";
|
|
22817
23349
|
/**
|
|
@@ -22994,6 +23526,48 @@ async function getMode() {
|
|
|
22994
23526
|
const { backtest: bt } = backtest$1.executionContextService.context;
|
|
22995
23527
|
return bt ? "backtest" : "live";
|
|
22996
23528
|
}
|
|
23529
|
+
/**
|
|
23530
|
+
* Gets the current trading symbol from execution context.
|
|
23531
|
+
*
|
|
23532
|
+
* @returns Promise resolving to the current trading symbol (e.g., "BTCUSDT")
|
|
23533
|
+
* @throws Error if execution context is not active
|
|
23534
|
+
*
|
|
23535
|
+
* @example
|
|
23536
|
+
* ```typescript
|
|
23537
|
+
* const symbol = await getSymbol();
|
|
23538
|
+
* console.log(symbol); // "BTCUSDT"
|
|
23539
|
+
* ```
|
|
23540
|
+
*/
|
|
23541
|
+
async function getSymbol() {
|
|
23542
|
+
backtest$1.loggerService.info(GET_SYMBOL_METHOD_NAME);
|
|
23543
|
+
if (!ExecutionContextService.hasContext()) {
|
|
23544
|
+
throw new Error("getSymbol requires an execution context");
|
|
23545
|
+
}
|
|
23546
|
+
const { symbol } = backtest$1.executionContextService.context;
|
|
23547
|
+
return symbol;
|
|
23548
|
+
}
|
|
23549
|
+
/**
|
|
23550
|
+
* Gets the current method context.
|
|
23551
|
+
*
|
|
23552
|
+
* Returns the context object from the method context service, which contains
|
|
23553
|
+
* information about the current method execution environment.
|
|
23554
|
+
*
|
|
23555
|
+
* @returns Promise resolving to the current method context object
|
|
23556
|
+
* @throws Error if method context is not active
|
|
23557
|
+
*
|
|
23558
|
+
* @example
|
|
23559
|
+
* ```typescript
|
|
23560
|
+
* const context = await getContext();
|
|
23561
|
+
* console.log(context); // { ...method context data... }
|
|
23562
|
+
* ```
|
|
23563
|
+
*/
|
|
23564
|
+
async function getContext() {
|
|
23565
|
+
backtest$1.loggerService.info(GET_CONTEXT_METHOD_NAME);
|
|
23566
|
+
if (!MethodContextService.hasContext()) {
|
|
23567
|
+
throw new Error("getContext requires a method context");
|
|
23568
|
+
}
|
|
23569
|
+
return backtest$1.methodContextService.context;
|
|
23570
|
+
}
|
|
22997
23571
|
/**
|
|
22998
23572
|
* Fetches order book for a trading pair from the registered exchange.
|
|
22999
23573
|
*
|
|
@@ -23030,52 +23604,12 @@ async function getOrderBook(symbol, depth) {
|
|
|
23030
23604
|
return await backtest$1.exchangeConnectionService.getOrderBook(symbol, depth);
|
|
23031
23605
|
}
|
|
23032
23606
|
|
|
23033
|
-
const
|
|
23034
|
-
const
|
|
23035
|
-
const
|
|
23036
|
-
const
|
|
23037
|
-
const
|
|
23038
|
-
const
|
|
23039
|
-
const BREAKEVEN_METHOD_NAME = "strategy.breakeven";
|
|
23040
|
-
/**
|
|
23041
|
-
* Stops the strategy from generating new signals.
|
|
23042
|
-
*
|
|
23043
|
-
* Sets internal flag to prevent strategy from opening new signals.
|
|
23044
|
-
* Current active signal (if any) will complete normally.
|
|
23045
|
-
* Backtest/Live mode will stop at the next safe point (idle state or after signal closes).
|
|
23046
|
-
*
|
|
23047
|
-
* Automatically detects backtest/live mode from execution context.
|
|
23048
|
-
*
|
|
23049
|
-
* @param symbol - Trading pair symbol
|
|
23050
|
-
* @param strategyName - Strategy name to stop
|
|
23051
|
-
* @returns Promise that resolves when stop flag is set
|
|
23052
|
-
*
|
|
23053
|
-
* @example
|
|
23054
|
-
* ```typescript
|
|
23055
|
-
* import { stop } from "backtest-kit";
|
|
23056
|
-
*
|
|
23057
|
-
* // Stop strategy after some condition
|
|
23058
|
-
* await stop("BTCUSDT", "my-strategy");
|
|
23059
|
-
* ```
|
|
23060
|
-
*/
|
|
23061
|
-
async function stop(symbol) {
|
|
23062
|
-
backtest$1.loggerService.info(STOP_METHOD_NAME, {
|
|
23063
|
-
symbol,
|
|
23064
|
-
});
|
|
23065
|
-
if (!ExecutionContextService.hasContext()) {
|
|
23066
|
-
throw new Error("stop requires an execution context");
|
|
23067
|
-
}
|
|
23068
|
-
if (!MethodContextService.hasContext()) {
|
|
23069
|
-
throw new Error("stop requires a method context");
|
|
23070
|
-
}
|
|
23071
|
-
const { backtest: isBacktest } = backtest$1.executionContextService.context;
|
|
23072
|
-
const { exchangeName, frameName, strategyName } = backtest$1.methodContextService.context;
|
|
23073
|
-
await backtest$1.strategyCoreService.stop(isBacktest, symbol, {
|
|
23074
|
-
exchangeName,
|
|
23075
|
-
frameName,
|
|
23076
|
-
strategyName,
|
|
23077
|
-
});
|
|
23078
|
-
}
|
|
23607
|
+
const CANCEL_METHOD_NAME = "strategy.commitCancel";
|
|
23608
|
+
const PARTIAL_PROFIT_METHOD_NAME = "strategy.commitPartialProfit";
|
|
23609
|
+
const PARTIAL_LOSS_METHOD_NAME = "strategy.commitPartialLoss";
|
|
23610
|
+
const TRAILING_STOP_METHOD_NAME = "strategy.commitTrailingStop";
|
|
23611
|
+
const TRAILING_PROFIT_METHOD_NAME = "strategy.commitTrailingTake";
|
|
23612
|
+
const BREAKEVEN_METHOD_NAME = "strategy.commitBreakeven";
|
|
23079
23613
|
/**
|
|
23080
23614
|
* Cancels the scheduled signal without stopping the strategy.
|
|
23081
23615
|
*
|
|
@@ -23098,7 +23632,7 @@ async function stop(symbol) {
|
|
|
23098
23632
|
* await cancel("BTCUSDT", "my-strategy", "manual-cancel-001");
|
|
23099
23633
|
* ```
|
|
23100
23634
|
*/
|
|
23101
|
-
async function
|
|
23635
|
+
async function commitCancel(symbol, cancelId) {
|
|
23102
23636
|
backtest$1.loggerService.info(CANCEL_METHOD_NAME, {
|
|
23103
23637
|
symbol,
|
|
23104
23638
|
cancelId,
|
|
@@ -23140,7 +23674,7 @@ async function cancel(symbol, cancelId) {
|
|
|
23140
23674
|
* }
|
|
23141
23675
|
* ```
|
|
23142
23676
|
*/
|
|
23143
|
-
async function
|
|
23677
|
+
async function commitPartialProfit(symbol, percentToClose) {
|
|
23144
23678
|
backtest$1.loggerService.info(PARTIAL_PROFIT_METHOD_NAME, {
|
|
23145
23679
|
symbol,
|
|
23146
23680
|
percentToClose,
|
|
@@ -23183,7 +23717,7 @@ async function partialProfit(symbol, percentToClose) {
|
|
|
23183
23717
|
* }
|
|
23184
23718
|
* ```
|
|
23185
23719
|
*/
|
|
23186
|
-
async function
|
|
23720
|
+
async function commitPartialLoss(symbol, percentToClose) {
|
|
23187
23721
|
backtest$1.loggerService.info(PARTIAL_LOSS_METHOD_NAME, {
|
|
23188
23722
|
symbol,
|
|
23189
23723
|
percentToClose,
|
|
@@ -23242,7 +23776,7 @@ async function partialLoss(symbol, percentToClose) {
|
|
|
23242
23776
|
* // success3 = true (ACCEPTED: newDistance = 10% - 7% = 3%, newSL = 97 > 95, better protection)
|
|
23243
23777
|
* ```
|
|
23244
23778
|
*/
|
|
23245
|
-
async function
|
|
23779
|
+
async function commitTrailingStop(symbol, percentShift, currentPrice) {
|
|
23246
23780
|
backtest$1.loggerService.info(TRAILING_STOP_METHOD_NAME, {
|
|
23247
23781
|
symbol,
|
|
23248
23782
|
percentShift,
|
|
@@ -23301,7 +23835,7 @@ async function trailingStop(symbol, percentShift, currentPrice) {
|
|
|
23301
23835
|
* // success3 = true (ACCEPTED: newDistance = 10% - 5% = 5%, newTP = 105 < 107, more conservative)
|
|
23302
23836
|
* ```
|
|
23303
23837
|
*/
|
|
23304
|
-
async function
|
|
23838
|
+
async function commitTrailingTake(symbol, percentShift, currentPrice) {
|
|
23305
23839
|
backtest$1.loggerService.info(TRAILING_PROFIT_METHOD_NAME, {
|
|
23306
23840
|
symbol,
|
|
23307
23841
|
percentShift,
|
|
@@ -23342,7 +23876,7 @@ async function trailingTake(symbol, percentShift, currentPrice) {
|
|
|
23342
23876
|
* }
|
|
23343
23877
|
* ```
|
|
23344
23878
|
*/
|
|
23345
|
-
async function
|
|
23879
|
+
async function commitBreakeven(symbol) {
|
|
23346
23880
|
backtest$1.loggerService.info(BREAKEVEN_METHOD_NAME, {
|
|
23347
23881
|
symbol,
|
|
23348
23882
|
});
|
|
@@ -23358,6 +23892,47 @@ async function breakeven(symbol) {
|
|
|
23358
23892
|
return await backtest$1.strategyCoreService.breakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
23359
23893
|
}
|
|
23360
23894
|
|
|
23895
|
+
const STOP_METHOD_NAME = "control.stop";
|
|
23896
|
+
/**
|
|
23897
|
+
* Stops the strategy from generating new signals.
|
|
23898
|
+
*
|
|
23899
|
+
* Sets internal flag to prevent strategy from opening new signals.
|
|
23900
|
+
* Current active signal (if any) will complete normally.
|
|
23901
|
+
* Backtest/Live mode will stop at the next safe point (idle state or after signal closes).
|
|
23902
|
+
*
|
|
23903
|
+
* Automatically detects backtest/live mode from execution context.
|
|
23904
|
+
*
|
|
23905
|
+
* @param symbol - Trading pair symbol
|
|
23906
|
+
* @param strategyName - Strategy name to stop
|
|
23907
|
+
* @returns Promise that resolves when stop flag is set
|
|
23908
|
+
*
|
|
23909
|
+
* @example
|
|
23910
|
+
* ```typescript
|
|
23911
|
+
* import { stop } from "backtest-kit";
|
|
23912
|
+
*
|
|
23913
|
+
* // Stop strategy after some condition
|
|
23914
|
+
* await stop("BTCUSDT", "my-strategy");
|
|
23915
|
+
* ```
|
|
23916
|
+
*/
|
|
23917
|
+
async function stop(symbol) {
|
|
23918
|
+
backtest$1.loggerService.info(STOP_METHOD_NAME, {
|
|
23919
|
+
symbol,
|
|
23920
|
+
});
|
|
23921
|
+
if (!ExecutionContextService.hasContext()) {
|
|
23922
|
+
throw new Error("stop requires an execution context");
|
|
23923
|
+
}
|
|
23924
|
+
if (!MethodContextService.hasContext()) {
|
|
23925
|
+
throw new Error("stop requires a method context");
|
|
23926
|
+
}
|
|
23927
|
+
const { backtest: isBacktest } = backtest$1.executionContextService.context;
|
|
23928
|
+
const { exchangeName, frameName, strategyName } = backtest$1.methodContextService.context;
|
|
23929
|
+
await backtest$1.strategyCoreService.stop(isBacktest, symbol, {
|
|
23930
|
+
exchangeName,
|
|
23931
|
+
frameName,
|
|
23932
|
+
strategyName,
|
|
23933
|
+
});
|
|
23934
|
+
}
|
|
23935
|
+
|
|
23361
23936
|
/**
|
|
23362
23937
|
* Sets custom logger implementation for the framework.
|
|
23363
23938
|
*
|
|
@@ -23508,14 +24083,14 @@ function getDefaultColumns() {
|
|
|
23508
24083
|
return DEFAULT_COLUMNS;
|
|
23509
24084
|
}
|
|
23510
24085
|
|
|
23511
|
-
const ADD_STRATEGY_METHOD_NAME = "add.
|
|
23512
|
-
const ADD_EXCHANGE_METHOD_NAME = "add.
|
|
23513
|
-
const ADD_FRAME_METHOD_NAME = "add.
|
|
23514
|
-
const ADD_WALKER_METHOD_NAME = "add.
|
|
23515
|
-
const ADD_SIZING_METHOD_NAME = "add.
|
|
23516
|
-
const ADD_RISK_METHOD_NAME = "add.
|
|
23517
|
-
const ADD_OPTIMIZER_METHOD_NAME = "add.
|
|
23518
|
-
const ADD_ACTION_METHOD_NAME = "add.
|
|
24086
|
+
const ADD_STRATEGY_METHOD_NAME = "add.addStrategySchema";
|
|
24087
|
+
const ADD_EXCHANGE_METHOD_NAME = "add.addExchangeSchema";
|
|
24088
|
+
const ADD_FRAME_METHOD_NAME = "add.addFrameSchema";
|
|
24089
|
+
const ADD_WALKER_METHOD_NAME = "add.addWalkerSchema";
|
|
24090
|
+
const ADD_SIZING_METHOD_NAME = "add.addSizingSchema";
|
|
24091
|
+
const ADD_RISK_METHOD_NAME = "add.addRiskSchema";
|
|
24092
|
+
const ADD_OPTIMIZER_METHOD_NAME = "add.addOptimizerSchema";
|
|
24093
|
+
const ADD_ACTION_METHOD_NAME = "add.addActionSchema";
|
|
23519
24094
|
/**
|
|
23520
24095
|
* Registers a trading strategy in the framework.
|
|
23521
24096
|
*
|
|
@@ -23550,7 +24125,7 @@ const ADD_ACTION_METHOD_NAME = "add.addAction";
|
|
|
23550
24125
|
* });
|
|
23551
24126
|
* ```
|
|
23552
24127
|
*/
|
|
23553
|
-
function
|
|
24128
|
+
function addStrategySchema(strategySchema) {
|
|
23554
24129
|
backtest$1.loggerService.info(ADD_STRATEGY_METHOD_NAME, {
|
|
23555
24130
|
strategySchema,
|
|
23556
24131
|
});
|
|
@@ -23592,7 +24167,7 @@ function addStrategy(strategySchema) {
|
|
|
23592
24167
|
* });
|
|
23593
24168
|
* ```
|
|
23594
24169
|
*/
|
|
23595
|
-
function
|
|
24170
|
+
function addExchangeSchema(exchangeSchema) {
|
|
23596
24171
|
backtest$1.loggerService.info(ADD_EXCHANGE_METHOD_NAME, {
|
|
23597
24172
|
exchangeSchema,
|
|
23598
24173
|
});
|
|
@@ -23629,7 +24204,7 @@ function addExchange(exchangeSchema) {
|
|
|
23629
24204
|
* });
|
|
23630
24205
|
* ```
|
|
23631
24206
|
*/
|
|
23632
|
-
function
|
|
24207
|
+
function addFrameSchema(frameSchema) {
|
|
23633
24208
|
backtest$1.loggerService.info(ADD_FRAME_METHOD_NAME, {
|
|
23634
24209
|
frameSchema,
|
|
23635
24210
|
});
|
|
@@ -23673,7 +24248,7 @@ function addFrame(frameSchema) {
|
|
|
23673
24248
|
* });
|
|
23674
24249
|
* ```
|
|
23675
24250
|
*/
|
|
23676
|
-
function
|
|
24251
|
+
function addWalkerSchema(walkerSchema) {
|
|
23677
24252
|
backtest$1.loggerService.info(ADD_WALKER_METHOD_NAME, {
|
|
23678
24253
|
walkerSchema,
|
|
23679
24254
|
});
|
|
@@ -23732,7 +24307,7 @@ function addWalker(walkerSchema) {
|
|
|
23732
24307
|
* });
|
|
23733
24308
|
* ```
|
|
23734
24309
|
*/
|
|
23735
|
-
function
|
|
24310
|
+
function addSizingSchema(sizingSchema) {
|
|
23736
24311
|
backtest$1.loggerService.info(ADD_SIZING_METHOD_NAME, {
|
|
23737
24312
|
sizingSchema,
|
|
23738
24313
|
});
|
|
@@ -23800,7 +24375,7 @@ function addSizing(sizingSchema) {
|
|
|
23800
24375
|
* });
|
|
23801
24376
|
* ```
|
|
23802
24377
|
*/
|
|
23803
|
-
function
|
|
24378
|
+
function addRiskSchema(riskSchema) {
|
|
23804
24379
|
backtest$1.loggerService.info(ADD_RISK_METHOD_NAME, {
|
|
23805
24380
|
riskSchema,
|
|
23806
24381
|
});
|
|
@@ -23894,7 +24469,7 @@ function addRisk(riskSchema) {
|
|
|
23894
24469
|
* });
|
|
23895
24470
|
* ```
|
|
23896
24471
|
*/
|
|
23897
|
-
function
|
|
24472
|
+
function addOptimizerSchema(optimizerSchema) {
|
|
23898
24473
|
backtest$1.loggerService.info(ADD_OPTIMIZER_METHOD_NAME, {
|
|
23899
24474
|
optimizerSchema,
|
|
23900
24475
|
});
|
|
@@ -23969,7 +24544,7 @@ function addOptimizer(optimizerSchema) {
|
|
|
23969
24544
|
* });
|
|
23970
24545
|
* ```
|
|
23971
24546
|
*/
|
|
23972
|
-
function
|
|
24547
|
+
function addActionSchema(actionSchema) {
|
|
23973
24548
|
backtest$1.loggerService.info(ADD_ACTION_METHOD_NAME, {
|
|
23974
24549
|
actionSchema,
|
|
23975
24550
|
});
|
|
@@ -23977,14 +24552,14 @@ function addAction(actionSchema) {
|
|
|
23977
24552
|
backtest$1.actionSchemaService.register(actionSchema.actionName, actionSchema);
|
|
23978
24553
|
}
|
|
23979
24554
|
|
|
23980
|
-
const METHOD_NAME_OVERRIDE_STRATEGY = "function.override.
|
|
23981
|
-
const METHOD_NAME_OVERRIDE_EXCHANGE = "function.override.
|
|
23982
|
-
const METHOD_NAME_OVERRIDE_FRAME = "function.override.
|
|
23983
|
-
const METHOD_NAME_OVERRIDE_WALKER = "function.override.
|
|
23984
|
-
const METHOD_NAME_OVERRIDE_SIZING = "function.override.
|
|
23985
|
-
const METHOD_NAME_OVERRIDE_RISK = "function.override.
|
|
23986
|
-
const METHOD_NAME_OVERRIDE_OPTIMIZER = "function.override.
|
|
23987
|
-
const METHOD_NAME_OVERRIDE_ACTION = "function.override.
|
|
24555
|
+
const METHOD_NAME_OVERRIDE_STRATEGY = "function.override.overrideStrategySchema";
|
|
24556
|
+
const METHOD_NAME_OVERRIDE_EXCHANGE = "function.override.overrideExchangeSchema";
|
|
24557
|
+
const METHOD_NAME_OVERRIDE_FRAME = "function.override.overrideFrameSchema";
|
|
24558
|
+
const METHOD_NAME_OVERRIDE_WALKER = "function.override.overrideWalkerSchema";
|
|
24559
|
+
const METHOD_NAME_OVERRIDE_SIZING = "function.override.overrideSizingSchema";
|
|
24560
|
+
const METHOD_NAME_OVERRIDE_RISK = "function.override.overrideRiskSchema";
|
|
24561
|
+
const METHOD_NAME_OVERRIDE_OPTIMIZER = "function.override.overrideOptimizerSchema";
|
|
24562
|
+
const METHOD_NAME_OVERRIDE_ACTION = "function.override.overrideActionSchema";
|
|
23988
24563
|
/**
|
|
23989
24564
|
* Overrides an existing trading strategy in the framework.
|
|
23990
24565
|
*
|
|
@@ -24005,7 +24580,7 @@ const METHOD_NAME_OVERRIDE_ACTION = "function.override.overrideAction";
|
|
|
24005
24580
|
* });
|
|
24006
24581
|
* ```
|
|
24007
24582
|
*/
|
|
24008
|
-
async function
|
|
24583
|
+
async function overrideStrategySchema(strategySchema) {
|
|
24009
24584
|
backtest$1.loggerService.log(METHOD_NAME_OVERRIDE_STRATEGY, {
|
|
24010
24585
|
strategySchema,
|
|
24011
24586
|
});
|
|
@@ -24033,7 +24608,7 @@ async function overrideStrategy(strategySchema) {
|
|
|
24033
24608
|
* });
|
|
24034
24609
|
* ```
|
|
24035
24610
|
*/
|
|
24036
|
-
async function
|
|
24611
|
+
async function overrideExchangeSchema(exchangeSchema) {
|
|
24037
24612
|
backtest$1.loggerService.log(METHOD_NAME_OVERRIDE_EXCHANGE, {
|
|
24038
24613
|
exchangeSchema,
|
|
24039
24614
|
});
|
|
@@ -24061,7 +24636,7 @@ async function overrideExchange(exchangeSchema) {
|
|
|
24061
24636
|
* });
|
|
24062
24637
|
* ```
|
|
24063
24638
|
*/
|
|
24064
|
-
async function
|
|
24639
|
+
async function overrideFrameSchema(frameSchema) {
|
|
24065
24640
|
backtest$1.loggerService.log(METHOD_NAME_OVERRIDE_FRAME, {
|
|
24066
24641
|
frameSchema,
|
|
24067
24642
|
});
|
|
@@ -24090,7 +24665,7 @@ async function overrideFrame(frameSchema) {
|
|
|
24090
24665
|
* });
|
|
24091
24666
|
* ```
|
|
24092
24667
|
*/
|
|
24093
|
-
async function
|
|
24668
|
+
async function overrideWalkerSchema(walkerSchema) {
|
|
24094
24669
|
backtest$1.loggerService.log(METHOD_NAME_OVERRIDE_WALKER, {
|
|
24095
24670
|
walkerSchema,
|
|
24096
24671
|
});
|
|
@@ -24122,7 +24697,7 @@ async function overrideWalker(walkerSchema) {
|
|
|
24122
24697
|
* });
|
|
24123
24698
|
* ```
|
|
24124
24699
|
*/
|
|
24125
|
-
async function
|
|
24700
|
+
async function overrideSizingSchema(sizingSchema) {
|
|
24126
24701
|
backtest$1.loggerService.log(METHOD_NAME_OVERRIDE_SIZING, {
|
|
24127
24702
|
sizingSchema,
|
|
24128
24703
|
});
|
|
@@ -24149,7 +24724,7 @@ async function overrideSizing(sizingSchema) {
|
|
|
24149
24724
|
* });
|
|
24150
24725
|
* ```
|
|
24151
24726
|
*/
|
|
24152
|
-
async function
|
|
24727
|
+
async function overrideRiskSchema(riskSchema) {
|
|
24153
24728
|
backtest$1.loggerService.log(METHOD_NAME_OVERRIDE_RISK, {
|
|
24154
24729
|
riskSchema,
|
|
24155
24730
|
});
|
|
@@ -24183,7 +24758,7 @@ async function overrideRisk(riskSchema) {
|
|
|
24183
24758
|
* });
|
|
24184
24759
|
* ```
|
|
24185
24760
|
*/
|
|
24186
|
-
async function
|
|
24761
|
+
async function overrideOptimizerSchema(optimizerSchema) {
|
|
24187
24762
|
backtest$1.loggerService.log(METHOD_NAME_OVERRIDE_OPTIMIZER, {
|
|
24188
24763
|
optimizerSchema,
|
|
24189
24764
|
});
|
|
@@ -24250,7 +24825,7 @@ async function overrideOptimizer(optimizerSchema) {
|
|
|
24250
24825
|
* });
|
|
24251
24826
|
* ```
|
|
24252
24827
|
*/
|
|
24253
|
-
async function
|
|
24828
|
+
async function overrideActionSchema(actionSchema) {
|
|
24254
24829
|
backtest$1.loggerService.log(METHOD_NAME_OVERRIDE_ACTION, {
|
|
24255
24830
|
actionSchema,
|
|
24256
24831
|
});
|
|
@@ -24258,13 +24833,13 @@ async function overrideAction(actionSchema) {
|
|
|
24258
24833
|
return backtest$1.actionSchemaService.override(actionSchema.actionName, actionSchema);
|
|
24259
24834
|
}
|
|
24260
24835
|
|
|
24261
|
-
const LIST_EXCHANGES_METHOD_NAME = "list.
|
|
24262
|
-
const LIST_STRATEGIES_METHOD_NAME = "list.
|
|
24263
|
-
const LIST_FRAMES_METHOD_NAME = "list.
|
|
24264
|
-
const LIST_WALKERS_METHOD_NAME = "list.
|
|
24265
|
-
const LIST_SIZINGS_METHOD_NAME = "list.
|
|
24266
|
-
const LIST_RISKS_METHOD_NAME = "list.
|
|
24267
|
-
const LIST_OPTIMIZERS_METHOD_NAME = "list.
|
|
24836
|
+
const LIST_EXCHANGES_METHOD_NAME = "list.listExchangeSchema";
|
|
24837
|
+
const LIST_STRATEGIES_METHOD_NAME = "list.listStrategySchema";
|
|
24838
|
+
const LIST_FRAMES_METHOD_NAME = "list.listFrameSchema";
|
|
24839
|
+
const LIST_WALKERS_METHOD_NAME = "list.listWalkerSchema";
|
|
24840
|
+
const LIST_SIZINGS_METHOD_NAME = "list.listSizingSchema";
|
|
24841
|
+
const LIST_RISKS_METHOD_NAME = "list.listRiskSchema";
|
|
24842
|
+
const LIST_OPTIMIZERS_METHOD_NAME = "list.listOptimizerSchema";
|
|
24268
24843
|
/**
|
|
24269
24844
|
* Returns a list of all registered exchange schemas.
|
|
24270
24845
|
*
|
|
@@ -24290,7 +24865,7 @@ const LIST_OPTIMIZERS_METHOD_NAME = "list.listOptimizers";
|
|
|
24290
24865
|
* // [{ exchangeName: "binance", note: "Binance cryptocurrency exchange", ... }]
|
|
24291
24866
|
* ```
|
|
24292
24867
|
*/
|
|
24293
|
-
async function
|
|
24868
|
+
async function listExchangeSchema() {
|
|
24294
24869
|
backtest$1.loggerService.log(LIST_EXCHANGES_METHOD_NAME);
|
|
24295
24870
|
return await backtest$1.exchangeValidationService.list();
|
|
24296
24871
|
}
|
|
@@ -24324,7 +24899,7 @@ async function listExchanges() {
|
|
|
24324
24899
|
* // [{ strategyName: "my-strategy", note: "Simple moving average...", ... }]
|
|
24325
24900
|
* ```
|
|
24326
24901
|
*/
|
|
24327
|
-
async function
|
|
24902
|
+
async function listStrategySchema() {
|
|
24328
24903
|
backtest$1.loggerService.log(LIST_STRATEGIES_METHOD_NAME);
|
|
24329
24904
|
return await backtest$1.strategyValidationService.list();
|
|
24330
24905
|
}
|
|
@@ -24353,7 +24928,7 @@ async function listStrategies() {
|
|
|
24353
24928
|
* // [{ frameName: "1d-backtest", note: "One day backtest...", ... }]
|
|
24354
24929
|
* ```
|
|
24355
24930
|
*/
|
|
24356
|
-
async function
|
|
24931
|
+
async function listFrameSchema() {
|
|
24357
24932
|
backtest$1.loggerService.log(LIST_FRAMES_METHOD_NAME);
|
|
24358
24933
|
return await backtest$1.frameValidationService.list();
|
|
24359
24934
|
}
|
|
@@ -24383,7 +24958,7 @@ async function listFrames() {
|
|
|
24383
24958
|
* // [{ walkerName: "llm-prompt-optimizer", note: "Compare LLM...", ... }]
|
|
24384
24959
|
* ```
|
|
24385
24960
|
*/
|
|
24386
|
-
async function
|
|
24961
|
+
async function listWalkerSchema() {
|
|
24387
24962
|
backtest$1.loggerService.log(LIST_WALKERS_METHOD_NAME);
|
|
24388
24963
|
return await backtest$1.walkerValidationService.list();
|
|
24389
24964
|
}
|
|
@@ -24422,7 +24997,7 @@ async function listWalkers() {
|
|
|
24422
24997
|
* // ]
|
|
24423
24998
|
* ```
|
|
24424
24999
|
*/
|
|
24425
|
-
async function
|
|
25000
|
+
async function listSizingSchema() {
|
|
24426
25001
|
backtest$1.loggerService.log(LIST_SIZINGS_METHOD_NAME);
|
|
24427
25002
|
return await backtest$1.sizingValidationService.list();
|
|
24428
25003
|
}
|
|
@@ -24458,7 +25033,7 @@ async function listSizings() {
|
|
|
24458
25033
|
* // ]
|
|
24459
25034
|
* ```
|
|
24460
25035
|
*/
|
|
24461
|
-
async function
|
|
25036
|
+
async function listRiskSchema() {
|
|
24462
25037
|
backtest$1.loggerService.log(LIST_RISKS_METHOD_NAME);
|
|
24463
25038
|
return await backtest$1.riskValidationService.list();
|
|
24464
25039
|
}
|
|
@@ -24498,7 +25073,7 @@ async function listRisks() {
|
|
|
24498
25073
|
* // [{ optimizerName: "llm-strategy-generator", note: "Generates...", ... }]
|
|
24499
25074
|
* ```
|
|
24500
25075
|
*/
|
|
24501
|
-
async function
|
|
25076
|
+
async function listOptimizerSchema() {
|
|
24502
25077
|
backtest$1.loggerService.log(LIST_OPTIMIZERS_METHOD_NAME);
|
|
24503
25078
|
return await backtest$1.optimizerValidationService.list();
|
|
24504
25079
|
}
|
|
@@ -24533,8 +25108,10 @@ const LISTEN_BREAKEVEN_METHOD_NAME = "event.listenBreakeven";
|
|
|
24533
25108
|
const LISTEN_BREAKEVEN_ONCE_METHOD_NAME = "event.listenBreakevenOnce";
|
|
24534
25109
|
const LISTEN_RISK_METHOD_NAME = "event.listenRisk";
|
|
24535
25110
|
const LISTEN_RISK_ONCE_METHOD_NAME = "event.listenRiskOnce";
|
|
24536
|
-
const
|
|
24537
|
-
const
|
|
25111
|
+
const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
|
|
25112
|
+
const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
|
|
25113
|
+
const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
|
|
25114
|
+
const LISTEN_ACTIVE_PING_ONCE_METHOD_NAME = "event.listenActivePingOnce";
|
|
24538
25115
|
/**
|
|
24539
25116
|
* Subscribes to all signal events with queued async processing.
|
|
24540
25117
|
*
|
|
@@ -25233,7 +25810,7 @@ function listenValidation(fn) {
|
|
|
25233
25810
|
* unsubscribe();
|
|
25234
25811
|
* ```
|
|
25235
25812
|
*/
|
|
25236
|
-
function
|
|
25813
|
+
function listenPartialProfitAvailable(fn) {
|
|
25237
25814
|
backtest$1.loggerService.log(LISTEN_PARTIAL_PROFIT_METHOD_NAME);
|
|
25238
25815
|
return partialProfitSubject.subscribe(queued(async (event) => fn(event)));
|
|
25239
25816
|
}
|
|
@@ -25267,7 +25844,7 @@ function listenPartialProfit(fn) {
|
|
|
25267
25844
|
* cancel();
|
|
25268
25845
|
* ```
|
|
25269
25846
|
*/
|
|
25270
|
-
function
|
|
25847
|
+
function listenPartialProfitAvailableOnce(filterFn, fn) {
|
|
25271
25848
|
backtest$1.loggerService.log(LISTEN_PARTIAL_PROFIT_ONCE_METHOD_NAME);
|
|
25272
25849
|
return partialProfitSubject.filter(filterFn).once(fn);
|
|
25273
25850
|
}
|
|
@@ -25295,7 +25872,7 @@ function listenPartialProfitOnce(filterFn, fn) {
|
|
|
25295
25872
|
* unsubscribe();
|
|
25296
25873
|
* ```
|
|
25297
25874
|
*/
|
|
25298
|
-
function
|
|
25875
|
+
function listenPartialLossAvailable(fn) {
|
|
25299
25876
|
backtest$1.loggerService.log(LISTEN_PARTIAL_LOSS_METHOD_NAME);
|
|
25300
25877
|
return partialLossSubject.subscribe(queued(async (event) => fn(event)));
|
|
25301
25878
|
}
|
|
@@ -25329,7 +25906,7 @@ function listenPartialLoss(fn) {
|
|
|
25329
25906
|
* cancel();
|
|
25330
25907
|
* ```
|
|
25331
25908
|
*/
|
|
25332
|
-
function
|
|
25909
|
+
function listenPartialLossAvailableOnce(filterFn, fn) {
|
|
25333
25910
|
backtest$1.loggerService.log(LISTEN_PARTIAL_LOSS_ONCE_METHOD_NAME);
|
|
25334
25911
|
return partialLossSubject.filter(filterFn).once(fn);
|
|
25335
25912
|
}
|
|
@@ -25359,7 +25936,7 @@ function listenPartialLossOnce(filterFn, fn) {
|
|
|
25359
25936
|
* unsubscribe();
|
|
25360
25937
|
* ```
|
|
25361
25938
|
*/
|
|
25362
|
-
function
|
|
25939
|
+
function listenBreakevenAvailable(fn) {
|
|
25363
25940
|
backtest$1.loggerService.log(LISTEN_BREAKEVEN_METHOD_NAME);
|
|
25364
25941
|
return breakevenSubject.subscribe(queued(async (event) => fn(event)));
|
|
25365
25942
|
}
|
|
@@ -25393,7 +25970,7 @@ function listenBreakeven(fn) {
|
|
|
25393
25970
|
* cancel();
|
|
25394
25971
|
* ```
|
|
25395
25972
|
*/
|
|
25396
|
-
function
|
|
25973
|
+
function listenBreakevenAvailableOnce(filterFn, fn) {
|
|
25397
25974
|
backtest$1.loggerService.log(LISTEN_BREAKEVEN_ONCE_METHOD_NAME);
|
|
25398
25975
|
return breakevenSubject.filter(filterFn).once(fn);
|
|
25399
25976
|
}
|
|
@@ -25489,9 +26066,9 @@ function listenRiskOnce(filterFn, fn) {
|
|
|
25489
26066
|
* unsubscribe();
|
|
25490
26067
|
* ```
|
|
25491
26068
|
*/
|
|
25492
|
-
function
|
|
25493
|
-
backtest$1.loggerService.log(
|
|
25494
|
-
return
|
|
26069
|
+
function listenSchedulePing(fn) {
|
|
26070
|
+
backtest$1.loggerService.log(LISTEN_SCHEDULE_PING_METHOD_NAME);
|
|
26071
|
+
return schedulePingSubject.subscribe(queued(async (event) => fn(event)));
|
|
25495
26072
|
}
|
|
25496
26073
|
/**
|
|
25497
26074
|
* Subscribes to filtered ping events with one-time execution.
|
|
@@ -25523,9 +26100,74 @@ function listenPing(fn) {
|
|
|
25523
26100
|
* cancel();
|
|
25524
26101
|
* ```
|
|
25525
26102
|
*/
|
|
25526
|
-
function
|
|
25527
|
-
backtest$1.loggerService.log(
|
|
25528
|
-
return
|
|
26103
|
+
function listenSchedulePingOnce(filterFn, fn) {
|
|
26104
|
+
backtest$1.loggerService.log(LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME);
|
|
26105
|
+
return schedulePingSubject.filter(filterFn).once(fn);
|
|
26106
|
+
}
|
|
26107
|
+
/**
|
|
26108
|
+
* Subscribes to active ping events with queued async processing.
|
|
26109
|
+
*
|
|
26110
|
+
* Listens for active pending signal monitoring events emitted every minute.
|
|
26111
|
+
* Useful for tracking active signal lifecycle and implementing dynamic management logic.
|
|
26112
|
+
*
|
|
26113
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
26114
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
26115
|
+
*
|
|
26116
|
+
* @param fn - Callback function to handle active ping events
|
|
26117
|
+
* @returns Unsubscribe function to stop listening
|
|
26118
|
+
*
|
|
26119
|
+
* @example
|
|
26120
|
+
* ```typescript
|
|
26121
|
+
* import { listenActivePing } from "./function/event";
|
|
26122
|
+
*
|
|
26123
|
+
* const unsubscribe = listenActivePing((event) => {
|
|
26124
|
+
* console.log(`[${event.backtest ? "Backtest" : "Live"}] Active Ping`);
|
|
26125
|
+
* console.log(`Symbol: ${event.symbol}, Strategy: ${event.strategyName}`);
|
|
26126
|
+
* console.log(`Signal ID: ${event.data.id}, Position: ${event.data.position}`);
|
|
26127
|
+
* console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
|
|
26128
|
+
* });
|
|
26129
|
+
*
|
|
26130
|
+
* // Later: stop listening
|
|
26131
|
+
* unsubscribe();
|
|
26132
|
+
* ```
|
|
26133
|
+
*/
|
|
26134
|
+
function listenActivePing(fn) {
|
|
26135
|
+
backtest$1.loggerService.log(LISTEN_ACTIVE_PING_METHOD_NAME);
|
|
26136
|
+
return activePingSubject.subscribe(queued(async (event) => fn(event)));
|
|
26137
|
+
}
|
|
26138
|
+
/**
|
|
26139
|
+
* Subscribes to filtered active ping events with one-time execution.
|
|
26140
|
+
*
|
|
26141
|
+
* Listens for events matching the filter predicate, then executes callback once
|
|
26142
|
+
* and automatically unsubscribes. Useful for waiting for specific active ping conditions.
|
|
26143
|
+
*
|
|
26144
|
+
* @param filterFn - Predicate to filter which events trigger the callback
|
|
26145
|
+
* @param fn - Callback function to handle the filtered event (called only once)
|
|
26146
|
+
* @returns Unsubscribe function to cancel the listener before it fires
|
|
26147
|
+
*
|
|
26148
|
+
* @example
|
|
26149
|
+
* ```typescript
|
|
26150
|
+
* import { listenActivePingOnce } from "./function/event";
|
|
26151
|
+
*
|
|
26152
|
+
* // Wait for first active ping on BTCUSDT
|
|
26153
|
+
* listenActivePingOnce(
|
|
26154
|
+
* (event) => event.symbol === "BTCUSDT",
|
|
26155
|
+
* (event) => console.log("First BTCUSDT active ping received")
|
|
26156
|
+
* );
|
|
26157
|
+
*
|
|
26158
|
+
* // Wait for active ping in backtest mode
|
|
26159
|
+
* const cancel = listenActivePingOnce(
|
|
26160
|
+
* (event) => event.backtest === true,
|
|
26161
|
+
* (event) => console.log("Backtest active ping received at", new Date(event.timestamp))
|
|
26162
|
+
* );
|
|
26163
|
+
*
|
|
26164
|
+
* // Cancel if needed before event fires
|
|
26165
|
+
* cancel();
|
|
26166
|
+
* ```
|
|
26167
|
+
*/
|
|
26168
|
+
function listenActivePingOnce(filterFn, fn) {
|
|
26169
|
+
backtest$1.loggerService.log(LISTEN_ACTIVE_PING_ONCE_METHOD_NAME);
|
|
26170
|
+
return activePingSubject.filter(filterFn).once(fn);
|
|
25529
26171
|
}
|
|
25530
26172
|
|
|
25531
26173
|
const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
|
|
@@ -25595,7 +26237,7 @@ const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
|
|
|
25595
26237
|
* // ./dump/strategy/{uuid}/06_llm_output.md (final signal)
|
|
25596
26238
|
* ```
|
|
25597
26239
|
*/
|
|
25598
|
-
async function
|
|
26240
|
+
async function dumpSignalData(signalId, history, signal, outputDir = "./dump/strategy") {
|
|
25599
26241
|
backtest$1.loggerService.info(DUMP_SIGNAL_METHOD_NAME, {
|
|
25600
26242
|
signalId,
|
|
25601
26243
|
history,
|
|
@@ -25615,11 +26257,12 @@ const BACKTEST_METHOD_NAME_GET_STATUS = "BacktestUtils.getStatus";
|
|
|
25615
26257
|
const BACKTEST_METHOD_NAME_GET_PENDING_SIGNAL = "BacktestUtils.getPendingSignal";
|
|
25616
26258
|
const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
|
|
25617
26259
|
const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
|
|
25618
|
-
const
|
|
25619
|
-
const
|
|
25620
|
-
const
|
|
25621
|
-
const
|
|
25622
|
-
const
|
|
26260
|
+
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
26261
|
+
const BACKTEST_METHOD_NAME_CANCEL = "BacktestUtils.commitCancel";
|
|
26262
|
+
const BACKTEST_METHOD_NAME_PARTIAL_PROFIT = "BacktestUtils.commitPartialProfit";
|
|
26263
|
+
const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.commitPartialLoss";
|
|
26264
|
+
const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.commitTrailingStop";
|
|
26265
|
+
const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.commitTrailingTake";
|
|
25623
26266
|
const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
|
|
25624
26267
|
/**
|
|
25625
26268
|
* Internal task function that runs backtest and handles completion.
|
|
@@ -26143,14 +26786,14 @@ class BacktestUtils {
|
|
|
26143
26786
|
* @example
|
|
26144
26787
|
* ```typescript
|
|
26145
26788
|
* // Cancel scheduled signal with custom ID
|
|
26146
|
-
* await Backtest.
|
|
26789
|
+
* await Backtest.commitCancel("BTCUSDT", "my-strategy", {
|
|
26147
26790
|
* exchangeName: "binance",
|
|
26148
26791
|
* frameName: "frame1",
|
|
26149
26792
|
* strategyName: "my-strategy"
|
|
26150
26793
|
* }, "manual-cancel-001");
|
|
26151
26794
|
* ```
|
|
26152
26795
|
*/
|
|
26153
|
-
this.
|
|
26796
|
+
this.commitCancel = async (symbol, context, cancelId) => {
|
|
26154
26797
|
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_CANCEL, {
|
|
26155
26798
|
symbol,
|
|
26156
26799
|
context,
|
|
@@ -26188,7 +26831,7 @@ class BacktestUtils {
|
|
|
26188
26831
|
* @example
|
|
26189
26832
|
* ```typescript
|
|
26190
26833
|
* // Close 30% of LONG position at profit
|
|
26191
|
-
* const success = await Backtest.
|
|
26834
|
+
* const success = await Backtest.commitPartialProfit("BTCUSDT", 30, 45000, {
|
|
26192
26835
|
* exchangeName: "binance",
|
|
26193
26836
|
* frameName: "frame1",
|
|
26194
26837
|
* strategyName: "my-strategy"
|
|
@@ -26198,7 +26841,7 @@ class BacktestUtils {
|
|
|
26198
26841
|
* }
|
|
26199
26842
|
* ```
|
|
26200
26843
|
*/
|
|
26201
|
-
this.
|
|
26844
|
+
this.commitPartialProfit = async (symbol, percentToClose, currentPrice, context) => {
|
|
26202
26845
|
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_PARTIAL_PROFIT, {
|
|
26203
26846
|
symbol,
|
|
26204
26847
|
percentToClose,
|
|
@@ -26237,7 +26880,7 @@ class BacktestUtils {
|
|
|
26237
26880
|
* @example
|
|
26238
26881
|
* ```typescript
|
|
26239
26882
|
* // Close 40% of LONG position at loss
|
|
26240
|
-
* const success = await Backtest.
|
|
26883
|
+
* const success = await Backtest.commitPartialLoss("BTCUSDT", 40, 38000, {
|
|
26241
26884
|
* exchangeName: "binance",
|
|
26242
26885
|
* frameName: "frame1",
|
|
26243
26886
|
* strategyName: "my-strategy"
|
|
@@ -26247,7 +26890,7 @@ class BacktestUtils {
|
|
|
26247
26890
|
* }
|
|
26248
26891
|
* ```
|
|
26249
26892
|
*/
|
|
26250
|
-
this.
|
|
26893
|
+
this.commitPartialLoss = async (symbol, percentToClose, currentPrice, context) => {
|
|
26251
26894
|
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_PARTIAL_LOSS, {
|
|
26252
26895
|
symbol,
|
|
26253
26896
|
percentToClose,
|
|
@@ -26295,7 +26938,7 @@ class BacktestUtils {
|
|
|
26295
26938
|
* // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
|
|
26296
26939
|
*
|
|
26297
26940
|
* // First call: tighten by 5%
|
|
26298
|
-
* await Backtest.
|
|
26941
|
+
* await Backtest.commitTrailingStop("BTCUSDT", -5, 102, {
|
|
26299
26942
|
* exchangeName: "binance",
|
|
26300
26943
|
* frameName: "frame1",
|
|
26301
26944
|
* strategyName: "my-strategy"
|
|
@@ -26303,15 +26946,15 @@ class BacktestUtils {
|
|
|
26303
26946
|
* // newDistance = 10% - 5% = 5%, newSL = 95
|
|
26304
26947
|
*
|
|
26305
26948
|
* // Second call: try weaker protection (smaller percentShift)
|
|
26306
|
-
* await Backtest.
|
|
26949
|
+
* await Backtest.commitTrailingStop("BTCUSDT", -3, 102, context);
|
|
26307
26950
|
* // SKIPPED: newSL=97 < 95 (worse protection, larger % absorbs smaller)
|
|
26308
26951
|
*
|
|
26309
26952
|
* // Third call: stronger protection (larger percentShift)
|
|
26310
|
-
* await Backtest.
|
|
26953
|
+
* await Backtest.commitTrailingStop("BTCUSDT", -7, 102, context);
|
|
26311
26954
|
* // ACCEPTED: newDistance = 10% - 7% = 3%, newSL = 97 > 95 (better protection)
|
|
26312
26955
|
* ```
|
|
26313
26956
|
*/
|
|
26314
|
-
this.
|
|
26957
|
+
this.commitTrailingStop = async (symbol, percentShift, currentPrice, context) => {
|
|
26315
26958
|
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_TRAILING_STOP, {
|
|
26316
26959
|
symbol,
|
|
26317
26960
|
percentShift,
|
|
@@ -26359,7 +27002,7 @@ class BacktestUtils {
|
|
|
26359
27002
|
* // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
|
|
26360
27003
|
*
|
|
26361
27004
|
* // First call: bring TP closer by 3%
|
|
26362
|
-
* await Backtest.
|
|
27005
|
+
* await Backtest.commitTrailingTake("BTCUSDT", -3, 102, {
|
|
26363
27006
|
* exchangeName: "binance",
|
|
26364
27007
|
* frameName: "frame1",
|
|
26365
27008
|
* strategyName: "my-strategy"
|
|
@@ -26367,15 +27010,15 @@ class BacktestUtils {
|
|
|
26367
27010
|
* // newDistance = 10% - 3% = 7%, newTP = 107
|
|
26368
27011
|
*
|
|
26369
27012
|
* // Second call: try to move TP further (less conservative)
|
|
26370
|
-
* await Backtest.
|
|
27013
|
+
* await Backtest.commitTrailingTake("BTCUSDT", 2, 102, context);
|
|
26371
27014
|
* // SKIPPED: newTP=112 > 107 (less conservative, larger % absorbs smaller)
|
|
26372
27015
|
*
|
|
26373
27016
|
* // Third call: even more conservative
|
|
26374
|
-
* await Backtest.
|
|
27017
|
+
* await Backtest.commitTrailingTake("BTCUSDT", -5, 102, context);
|
|
26375
27018
|
* // ACCEPTED: newDistance = 10% - 5% = 5%, newTP = 105 < 107 (more conservative)
|
|
26376
27019
|
* ```
|
|
26377
27020
|
*/
|
|
26378
|
-
this.
|
|
27021
|
+
this.commitTrailingTake = async (symbol, percentShift, currentPrice, context) => {
|
|
26379
27022
|
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_TRAILING_PROFIT, {
|
|
26380
27023
|
symbol,
|
|
26381
27024
|
percentShift,
|
|
@@ -26408,7 +27051,7 @@ class BacktestUtils {
|
|
|
26408
27051
|
*
|
|
26409
27052
|
* @example
|
|
26410
27053
|
* ```typescript
|
|
26411
|
-
* const moved = await Backtest.
|
|
27054
|
+
* const moved = await Backtest.commitBreakeven(
|
|
26412
27055
|
* "BTCUSDT",
|
|
26413
27056
|
* 112,
|
|
26414
27057
|
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" }
|
|
@@ -26416,22 +27059,22 @@ class BacktestUtils {
|
|
|
26416
27059
|
* console.log(moved); // true (SL moved to entry price)
|
|
26417
27060
|
* ```
|
|
26418
27061
|
*/
|
|
26419
|
-
this.
|
|
26420
|
-
backtest$1.loggerService.info(
|
|
27062
|
+
this.commitBreakeven = async (symbol, currentPrice, context) => {
|
|
27063
|
+
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_BREAKEVEN, {
|
|
26421
27064
|
symbol,
|
|
26422
27065
|
currentPrice,
|
|
26423
27066
|
context,
|
|
26424
27067
|
});
|
|
26425
|
-
backtest$1.strategyValidationService.validate(context.strategyName,
|
|
26426
|
-
backtest$1.exchangeValidationService.validate(context.exchangeName,
|
|
27068
|
+
backtest$1.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_BREAKEVEN);
|
|
27069
|
+
backtest$1.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_BREAKEVEN);
|
|
26427
27070
|
{
|
|
26428
27071
|
const { riskName, riskList, actions } = backtest$1.strategySchemaService.get(context.strategyName);
|
|
26429
27072
|
riskName &&
|
|
26430
|
-
backtest$1.riskValidationService.validate(riskName,
|
|
27073
|
+
backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_BREAKEVEN);
|
|
26431
27074
|
riskList &&
|
|
26432
|
-
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName,
|
|
27075
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_BREAKEVEN));
|
|
26433
27076
|
actions &&
|
|
26434
|
-
actions.forEach((actionName) => backtest$1.actionValidationService.validate(actionName,
|
|
27077
|
+
actions.forEach((actionName) => backtest$1.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_BREAKEVEN));
|
|
26435
27078
|
}
|
|
26436
27079
|
return await backtest$1.strategyCoreService.breakeven(true, symbol, currentPrice, context);
|
|
26437
27080
|
};
|
|
@@ -26603,11 +27246,12 @@ const LIVE_METHOD_NAME_GET_STATUS = "LiveUtils.getStatus";
|
|
|
26603
27246
|
const LIVE_METHOD_NAME_GET_PENDING_SIGNAL = "LiveUtils.getPendingSignal";
|
|
26604
27247
|
const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
|
|
26605
27248
|
const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
|
|
26606
|
-
const
|
|
26607
|
-
const
|
|
26608
|
-
const
|
|
26609
|
-
const
|
|
26610
|
-
const
|
|
27249
|
+
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
27250
|
+
const LIVE_METHOD_NAME_CANCEL = "LiveUtils.commitCancel";
|
|
27251
|
+
const LIVE_METHOD_NAME_PARTIAL_PROFIT = "LiveUtils.commitPartialProfit";
|
|
27252
|
+
const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.commitPartialLoss";
|
|
27253
|
+
const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.commitTrailingStop";
|
|
27254
|
+
const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.commitTrailingTake";
|
|
26611
27255
|
/**
|
|
26612
27256
|
* Internal task function that runs live trading and handles completion.
|
|
26613
27257
|
* Consumes live trading results and updates instance state flags.
|
|
@@ -27097,14 +27741,14 @@ class LiveUtils {
|
|
|
27097
27741
|
* @example
|
|
27098
27742
|
* ```typescript
|
|
27099
27743
|
* // Cancel scheduled signal in live trading with custom ID
|
|
27100
|
-
* await Live.
|
|
27744
|
+
* await Live.commitCancel("BTCUSDT", "my-strategy", {
|
|
27101
27745
|
* exchangeName: "binance",
|
|
27102
27746
|
* frameName: "",
|
|
27103
27747
|
* strategyName: "my-strategy"
|
|
27104
27748
|
* }, "manual-cancel-001");
|
|
27105
27749
|
* ```
|
|
27106
27750
|
*/
|
|
27107
|
-
this.
|
|
27751
|
+
this.commitCancel = async (symbol, context, cancelId) => {
|
|
27108
27752
|
backtest$1.loggerService.info(LIVE_METHOD_NAME_CANCEL, {
|
|
27109
27753
|
symbol,
|
|
27110
27754
|
context,
|
|
@@ -27143,7 +27787,7 @@ class LiveUtils {
|
|
|
27143
27787
|
* @example
|
|
27144
27788
|
* ```typescript
|
|
27145
27789
|
* // Close 30% of LONG position at profit
|
|
27146
|
-
* const success = await Live.
|
|
27790
|
+
* const success = await Live.commitPartialProfit("BTCUSDT", 30, 45000, {
|
|
27147
27791
|
* exchangeName: "binance",
|
|
27148
27792
|
* strategyName: "my-strategy"
|
|
27149
27793
|
* });
|
|
@@ -27152,7 +27796,7 @@ class LiveUtils {
|
|
|
27152
27796
|
* }
|
|
27153
27797
|
* ```
|
|
27154
27798
|
*/
|
|
27155
|
-
this.
|
|
27799
|
+
this.commitPartialProfit = async (symbol, percentToClose, currentPrice, context) => {
|
|
27156
27800
|
backtest$1.loggerService.info(LIVE_METHOD_NAME_PARTIAL_PROFIT, {
|
|
27157
27801
|
symbol,
|
|
27158
27802
|
percentToClose,
|
|
@@ -27192,7 +27836,7 @@ class LiveUtils {
|
|
|
27192
27836
|
* @example
|
|
27193
27837
|
* ```typescript
|
|
27194
27838
|
* // Close 40% of LONG position at loss
|
|
27195
|
-
* const success = await Live.
|
|
27839
|
+
* const success = await Live.commitPartialLoss("BTCUSDT", 40, 38000, {
|
|
27196
27840
|
* exchangeName: "binance",
|
|
27197
27841
|
* strategyName: "my-strategy"
|
|
27198
27842
|
* });
|
|
@@ -27201,7 +27845,7 @@ class LiveUtils {
|
|
|
27201
27845
|
* }
|
|
27202
27846
|
* ```
|
|
27203
27847
|
*/
|
|
27204
|
-
this.
|
|
27848
|
+
this.commitPartialLoss = async (symbol, percentToClose, currentPrice, context) => {
|
|
27205
27849
|
backtest$1.loggerService.info(LIVE_METHOD_NAME_PARTIAL_LOSS, {
|
|
27206
27850
|
symbol,
|
|
27207
27851
|
percentToClose,
|
|
@@ -27250,22 +27894,22 @@ class LiveUtils {
|
|
|
27250
27894
|
* // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
|
|
27251
27895
|
*
|
|
27252
27896
|
* // First call: tighten by 5%
|
|
27253
|
-
* const success1 = await Live.
|
|
27897
|
+
* const success1 = await Live.commitTrailingStop("BTCUSDT", -5, 102, {
|
|
27254
27898
|
* exchangeName: "binance",
|
|
27255
27899
|
* strategyName: "my-strategy"
|
|
27256
27900
|
* });
|
|
27257
27901
|
* // success1 = true, newDistance = 10% - 5% = 5%, newSL = 95
|
|
27258
27902
|
*
|
|
27259
27903
|
* // Second call: try weaker protection (smaller percentShift)
|
|
27260
|
-
* const success2 = await Live.
|
|
27904
|
+
* const success2 = await Live.commitTrailingStop("BTCUSDT", -3, 102, context);
|
|
27261
27905
|
* // success2 = false (SKIPPED: newSL=97 < 95, worse protection, larger % absorbs smaller)
|
|
27262
27906
|
*
|
|
27263
27907
|
* // Third call: stronger protection (larger percentShift)
|
|
27264
|
-
* const success3 = await Live.
|
|
27908
|
+
* const success3 = await Live.commitTrailingStop("BTCUSDT", -7, 102, context);
|
|
27265
27909
|
* // success3 = true (ACCEPTED: newDistance = 10% - 7% = 3%, newSL = 97 > 95, better protection)
|
|
27266
27910
|
* ```
|
|
27267
27911
|
*/
|
|
27268
|
-
this.
|
|
27912
|
+
this.commitTrailingStop = async (symbol, percentShift, currentPrice, context) => {
|
|
27269
27913
|
backtest$1.loggerService.info(LIVE_METHOD_NAME_TRAILING_STOP, {
|
|
27270
27914
|
symbol,
|
|
27271
27915
|
percentShift,
|
|
@@ -27314,22 +27958,22 @@ class LiveUtils {
|
|
|
27314
27958
|
* // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
|
|
27315
27959
|
*
|
|
27316
27960
|
* // First call: bring TP closer by 3%
|
|
27317
|
-
* const success1 = await Live.
|
|
27961
|
+
* const success1 = await Live.commitTrailingTake("BTCUSDT", -3, 102, {
|
|
27318
27962
|
* exchangeName: "binance",
|
|
27319
27963
|
* strategyName: "my-strategy"
|
|
27320
27964
|
* });
|
|
27321
27965
|
* // success1 = true, newDistance = 10% - 3% = 7%, newTP = 107
|
|
27322
27966
|
*
|
|
27323
27967
|
* // Second call: try to move TP further (less conservative)
|
|
27324
|
-
* const success2 = await Live.
|
|
27968
|
+
* const success2 = await Live.commitTrailingTake("BTCUSDT", 2, 102, context);
|
|
27325
27969
|
* // success2 = false (SKIPPED: newTP=112 > 107, less conservative, larger % absorbs smaller)
|
|
27326
27970
|
*
|
|
27327
27971
|
* // Third call: even more conservative
|
|
27328
|
-
* const success3 = await Live.
|
|
27972
|
+
* const success3 = await Live.commitTrailingTake("BTCUSDT", -5, 102, context);
|
|
27329
27973
|
* // success3 = true (ACCEPTED: newDistance = 10% - 5% = 5%, newTP = 105 < 107, more conservative)
|
|
27330
27974
|
* ```
|
|
27331
27975
|
*/
|
|
27332
|
-
this.
|
|
27976
|
+
this.commitTrailingTake = async (symbol, percentShift, currentPrice, context) => {
|
|
27333
27977
|
backtest$1.loggerService.info(LIVE_METHOD_NAME_TRAILING_PROFIT, {
|
|
27334
27978
|
symbol,
|
|
27335
27979
|
percentShift,
|
|
@@ -27363,7 +28007,7 @@ class LiveUtils {
|
|
|
27363
28007
|
*
|
|
27364
28008
|
* @example
|
|
27365
28009
|
* ```typescript
|
|
27366
|
-
* const moved = await Live.
|
|
28010
|
+
* const moved = await Live.commitBreakeven(
|
|
27367
28011
|
* "BTCUSDT",
|
|
27368
28012
|
* 112,
|
|
27369
28013
|
* { strategyName: "my-strategy", exchangeName: "binance" }
|
|
@@ -27371,19 +28015,19 @@ class LiveUtils {
|
|
|
27371
28015
|
* console.log(moved); // true (SL moved to entry price)
|
|
27372
28016
|
* ```
|
|
27373
28017
|
*/
|
|
27374
|
-
this.
|
|
27375
|
-
backtest$1.loggerService.info(
|
|
28018
|
+
this.commitBreakeven = async (symbol, currentPrice, context) => {
|
|
28019
|
+
backtest$1.loggerService.info(LIVE_METHOD_NAME_BREAKEVEN, {
|
|
27376
28020
|
symbol,
|
|
27377
28021
|
currentPrice,
|
|
27378
28022
|
context,
|
|
27379
28023
|
});
|
|
27380
|
-
backtest$1.strategyValidationService.validate(context.strategyName,
|
|
27381
|
-
backtest$1.exchangeValidationService.validate(context.exchangeName,
|
|
28024
|
+
backtest$1.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_BREAKEVEN);
|
|
28025
|
+
backtest$1.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_BREAKEVEN);
|
|
27382
28026
|
{
|
|
27383
28027
|
const { riskName, riskList, actions } = backtest$1.strategySchemaService.get(context.strategyName);
|
|
27384
|
-
riskName && backtest$1.riskValidationService.validate(riskName,
|
|
27385
|
-
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName,
|
|
27386
|
-
actions && actions.forEach((actionName) => backtest$1.actionValidationService.validate(actionName,
|
|
28028
|
+
riskName && backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_BREAKEVEN);
|
|
28029
|
+
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_BREAKEVEN));
|
|
28030
|
+
actions && actions.forEach((actionName) => backtest$1.actionValidationService.validate(actionName, LIVE_METHOD_NAME_BREAKEVEN));
|
|
27387
28031
|
}
|
|
27388
28032
|
return await backtest$1.strategyCoreService.breakeven(false, symbol, currentPrice, {
|
|
27389
28033
|
strategyName: context.strategyName,
|
|
@@ -29211,10 +29855,15 @@ class ExchangeInstance {
|
|
|
29211
29855
|
const whenTimestamp = when.getTime();
|
|
29212
29856
|
const sinceTimestamp = since.getTime();
|
|
29213
29857
|
const filteredData = allData.filter((candle) => candle.timestamp >= sinceTimestamp && candle.timestamp <= whenTimestamp);
|
|
29214
|
-
|
|
29215
|
-
|
|
29858
|
+
// Apply distinct by timestamp to remove duplicates
|
|
29859
|
+
const uniqueData = Array.from(new Map(filteredData.map((candle) => [candle.timestamp, candle])).values());
|
|
29860
|
+
if (filteredData.length !== uniqueData.length) {
|
|
29861
|
+
backtest$1.loggerService.warn(`ExchangeInstance Removed ${filteredData.length - uniqueData.length} duplicate candles by timestamp`);
|
|
29216
29862
|
}
|
|
29217
|
-
|
|
29863
|
+
if (uniqueData.length < limit) {
|
|
29864
|
+
backtest$1.loggerService.warn(`ExchangeInstance Expected ${limit} candles, got ${uniqueData.length}`);
|
|
29865
|
+
}
|
|
29866
|
+
return uniqueData;
|
|
29218
29867
|
};
|
|
29219
29868
|
/**
|
|
29220
29869
|
* Calculates VWAP (Volume Weighted Average Price) from last N 1m candles.
|
|
@@ -30393,10 +31042,11 @@ const METHOD_NAME_INIT = "ActionBase.init";
|
|
|
30393
31042
|
const METHOD_NAME_EVENT = "ActionBase.event";
|
|
30394
31043
|
const METHOD_NAME_SIGNAL_LIVE = "ActionBase.signalLive";
|
|
30395
31044
|
const METHOD_NAME_SIGNAL_BACKTEST = "ActionBase.signalBacktest";
|
|
30396
|
-
const
|
|
30397
|
-
const
|
|
30398
|
-
const
|
|
30399
|
-
const
|
|
31045
|
+
const METHOD_NAME_BREAKEVEN_AVAILABLE = "ActionBase.breakevenAvailable";
|
|
31046
|
+
const METHOD_NAME_PARTIAL_PROFIT_AVAILABLE = "ActionBase.partialProfitAvailable";
|
|
31047
|
+
const METHOD_NAME_PARTIAL_LOSS_AVAILABLE = "ActionBase.partialLossAvailable";
|
|
31048
|
+
const METHOD_NAME_PING_SCHEDULED = "ActionBase.pingScheduled";
|
|
31049
|
+
const METHOD_NAME_PING_ACTIVE = "ActionBase.pingActive";
|
|
30400
31050
|
const METHOD_NAME_RISK_REJECTION = "ActionBase.riskRejection";
|
|
30401
31051
|
const METHOD_NAME_DISPOSE = "ActionBase.dispose";
|
|
30402
31052
|
const DEFAULT_SOURCE = "default";
|
|
@@ -30427,10 +31077,11 @@ const DEFAULT_SOURCE = "default";
|
|
|
30427
31077
|
* - signal() - Called on every tick/candle (all modes)
|
|
30428
31078
|
* - signalLive() - Called only in live mode
|
|
30429
31079
|
* - signalBacktest() - Called only in backtest mode
|
|
30430
|
-
* -
|
|
30431
|
-
* -
|
|
30432
|
-
* -
|
|
30433
|
-
* -
|
|
31080
|
+
* - breakevenAvailable() - Called when SL moved to entry
|
|
31081
|
+
* - partialProfitAvailable() - Called on profit milestones (10%, 20%, etc.)
|
|
31082
|
+
* - partialLossAvailable() - Called on loss milestones (-10%, -20%, etc.)
|
|
31083
|
+
* - pingScheduled() - Called every minute during scheduled signal monitoring
|
|
31084
|
+
* - pingActive() - Called every minute during active pending signal monitoring
|
|
30434
31085
|
* - riskRejection() - Called when signal rejected by risk management
|
|
30435
31086
|
*
|
|
30436
31087
|
* @example
|
|
@@ -30471,7 +31122,7 @@ const DEFAULT_SOURCE = "default";
|
|
|
30471
31122
|
* }
|
|
30472
31123
|
*
|
|
30473
31124
|
* // Register the action
|
|
30474
|
-
*
|
|
31125
|
+
* addActionSchema({
|
|
30475
31126
|
* actionName: "telegram-notifier",
|
|
30476
31127
|
* handler: TelegramNotifier
|
|
30477
31128
|
* });
|
|
@@ -30513,11 +31164,13 @@ class ActionBase {
|
|
|
30513
31164
|
* @param strategyName - Strategy identifier this action is attached to
|
|
30514
31165
|
* @param frameName - Timeframe identifier this action is attached to
|
|
30515
31166
|
* @param actionName - Action identifier
|
|
31167
|
+
* @param backtest - If running in backtest
|
|
30516
31168
|
*/
|
|
30517
|
-
constructor(strategyName, frameName, actionName) {
|
|
31169
|
+
constructor(strategyName, frameName, actionName, backtest) {
|
|
30518
31170
|
this.strategyName = strategyName;
|
|
30519
31171
|
this.frameName = frameName;
|
|
30520
31172
|
this.actionName = actionName;
|
|
31173
|
+
this.backtest = backtest;
|
|
30521
31174
|
}
|
|
30522
31175
|
/**
|
|
30523
31176
|
* Initializes the action handler.
|
|
@@ -30641,7 +31294,7 @@ class ActionBase {
|
|
|
30641
31294
|
* Called once per signal when price moves far enough to cover fees and slippage.
|
|
30642
31295
|
* Breakeven threshold: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2 + CC_BREAKEVEN_THRESHOLD
|
|
30643
31296
|
*
|
|
30644
|
-
* Triggered by: ActionCoreService.
|
|
31297
|
+
* Triggered by: ActionCoreService.breakevenAvailable() via BreakevenConnectionService
|
|
30645
31298
|
* Source: breakevenSubject.next() in CREATE_COMMIT_BREAKEVEN_FN callback
|
|
30646
31299
|
* Frequency: Once per signal when threshold reached
|
|
30647
31300
|
*
|
|
@@ -30651,7 +31304,7 @@ class ActionBase {
|
|
|
30651
31304
|
*
|
|
30652
31305
|
* @example
|
|
30653
31306
|
* ```typescript
|
|
30654
|
-
* async
|
|
31307
|
+
* async breakevenAvailable(event: BreakevenContract) {
|
|
30655
31308
|
* await this.telegram.send(
|
|
30656
31309
|
* `[${event.strategyName}] Breakeven reached! ` +
|
|
30657
31310
|
* `Signal: ${event.data.side} @ ${event.currentPrice}`
|
|
@@ -30659,8 +31312,8 @@ class ActionBase {
|
|
|
30659
31312
|
* }
|
|
30660
31313
|
* ```
|
|
30661
31314
|
*/
|
|
30662
|
-
|
|
30663
|
-
backtest$1.loggerService.info(
|
|
31315
|
+
breakevenAvailable(event, source = DEFAULT_SOURCE) {
|
|
31316
|
+
backtest$1.loggerService.info(METHOD_NAME_BREAKEVEN_AVAILABLE, {
|
|
30664
31317
|
event,
|
|
30665
31318
|
source,
|
|
30666
31319
|
});
|
|
@@ -30671,7 +31324,7 @@ class ActionBase {
|
|
|
30671
31324
|
* Called once per profit level per signal (deduplicated).
|
|
30672
31325
|
* Use to track profit milestones and adjust position management.
|
|
30673
31326
|
*
|
|
30674
|
-
* Triggered by: ActionCoreService.
|
|
31327
|
+
* Triggered by: ActionCoreService.partialProfitAvailable() via PartialConnectionService
|
|
30675
31328
|
* Source: partialProfitSubject.next() in CREATE_COMMIT_PROFIT_FN callback
|
|
30676
31329
|
* Frequency: Once per profit level per signal
|
|
30677
31330
|
*
|
|
@@ -30681,7 +31334,7 @@ class ActionBase {
|
|
|
30681
31334
|
*
|
|
30682
31335
|
* @example
|
|
30683
31336
|
* ```typescript
|
|
30684
|
-
* async
|
|
31337
|
+
* async partialProfitAvailable(event: PartialProfitContract) {
|
|
30685
31338
|
* await this.telegram.send(
|
|
30686
31339
|
* `[${event.strategyName}] Profit ${event.level}% reached! ` +
|
|
30687
31340
|
* `Current price: ${event.currentPrice}`
|
|
@@ -30690,8 +31343,8 @@ class ActionBase {
|
|
|
30690
31343
|
* }
|
|
30691
31344
|
* ```
|
|
30692
31345
|
*/
|
|
30693
|
-
|
|
30694
|
-
backtest$1.loggerService.info(
|
|
31346
|
+
partialProfitAvailable(event, source = DEFAULT_SOURCE) {
|
|
31347
|
+
backtest$1.loggerService.info(METHOD_NAME_PARTIAL_PROFIT_AVAILABLE, {
|
|
30695
31348
|
event,
|
|
30696
31349
|
source,
|
|
30697
31350
|
});
|
|
@@ -30702,7 +31355,7 @@ class ActionBase {
|
|
|
30702
31355
|
* Called once per loss level per signal (deduplicated).
|
|
30703
31356
|
* Use to track loss milestones and implement risk management actions.
|
|
30704
31357
|
*
|
|
30705
|
-
* Triggered by: ActionCoreService.
|
|
31358
|
+
* Triggered by: ActionCoreService.partialLossAvailable() via PartialConnectionService
|
|
30706
31359
|
* Source: partialLossSubject.next() in CREATE_COMMIT_LOSS_FN callback
|
|
30707
31360
|
* Frequency: Once per loss level per signal
|
|
30708
31361
|
*
|
|
@@ -30712,7 +31365,7 @@ class ActionBase {
|
|
|
30712
31365
|
*
|
|
30713
31366
|
* @example
|
|
30714
31367
|
* ```typescript
|
|
30715
|
-
* async
|
|
31368
|
+
* async partialLossAvailable(event: PartialLossContract) {
|
|
30716
31369
|
* await this.telegram.send(
|
|
30717
31370
|
* `[${event.strategyName}] Loss ${event.level}% reached! ` +
|
|
30718
31371
|
* `Current price: ${event.currentPrice}`
|
|
@@ -30721,37 +31374,66 @@ class ActionBase {
|
|
|
30721
31374
|
* }
|
|
30722
31375
|
* ```
|
|
30723
31376
|
*/
|
|
30724
|
-
|
|
30725
|
-
backtest$1.loggerService.info(
|
|
31377
|
+
partialLossAvailable(event, source = DEFAULT_SOURCE) {
|
|
31378
|
+
backtest$1.loggerService.info(METHOD_NAME_PARTIAL_LOSS_AVAILABLE, {
|
|
30726
31379
|
event,
|
|
30727
31380
|
source,
|
|
30728
31381
|
});
|
|
30729
31382
|
}
|
|
30730
31383
|
/**
|
|
30731
|
-
* Handles ping events during scheduled signal monitoring.
|
|
31384
|
+
* Handles scheduled ping events during scheduled signal monitoring.
|
|
30732
31385
|
*
|
|
30733
31386
|
* Called every minute while a scheduled signal is waiting for activation.
|
|
30734
31387
|
* Use to monitor pending signals and track wait time.
|
|
30735
31388
|
*
|
|
30736
|
-
* Triggered by: ActionCoreService.
|
|
30737
|
-
* Source:
|
|
31389
|
+
* Triggered by: ActionCoreService.pingScheduled() via StrategyConnectionService
|
|
31390
|
+
* Source: schedulePingSubject.next() in CREATE_COMMIT_SCHEDULE_PING_FN callback
|
|
30738
31391
|
* Frequency: Every minute while scheduled signal is waiting
|
|
30739
31392
|
*
|
|
30740
|
-
* Default implementation: Logs ping event.
|
|
31393
|
+
* Default implementation: Logs scheduled ping event.
|
|
30741
31394
|
*
|
|
30742
31395
|
* @param event - Scheduled signal monitoring data with symbol, strategy info, signal data, timestamp
|
|
30743
31396
|
*
|
|
30744
31397
|
* @example
|
|
30745
31398
|
* ```typescript
|
|
30746
|
-
*
|
|
31399
|
+
* pingScheduled(event: SchedulePingContract) {
|
|
30747
31400
|
* const waitTime = Date.now() - event.data.timestampScheduled;
|
|
30748
31401
|
* const waitMinutes = Math.floor(waitTime / 60000);
|
|
30749
31402
|
* console.log(`Scheduled signal waiting ${waitMinutes} minutes`);
|
|
30750
31403
|
* }
|
|
30751
31404
|
* ```
|
|
30752
31405
|
*/
|
|
30753
|
-
|
|
30754
|
-
backtest$1.loggerService.info(
|
|
31406
|
+
pingScheduled(event, source = DEFAULT_SOURCE) {
|
|
31407
|
+
backtest$1.loggerService.info(METHOD_NAME_PING_SCHEDULED, {
|
|
31408
|
+
event,
|
|
31409
|
+
source,
|
|
31410
|
+
});
|
|
31411
|
+
}
|
|
31412
|
+
/**
|
|
31413
|
+
* Handles active ping events during active pending signal monitoring.
|
|
31414
|
+
*
|
|
31415
|
+
* Called every minute while a pending signal is active (position open).
|
|
31416
|
+
* Use to monitor active positions and track lifecycle.
|
|
31417
|
+
*
|
|
31418
|
+
* Triggered by: ActionCoreService.pingActive() via StrategyConnectionService
|
|
31419
|
+
* Source: activePingSubject.next() in CREATE_COMMIT_ACTIVE_PING_FN callback
|
|
31420
|
+
* Frequency: Every minute while pending signal is active
|
|
31421
|
+
*
|
|
31422
|
+
* Default implementation: Logs active ping event.
|
|
31423
|
+
*
|
|
31424
|
+
* @param event - Active pending signal monitoring data with symbol, strategy info, signal data, timestamp
|
|
31425
|
+
*
|
|
31426
|
+
* @example
|
|
31427
|
+
* ```typescript
|
|
31428
|
+
* pingActive(event: ActivePingContract) {
|
|
31429
|
+
* const holdTime = Date.now() - event.data.pendingAt;
|
|
31430
|
+
* const holdMinutes = Math.floor(holdTime / 60000);
|
|
31431
|
+
* console.log(`Active signal holding ${holdMinutes} minutes`);
|
|
31432
|
+
* }
|
|
31433
|
+
* ```
|
|
31434
|
+
*/
|
|
31435
|
+
pingActive(event, source = DEFAULT_SOURCE) {
|
|
31436
|
+
backtest$1.loggerService.info(METHOD_NAME_PING_ACTIVE, {
|
|
30755
31437
|
event,
|
|
30756
31438
|
source,
|
|
30757
31439
|
});
|
|
@@ -30870,4 +31552,4 @@ const set = (object, path, value) => {
|
|
|
30870
31552
|
}
|
|
30871
31553
|
};
|
|
30872
31554
|
|
|
30873
|
-
export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, Optimizer, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Walker,
|
|
31555
|
+
export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, Optimizer, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addOptimizerSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, commitBreakeven, commitCancel, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, dumpSignalData, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getOptimizerSchema, getOrderBook, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listOptimizerSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideOptimizerSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, roundTicks, set, setColumns, setConfig, setLogger, stop, validate };
|