backtest-kit 12.8.0 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +1997 -1997
- package/build/index.cjs +368 -69
- package/build/index.mjs +367 -70
- package/package.json +86 -86
- package/types.d.ts +205 -1
package/build/index.mjs
CHANGED
|
@@ -981,6 +981,12 @@ const PERSIST_SCHEDULE_UTILS_METHOD_NAME_WRITE_DATA = "PersistScheduleUtils.writ
|
|
|
981
981
|
const PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_JSON = "PersistScheduleUtils.useJson";
|
|
982
982
|
const PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_DUMMY = "PersistScheduleUtils.useDummy";
|
|
983
983
|
const PERSIST_SCHEDULE_UTILS_METHOD_NAME_CLEAR = "PersistScheduleUtils.clear";
|
|
984
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_PERSIST_STRATEGY_ADAPTER = "PersistStrategyUtils.usePersistStrategyAdapter";
|
|
985
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_READ_DATA = "PersistStrategyUtils.readStrategyData";
|
|
986
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_WRITE_DATA = "PersistStrategyUtils.writeStrategyData";
|
|
987
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_JSON = "PersistStrategyUtils.useJson";
|
|
988
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_DUMMY = "PersistStrategyUtils.useDummy";
|
|
989
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_CLEAR = "PersistStrategyUtils.clear";
|
|
984
990
|
const PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_PERSIST_PARTIAL_ADAPTER = "PersistPartialUtils.usePersistPartialAdapter";
|
|
985
991
|
const PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA = "PersistPartialUtils.readPartialData";
|
|
986
992
|
const PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistPartialUtils.writePartialData";
|
|
@@ -1844,6 +1850,212 @@ class PersistScheduleUtils {
|
|
|
1844
1850
|
* ```
|
|
1845
1851
|
*/
|
|
1846
1852
|
const PersistScheduleAdapter = new PersistScheduleUtils();
|
|
1853
|
+
/**
|
|
1854
|
+
* Default file-based implementation of IPersistStrategyInstance.
|
|
1855
|
+
*
|
|
1856
|
+
* Features:
|
|
1857
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
1858
|
+
* - Uses fixed entity ID "strategy" within a per-context PersistBase
|
|
1859
|
+
* - Crash-safe via atomic writes
|
|
1860
|
+
*
|
|
1861
|
+
* @example
|
|
1862
|
+
* ```typescript
|
|
1863
|
+
* const instance = new PersistStrategyInstance("BTCUSDT", "my-strategy", "binance");
|
|
1864
|
+
* await instance.waitForInit(true);
|
|
1865
|
+
* await instance.writeStrategyData(snapshot);
|
|
1866
|
+
* const restored = await instance.readStrategyData();
|
|
1867
|
+
* ```
|
|
1868
|
+
*/
|
|
1869
|
+
class PersistStrategyInstance {
|
|
1870
|
+
/**
|
|
1871
|
+
* Creates new deferred strategy state persistence instance.
|
|
1872
|
+
*
|
|
1873
|
+
* @param symbol - Trading pair symbol
|
|
1874
|
+
* @param strategyName - Strategy identifier
|
|
1875
|
+
* @param exchangeName - Exchange identifier
|
|
1876
|
+
*/
|
|
1877
|
+
constructor(symbol, strategyName, exchangeName) {
|
|
1878
|
+
this.symbol = symbol;
|
|
1879
|
+
this.strategyName = strategyName;
|
|
1880
|
+
this.exchangeName = exchangeName;
|
|
1881
|
+
this._storage = new PersistBase(`${symbol}_${strategyName}_${exchangeName}`, `./dump/data/strategy/`);
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Initializes the underlying PersistBase storage.
|
|
1885
|
+
*
|
|
1886
|
+
* @param initial - Whether this is the first initialization
|
|
1887
|
+
* @returns Promise that resolves when initialization is complete
|
|
1888
|
+
*/
|
|
1889
|
+
async waitForInit(initial) {
|
|
1890
|
+
await this._storage.waitForInit(initial);
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Reads the persisted strategy state snapshot using the fixed STORAGE_KEY.
|
|
1894
|
+
*
|
|
1895
|
+
* @returns Promise resolving to strategy state snapshot or null if not found
|
|
1896
|
+
*/
|
|
1897
|
+
async readStrategyData() {
|
|
1898
|
+
if (await this._storage.hasValue(PersistStrategyInstance.STORAGE_KEY)) {
|
|
1899
|
+
const strategyData = await this._storage.readValue(PersistStrategyInstance.STORAGE_KEY);
|
|
1900
|
+
// JSON serializes Infinity as null, so an eternal-hold signal
|
|
1901
|
+
// (minuteEstimatedTime: Infinity) reads back as null — restore it.
|
|
1902
|
+
for (const signal of [strategyData.closedSignal, strategyData.cancelledSignal, strategyData.activatedSignal]) {
|
|
1903
|
+
if (signal && signal.minuteEstimatedTime == null) {
|
|
1904
|
+
signal.minuteEstimatedTime = Infinity;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
return strategyData;
|
|
1908
|
+
}
|
|
1909
|
+
return null;
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Writes the strategy state snapshot (or null to clear) using the fixed STORAGE_KEY.
|
|
1913
|
+
*
|
|
1914
|
+
* @param row - Strategy state snapshot to persist, or null to clear
|
|
1915
|
+
* @returns Promise that resolves when write is complete
|
|
1916
|
+
*/
|
|
1917
|
+
async writeStrategyData(row) {
|
|
1918
|
+
await this._storage.writeValue(PersistStrategyInstance.STORAGE_KEY, row);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
/** Fixed entity key for storing the strategy state snapshot */
|
|
1922
|
+
PersistStrategyInstance.STORAGE_KEY = "strategy";
|
|
1923
|
+
/**
|
|
1924
|
+
* No-op IPersistStrategyInstance implementation used by PersistStrategyUtils.useDummy().
|
|
1925
|
+
* All reads return null, all writes are discarded.
|
|
1926
|
+
*/
|
|
1927
|
+
class PersistStrategyDummyInstance {
|
|
1928
|
+
/**
|
|
1929
|
+
* No-op constructor.
|
|
1930
|
+
* Context arguments are accepted to satisfy TPersistStrategyInstanceCtor.
|
|
1931
|
+
*/
|
|
1932
|
+
constructor(_symbol, _strategyName, _exchangeName) { }
|
|
1933
|
+
/**
|
|
1934
|
+
* No-op initialization.
|
|
1935
|
+
* @returns Promise that resolves immediately
|
|
1936
|
+
*/
|
|
1937
|
+
async waitForInit(_initial) { }
|
|
1938
|
+
/**
|
|
1939
|
+
* Always returns null (no persisted strategy state).
|
|
1940
|
+
* @returns Promise resolving to null
|
|
1941
|
+
*/
|
|
1942
|
+
async readStrategyData() { return null; }
|
|
1943
|
+
/**
|
|
1944
|
+
* No-op write (discards strategy state).
|
|
1945
|
+
* @returns Promise that resolves immediately
|
|
1946
|
+
*/
|
|
1947
|
+
async writeStrategyData(_row) { }
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Utility class for managing deferred strategy state persistence.
|
|
1951
|
+
*
|
|
1952
|
+
* Features:
|
|
1953
|
+
* - Memoized storage instances per strategy
|
|
1954
|
+
* - Custom adapter support
|
|
1955
|
+
* - Atomic read/write operations for the deferred state snapshot
|
|
1956
|
+
* - Crash-safe in-flight broker operation state management
|
|
1957
|
+
*
|
|
1958
|
+
* Used by ClientStrategy for live mode persistence of the commit queue and deferred
|
|
1959
|
+
* user actions (_commitQueue, _closedSignal, _cancelledSignal, _activatedSignal).
|
|
1960
|
+
*/
|
|
1961
|
+
class PersistStrategyUtils {
|
|
1962
|
+
constructor() {
|
|
1963
|
+
/**
|
|
1964
|
+
* Constructor used to create per-context strategy state instances.
|
|
1965
|
+
* Replaceable via usePersistStrategyAdapter() / useJson() / useDummy().
|
|
1966
|
+
*/
|
|
1967
|
+
this.PersistStrategyInstanceCtor = PersistStrategyInstance;
|
|
1968
|
+
/**
|
|
1969
|
+
* Memoized factory creating one IPersistStrategyInstance per (symbol, strategy, exchange) triple.
|
|
1970
|
+
*/
|
|
1971
|
+
this.getStrategyStorage = memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.PersistStrategyInstanceCtor, [symbol, strategyName, exchangeName]));
|
|
1972
|
+
/**
|
|
1973
|
+
* Reads persisted deferred strategy state for the given context.
|
|
1974
|
+
* Lazily initializes the instance on first access.
|
|
1975
|
+
*
|
|
1976
|
+
* @param symbol - Trading pair symbol
|
|
1977
|
+
* @param strategyName - Strategy identifier
|
|
1978
|
+
* @param exchangeName - Exchange identifier
|
|
1979
|
+
* @returns Promise resolving to strategy state snapshot or null if none persisted
|
|
1980
|
+
*/
|
|
1981
|
+
this.readStrategyData = async (symbol, strategyName, exchangeName) => {
|
|
1982
|
+
LOGGER_SERVICE$9.info(PERSIST_STRATEGY_UTILS_METHOD_NAME_READ_DATA);
|
|
1983
|
+
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1984
|
+
const isInitial = !this.getStrategyStorage.has(key);
|
|
1985
|
+
const instance = this.getStrategyStorage(symbol, strategyName, exchangeName);
|
|
1986
|
+
await instance.waitForInit(isInitial);
|
|
1987
|
+
return instance.readStrategyData();
|
|
1988
|
+
};
|
|
1989
|
+
/**
|
|
1990
|
+
* Writes deferred strategy state (or null to clear) for the given context.
|
|
1991
|
+
* Lazily initializes the instance on first access.
|
|
1992
|
+
*
|
|
1993
|
+
* @param strategyRow - Strategy state snapshot to persist, or null to clear
|
|
1994
|
+
* @param symbol - Trading pair symbol
|
|
1995
|
+
* @param strategyName - Strategy identifier
|
|
1996
|
+
* @param exchangeName - Exchange identifier
|
|
1997
|
+
* @returns Promise that resolves when write is complete
|
|
1998
|
+
*/
|
|
1999
|
+
this.writeStrategyData = async (strategyRow, symbol, strategyName, exchangeName) => {
|
|
2000
|
+
LOGGER_SERVICE$9.info(PERSIST_STRATEGY_UTILS_METHOD_NAME_WRITE_DATA);
|
|
2001
|
+
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
2002
|
+
const isInitial = !this.getStrategyStorage.has(key);
|
|
2003
|
+
const instance = this.getStrategyStorage(symbol, strategyName, exchangeName);
|
|
2004
|
+
await instance.waitForInit(isInitial);
|
|
2005
|
+
return instance.writeStrategyData(strategyRow);
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Registers a custom IPersistStrategyInstance constructor.
|
|
2010
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
2011
|
+
*
|
|
2012
|
+
* @param Ctor - Custom IPersistStrategyInstance constructor
|
|
2013
|
+
*/
|
|
2014
|
+
usePersistStrategyAdapter(Ctor) {
|
|
2015
|
+
LOGGER_SERVICE$9.info(PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_PERSIST_STRATEGY_ADAPTER);
|
|
2016
|
+
this.PersistStrategyInstanceCtor = Ctor;
|
|
2017
|
+
this.getStrategyStorage.clear();
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Clears the memoized instance cache.
|
|
2021
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
2022
|
+
*/
|
|
2023
|
+
clear() {
|
|
2024
|
+
LOGGER_SERVICE$9.log(PERSIST_STRATEGY_UTILS_METHOD_NAME_CLEAR);
|
|
2025
|
+
this.getStrategyStorage.clear();
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* Switches to the default file-based PersistStrategyInstance.
|
|
2029
|
+
*/
|
|
2030
|
+
useJson() {
|
|
2031
|
+
LOGGER_SERVICE$9.log(PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_JSON);
|
|
2032
|
+
this.usePersistStrategyAdapter(PersistStrategyInstance);
|
|
2033
|
+
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Switches to PersistStrategyDummyInstance (all operations are no-ops).
|
|
2036
|
+
*/
|
|
2037
|
+
useDummy() {
|
|
2038
|
+
LOGGER_SERVICE$9.log(PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2039
|
+
this.usePersistStrategyAdapter(PersistStrategyDummyInstance);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Global singleton instance of PersistStrategyUtils.
|
|
2044
|
+
* Used by ClientStrategy for deferred strategy state persistence.
|
|
2045
|
+
*
|
|
2046
|
+
* @example
|
|
2047
|
+
* ```typescript
|
|
2048
|
+
* // Custom adapter
|
|
2049
|
+
* PersistStrategyAdapter.usePersistStrategyAdapter(RedisPersist);
|
|
2050
|
+
*
|
|
2051
|
+
* // Read strategy state
|
|
2052
|
+
* const state = await PersistStrategyAdapter.readStrategyData("BTCUSDT", "my-strategy", "binance");
|
|
2053
|
+
*
|
|
2054
|
+
* // Write strategy state
|
|
2055
|
+
* await PersistStrategyAdapter.writeStrategyData(state, "BTCUSDT", "my-strategy", "binance");
|
|
2056
|
+
* ```
|
|
2057
|
+
*/
|
|
2058
|
+
const PersistStrategyAdapter = new PersistStrategyUtils();
|
|
1847
2059
|
/**
|
|
1848
2060
|
* Default file-based implementation of IPersistPartialInstance.
|
|
1849
2061
|
*
|
|
@@ -6771,6 +6983,9 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
6771
6983
|
{
|
|
6772
6984
|
self._commitQueue = [];
|
|
6773
6985
|
}
|
|
6986
|
+
// Persist the now-empty queue so a crash after draining does not replay commits
|
|
6987
|
+
// that were already forwarded to the broker on the next restart.
|
|
6988
|
+
await PERSIST_STRATEGY_FN(self);
|
|
6774
6989
|
if (!self._pendingSignal) {
|
|
6775
6990
|
return;
|
|
6776
6991
|
}
|
|
@@ -7227,6 +7442,19 @@ const WAIT_FOR_INIT_FN$4 = async (self) => {
|
|
|
7227
7442
|
self._lastPendingId = recentSignal.id;
|
|
7228
7443
|
}
|
|
7229
7444
|
}
|
|
7445
|
+
// Restore deferred strategy state (commit queue + deferred user actions) so any
|
|
7446
|
+
// confirmed-but-not-yet-forwarded broker operation survives a live crash and is
|
|
7447
|
+
// re-drained on the next tick. Placed before the pending/scheduled restore which
|
|
7448
|
+
// may early-return on exchange/strategy mismatch.
|
|
7449
|
+
{
|
|
7450
|
+
const strategyData = await PersistStrategyAdapter.readStrategyData(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
7451
|
+
if (strategyData) {
|
|
7452
|
+
self._commitQueue = strategyData.commitQueue ?? [];
|
|
7453
|
+
self._closedSignal = strategyData.closedSignal;
|
|
7454
|
+
self._cancelledSignal = strategyData.cancelledSignal;
|
|
7455
|
+
self._activatedSignal = strategyData.activatedSignal;
|
|
7456
|
+
}
|
|
7457
|
+
}
|
|
7230
7458
|
// Restore pending signal
|
|
7231
7459
|
const pendingSignal = await PersistSignalAdapter.readSignalData(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
7232
7460
|
if (pendingSignal) {
|
|
@@ -7276,6 +7504,28 @@ const WAIT_FOR_DISPOSE_FN$1 = async (self) => {
|
|
|
7276
7504
|
self.params.logger.debug("ClientStrategy dispose");
|
|
7277
7505
|
await self.params.onDispose(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName, self.params.method.context.frameName, self.params.execution.context.backtest);
|
|
7278
7506
|
};
|
|
7507
|
+
/**
|
|
7508
|
+
* Persists the deferred strategy state snapshot (commit queue + deferred user actions)
|
|
7509
|
+
* to disk in live mode.
|
|
7510
|
+
*
|
|
7511
|
+
* These fields carry confirmed-but-not-yet-forwarded broker operations and survive the
|
|
7512
|
+
* gap between ticks (drained at the start of the next tick). Without persistence a live
|
|
7513
|
+
* crash in that window silently loses the pending broker operation while _pendingSignal
|
|
7514
|
+
* (already mutated and saved) claims it happened. Skipped in backtest mode.
|
|
7515
|
+
*
|
|
7516
|
+
* @param self - ClientStrategy instance
|
|
7517
|
+
*/
|
|
7518
|
+
const PERSIST_STRATEGY_FN = async (self) => {
|
|
7519
|
+
if (self.params.backtest) {
|
|
7520
|
+
return;
|
|
7521
|
+
}
|
|
7522
|
+
await PersistStrategyAdapter.writeStrategyData({
|
|
7523
|
+
commitQueue: self._commitQueue,
|
|
7524
|
+
closedSignal: self._closedSignal,
|
|
7525
|
+
cancelledSignal: self._cancelledSignal,
|
|
7526
|
+
activatedSignal: self._activatedSignal,
|
|
7527
|
+
}, self.params.symbol, self.params.strategyName, self.params.exchangeName);
|
|
7528
|
+
};
|
|
7279
7529
|
const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
|
|
7280
7530
|
// Initialize partial array if not present
|
|
7281
7531
|
if (!signal._partial)
|
|
@@ -8900,14 +9150,15 @@ const CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, signal, averagePrice, c
|
|
|
8900
9150
|
const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, averagePrice, closeTimestamp) => {
|
|
8901
9151
|
const syncCloseAllowed = await CALL_SIGNAL_SYNC_CLOSE_FN(closeTimestamp, averagePrice, "closed", closedSignal, self);
|
|
8902
9152
|
if (!syncCloseAllowed) {
|
|
8903
|
-
|
|
9153
|
+
// Sync close rejected (e.g. broker rejected the order) — keep _closedSignal intact
|
|
9154
|
+
// and return null so the candle loop re-attempts on the next candle. Mirrors live
|
|
9155
|
+
// tick, which keeps _closedSignal and returns idle on a rejected user close,
|
|
9156
|
+
// re-trying on the following tick.
|
|
9157
|
+
self.params.logger.info("ClientStrategy backtest: user-closed signal rejected by sync, will retry on next candle", {
|
|
8904
9158
|
symbol: self.params.execution.context.symbol,
|
|
8905
9159
|
signalId: closedSignal.id,
|
|
8906
9160
|
});
|
|
8907
|
-
|
|
8908
|
-
self._pendingSignal = closedSignal;
|
|
8909
|
-
throw new Error(`ClientStrategy backtest: signal close rejected by sync (signalId=${closedSignal.id}). ` +
|
|
8910
|
-
`Retry backtest() with new candle data.`);
|
|
9161
|
+
return null;
|
|
8911
9162
|
}
|
|
8912
9163
|
self._closedSignal = null;
|
|
8913
9164
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", closedSignal, averagePrice);
|
|
@@ -9159,7 +9410,15 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles, frameEnd
|
|
|
9159
9410
|
}
|
|
9160
9411
|
// КРИТИЧНО: Проверяем был ли сигнал закрыт пользователем через closePending()
|
|
9161
9412
|
if (self._closedSignal) {
|
|
9162
|
-
|
|
9413
|
+
const userCloseResult = await CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN(self, self._closedSignal, averagePrice, currentCandleTimestamp);
|
|
9414
|
+
// Sync close accepted — position closed, return. If rejected, _closedSignal is
|
|
9415
|
+
// kept and we skip this candle: live tick returns idle on a rejected user close
|
|
9416
|
+
// (it does NOT fall into the TP/SL or active-monitoring path), so the candle is
|
|
9417
|
+
// skipped here and the close is re-attempted on the next candle.
|
|
9418
|
+
if (userCloseResult) {
|
|
9419
|
+
return userCloseResult;
|
|
9420
|
+
}
|
|
9421
|
+
continue;
|
|
9163
9422
|
}
|
|
9164
9423
|
let shouldClose = false;
|
|
9165
9424
|
let closeReason;
|
|
@@ -9210,7 +9469,19 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles, frameEnd
|
|
|
9210
9469
|
else {
|
|
9211
9470
|
closePrice = averagePrice; // time_expired uses VWAP
|
|
9212
9471
|
}
|
|
9213
|
-
|
|
9472
|
+
const closeResult = await CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN(self, signal, closePrice, closeReason, currentCandleTimestamp);
|
|
9473
|
+
// Sync close accepted — position closed, return.
|
|
9474
|
+
if (closeResult) {
|
|
9475
|
+
return closeResult;
|
|
9476
|
+
}
|
|
9477
|
+
// Sync close rejected (e.g. broker rejected the order) — _pendingSignal is left
|
|
9478
|
+
// intact (not cleared on rejection). Do NOT return/continue: fall through to the
|
|
9479
|
+
// active-monitoring block below so this candle is processed exactly like live
|
|
9480
|
+
// tick, which runs RETURN_PENDING_SIGNAL_ACTIVE_FN when CLOSE_PENDING_SIGNAL_FN
|
|
9481
|
+
// returns null (updates _peak/_fall, fires active ping / breakeven / partial
|
|
9482
|
+
// callbacks, drains the commit queue). The close is re-attempted on the next
|
|
9483
|
+
// candle; for time_expired this eventually reaches the loop-exhausted close
|
|
9484
|
+
// (which throws if still rejected).
|
|
9214
9485
|
}
|
|
9215
9486
|
// Call onPartialProfit/onPartialLoss callbacks during backtest candle processing
|
|
9216
9487
|
// Calculate percentage of path to TP/SL
|
|
@@ -9632,7 +9903,7 @@ class ClientStrategy {
|
|
|
9632
9903
|
async getStopped(symbol) {
|
|
9633
9904
|
this.params.logger.debug("ClientStrategy getStopped", {
|
|
9634
9905
|
symbol,
|
|
9635
|
-
strategyName: this.params.
|
|
9906
|
+
strategyName: this.params.strategyName,
|
|
9636
9907
|
});
|
|
9637
9908
|
return this._isStopped;
|
|
9638
9909
|
}
|
|
@@ -10315,6 +10586,8 @@ class ClientStrategy {
|
|
|
10315
10586
|
if (this._cancelledSignal) {
|
|
10316
10587
|
const cancelledSignal = this._cancelledSignal;
|
|
10317
10588
|
this._cancelledSignal = null; // Clear after emitting
|
|
10589
|
+
// Persist the cleared deferred state so the drained flag is not replayed on restart
|
|
10590
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10318
10591
|
this.params.logger.info("ClientStrategy tick: scheduled signal was cancelled", {
|
|
10319
10592
|
symbol: this.params.execution.context.symbol,
|
|
10320
10593
|
signalId: cancelledSignal.id,
|
|
@@ -10373,6 +10646,8 @@ class ClientStrategy {
|
|
|
10373
10646
|
return await RETURN_IDLE_FN(this, currentPrice);
|
|
10374
10647
|
}
|
|
10375
10648
|
this._closedSignal = null; // Clear only after sync confirmed
|
|
10649
|
+
// Persist the cleared deferred state so the drained flag is not replayed on restart
|
|
10650
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10376
10651
|
this.params.logger.info("ClientStrategy tick: pending signal was closed", {
|
|
10377
10652
|
symbol: this.params.execution.context.symbol,
|
|
10378
10653
|
signalId: closedSignal.id,
|
|
@@ -10428,6 +10703,8 @@ class ClientStrategy {
|
|
|
10428
10703
|
const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
|
|
10429
10704
|
const activatedSignal = this._activatedSignal;
|
|
10430
10705
|
this._activatedSignal = null; // Clear after emitting
|
|
10706
|
+
// Persist the cleared deferred state so the drained flag is not replayed on restart
|
|
10707
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10431
10708
|
this.params.logger.info("ClientStrategy tick: scheduled signal was activated", {
|
|
10432
10709
|
symbol: this.params.execution.context.symbol,
|
|
10433
10710
|
signalId: activatedSignal.id,
|
|
@@ -10685,18 +10962,20 @@ class ClientStrategy {
|
|
|
10685
10962
|
const currentPrice = await this.params.exchange.getAveragePrice(symbol);
|
|
10686
10963
|
const closedSignal = this._closedSignal;
|
|
10687
10964
|
const closeTimestamp = this.params.execution.context.when.getTime();
|
|
10688
|
-
// Sync close: if external system rejects —
|
|
10965
|
+
// Sync close: if external system rejects — keep the close pending and re-attempt
|
|
10966
|
+
// it inside the candle loop below (PROCESS_PENDING_SIGNAL_CANDLES_FN handles
|
|
10967
|
+
// _closedSignal per candle). Mirrors live tick, which keeps _closedSignal and
|
|
10968
|
+
// retries on the next tick instead of failing.
|
|
10689
10969
|
const syncCloseAllowed = await CALL_SIGNAL_SYNC_CLOSE_FN(closeTimestamp, currentPrice, "closed", closedSignal, this);
|
|
10690
10970
|
if (!syncCloseAllowed) {
|
|
10691
|
-
this.params.logger.info("ClientStrategy backtest: user-closed signal rejected by sync, will retry", {
|
|
10971
|
+
this.params.logger.info("ClientStrategy backtest: user-closed signal rejected by sync, will retry in candle loop", {
|
|
10692
10972
|
symbol: this.params.execution.context.symbol,
|
|
10693
10973
|
signalId: closedSignal.id,
|
|
10694
10974
|
});
|
|
10695
|
-
// Restore _pendingSignal so
|
|
10696
|
-
|
|
10975
|
+
// Restore _pendingSignal so the candle loop processes the position normally;
|
|
10976
|
+
// _closedSignal is kept so the loop re-attempts the close on each candle.
|
|
10697
10977
|
this._pendingSignal = closedSignal;
|
|
10698
|
-
|
|
10699
|
-
`Retry backtest() with new candle data.`);
|
|
10978
|
+
return await PROCESS_PENDING_SIGNAL_CANDLES_FN(this, this._pendingSignal, candles, frameEndTime);
|
|
10700
10979
|
}
|
|
10701
10980
|
this._closedSignal = null; // Clear only after sync confirmed
|
|
10702
10981
|
// Emit commit with correct timestamp from backtest context
|
|
@@ -10878,7 +11157,7 @@ class ClientStrategy {
|
|
|
10878
11157
|
if (backtest) {
|
|
10879
11158
|
return;
|
|
10880
11159
|
}
|
|
10881
|
-
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.
|
|
11160
|
+
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
10882
11161
|
}
|
|
10883
11162
|
/**
|
|
10884
11163
|
* Cancels the scheduled signal without stopping the strategy.
|
|
@@ -10922,7 +11201,9 @@ class ClientStrategy {
|
|
|
10922
11201
|
// Commit will be emitted in backtest() with correct candle timestamp
|
|
10923
11202
|
return;
|
|
10924
11203
|
}
|
|
10925
|
-
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.
|
|
11204
|
+
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
11205
|
+
// Persist deferred _cancelledSignal so a crash before the next tick does not lose it
|
|
11206
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10926
11207
|
// Commit will be emitted in tick() with correct currentTime
|
|
10927
11208
|
}
|
|
10928
11209
|
/**
|
|
@@ -10973,7 +11254,9 @@ class ClientStrategy {
|
|
|
10973
11254
|
// Commit will be emitted AFTER successful risk check in PROCESS_SCHEDULED_SIGNAL_CANDLES_FN
|
|
10974
11255
|
return;
|
|
10975
11256
|
}
|
|
10976
|
-
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.
|
|
11257
|
+
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
11258
|
+
// Persist deferred _activatedSignal so a crash before the next tick does not lose it
|
|
11259
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10977
11260
|
// Commit will be emitted AFTER successful risk check in tick()
|
|
10978
11261
|
}
|
|
10979
11262
|
/**
|
|
@@ -11018,6 +11301,8 @@ class ClientStrategy {
|
|
|
11018
11301
|
return;
|
|
11019
11302
|
}
|
|
11020
11303
|
await PersistSignalAdapter.writeSignalData(this._pendingSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
11304
|
+
// Persist deferred _closedSignal so a crash before the next tick does not lose it
|
|
11305
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11021
11306
|
// Commit will be emitted in tick() with correct currentTime
|
|
11022
11307
|
}
|
|
11023
11308
|
/**
|
|
@@ -11188,10 +11473,10 @@ class ClientStrategy {
|
|
|
11188
11473
|
});
|
|
11189
11474
|
// Call onWrite callback for testing persist storage
|
|
11190
11475
|
if (this.params.callbacks?.onWrite) {
|
|
11191
|
-
this.params.callbacks.onWrite(this.params.
|
|
11476
|
+
this.params.callbacks.onWrite(this.params.symbol, TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice), backtest);
|
|
11192
11477
|
}
|
|
11193
11478
|
if (!backtest) {
|
|
11194
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
11479
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11195
11480
|
}
|
|
11196
11481
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11197
11482
|
this._commitQueue.push({
|
|
@@ -11201,6 +11486,8 @@ class ClientStrategy {
|
|
|
11201
11486
|
percentToClose,
|
|
11202
11487
|
currentPrice,
|
|
11203
11488
|
});
|
|
11489
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
11490
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11204
11491
|
return true;
|
|
11205
11492
|
}
|
|
11206
11493
|
/**
|
|
@@ -11371,10 +11658,10 @@ class ClientStrategy {
|
|
|
11371
11658
|
});
|
|
11372
11659
|
// Call onWrite callback for testing persist storage
|
|
11373
11660
|
if (this.params.callbacks?.onWrite) {
|
|
11374
|
-
this.params.callbacks.onWrite(this.params.
|
|
11661
|
+
this.params.callbacks.onWrite(this.params.symbol, TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice), backtest);
|
|
11375
11662
|
}
|
|
11376
11663
|
if (!backtest) {
|
|
11377
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
11664
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11378
11665
|
}
|
|
11379
11666
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11380
11667
|
this._commitQueue.push({
|
|
@@ -11384,6 +11671,8 @@ class ClientStrategy {
|
|
|
11384
11671
|
percentToClose,
|
|
11385
11672
|
currentPrice,
|
|
11386
11673
|
});
|
|
11674
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
11675
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11387
11676
|
return true;
|
|
11388
11677
|
}
|
|
11389
11678
|
/**
|
|
@@ -11570,10 +11859,10 @@ class ClientStrategy {
|
|
|
11570
11859
|
// Call onWrite callback for testing persist storage
|
|
11571
11860
|
if (this.params.callbacks?.onWrite) {
|
|
11572
11861
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice);
|
|
11573
|
-
this.params.callbacks.onWrite(this.params.
|
|
11862
|
+
this.params.callbacks.onWrite(this.params.symbol, publicSignal, backtest);
|
|
11574
11863
|
}
|
|
11575
11864
|
if (!backtest) {
|
|
11576
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
11865
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11577
11866
|
}
|
|
11578
11867
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11579
11868
|
this._commitQueue.push({
|
|
@@ -11582,6 +11871,8 @@ class ClientStrategy {
|
|
|
11582
11871
|
backtest,
|
|
11583
11872
|
currentPrice,
|
|
11584
11873
|
});
|
|
11874
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
11875
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11585
11876
|
return true;
|
|
11586
11877
|
}
|
|
11587
11878
|
/**
|
|
@@ -11819,10 +12110,10 @@ class ClientStrategy {
|
|
|
11819
12110
|
// Call onWrite callback for testing persist storage
|
|
11820
12111
|
if (this.params.callbacks?.onWrite) {
|
|
11821
12112
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice);
|
|
11822
|
-
this.params.callbacks.onWrite(this.params.
|
|
12113
|
+
this.params.callbacks.onWrite(this.params.symbol, publicSignal, backtest);
|
|
11823
12114
|
}
|
|
11824
12115
|
if (!backtest) {
|
|
11825
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
12116
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11826
12117
|
}
|
|
11827
12118
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11828
12119
|
this._commitQueue.push({
|
|
@@ -11832,6 +12123,8 @@ class ClientStrategy {
|
|
|
11832
12123
|
percentShift,
|
|
11833
12124
|
currentPrice,
|
|
11834
12125
|
});
|
|
12126
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
12127
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11835
12128
|
return true;
|
|
11836
12129
|
}
|
|
11837
12130
|
/**
|
|
@@ -12055,10 +12348,10 @@ class ClientStrategy {
|
|
|
12055
12348
|
// Call onWrite callback for testing persist storage
|
|
12056
12349
|
if (this.params.callbacks?.onWrite) {
|
|
12057
12350
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice);
|
|
12058
|
-
this.params.callbacks.onWrite(this.params.
|
|
12351
|
+
this.params.callbacks.onWrite(this.params.symbol, publicSignal, backtest);
|
|
12059
12352
|
}
|
|
12060
12353
|
if (!backtest) {
|
|
12061
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
12354
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
12062
12355
|
}
|
|
12063
12356
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
12064
12357
|
this._commitQueue.push({
|
|
@@ -12068,6 +12361,8 @@ class ClientStrategy {
|
|
|
12068
12361
|
percentShift,
|
|
12069
12362
|
currentPrice,
|
|
12070
12363
|
});
|
|
12364
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
12365
|
+
await PERSIST_STRATEGY_FN(this);
|
|
12071
12366
|
return true;
|
|
12072
12367
|
}
|
|
12073
12368
|
/**
|
|
@@ -12146,10 +12441,10 @@ class ClientStrategy {
|
|
|
12146
12441
|
});
|
|
12147
12442
|
// Call onWrite callback for testing persist storage
|
|
12148
12443
|
if (this.params.callbacks?.onWrite) {
|
|
12149
|
-
this.params.callbacks.onWrite(this.params.
|
|
12444
|
+
this.params.callbacks.onWrite(this.params.symbol, TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice), backtest);
|
|
12150
12445
|
}
|
|
12151
12446
|
if (!backtest) {
|
|
12152
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
12447
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
12153
12448
|
}
|
|
12154
12449
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
12155
12450
|
this._commitQueue.push({
|
|
@@ -12160,6 +12455,8 @@ class ClientStrategy {
|
|
|
12160
12455
|
cost,
|
|
12161
12456
|
totalEntries: this._pendingSignal._entry?.length ?? 1,
|
|
12162
12457
|
});
|
|
12458
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
12459
|
+
await PERSIST_STRATEGY_FN(this);
|
|
12163
12460
|
return true;
|
|
12164
12461
|
}
|
|
12165
12462
|
}
|
|
@@ -22900,32 +23197,32 @@ const heat_columns = [
|
|
|
22900
23197
|
{
|
|
22901
23198
|
key: "totalPnl",
|
|
22902
23199
|
label: "Total PNL",
|
|
22903
|
-
format: (data) => data.totalPnl !== null ?
|
|
23200
|
+
format: (data) => data.totalPnl !== null ? `${data.totalPnl.toFixed(2)}%` : "N/A",
|
|
22904
23201
|
isVisible: () => true,
|
|
22905
23202
|
},
|
|
22906
23203
|
{
|
|
22907
23204
|
key: "sharpeRatio",
|
|
22908
23205
|
label: "Sharpe",
|
|
22909
|
-
format: (data) => data.sharpeRatio !== null ?
|
|
23206
|
+
format: (data) => data.sharpeRatio !== null ? data.sharpeRatio.toFixed(3) : "N/A",
|
|
22910
23207
|
isVisible: () => true,
|
|
22911
23208
|
},
|
|
22912
23209
|
{
|
|
22913
23210
|
key: "annualizedSharpeRatio",
|
|
22914
23211
|
label: "Ann Sharpe",
|
|
22915
|
-
format: (data) => data.annualizedSharpeRatio !== null ?
|
|
23212
|
+
format: (data) => data.annualizedSharpeRatio !== null ? data.annualizedSharpeRatio.toFixed(3) : "N/A",
|
|
22916
23213
|
isVisible: () => true,
|
|
22917
23214
|
},
|
|
22918
23215
|
{
|
|
22919
23216
|
key: "certaintyRatio",
|
|
22920
23217
|
label: "Certainty",
|
|
22921
|
-
format: (data) => data.certaintyRatio !== null ?
|
|
23218
|
+
format: (data) => data.certaintyRatio !== null ? data.certaintyRatio.toFixed(3) : "N/A",
|
|
22922
23219
|
isVisible: () => true,
|
|
22923
23220
|
},
|
|
22924
23221
|
{
|
|
22925
23222
|
key: "expectedYearlyReturns",
|
|
22926
23223
|
label: "Exp Yearly",
|
|
22927
23224
|
format: (data) => data.expectedYearlyReturns !== null
|
|
22928
|
-
?
|
|
23225
|
+
? `${data.expectedYearlyReturns.toFixed(2)}%`
|
|
22929
23226
|
: "N/A",
|
|
22930
23227
|
isVisible: () => true,
|
|
22931
23228
|
},
|
|
@@ -22938,37 +23235,37 @@ const heat_columns = [
|
|
|
22938
23235
|
{
|
|
22939
23236
|
key: "profitFactor",
|
|
22940
23237
|
label: "PF",
|
|
22941
|
-
format: (data) => data.profitFactor !== null ?
|
|
23238
|
+
format: (data) => data.profitFactor !== null ? data.profitFactor.toFixed(3) : "N/A",
|
|
22942
23239
|
isVisible: () => true,
|
|
22943
23240
|
},
|
|
22944
23241
|
{
|
|
22945
23242
|
key: "expectancy",
|
|
22946
23243
|
label: "Expect",
|
|
22947
|
-
format: (data) => data.expectancy !== null ?
|
|
23244
|
+
format: (data) => data.expectancy !== null ? `${data.expectancy.toFixed(2)}%` : "N/A",
|
|
22948
23245
|
isVisible: () => true,
|
|
22949
23246
|
},
|
|
22950
23247
|
{
|
|
22951
23248
|
key: "winRate",
|
|
22952
23249
|
label: "WR",
|
|
22953
|
-
format: (data) => data.winRate !== null ?
|
|
23250
|
+
format: (data) => data.winRate !== null ? `${data.winRate.toFixed(2)}%` : "N/A",
|
|
22954
23251
|
isVisible: () => true,
|
|
22955
23252
|
},
|
|
22956
23253
|
{
|
|
22957
23254
|
key: "avgWin",
|
|
22958
23255
|
label: "Avg Win",
|
|
22959
|
-
format: (data) => data.avgWin !== null ?
|
|
23256
|
+
format: (data) => data.avgWin !== null ? `${data.avgWin.toFixed(2)}%` : "N/A",
|
|
22960
23257
|
isVisible: () => true,
|
|
22961
23258
|
},
|
|
22962
23259
|
{
|
|
22963
23260
|
key: "avgLoss",
|
|
22964
23261
|
label: "Avg Loss",
|
|
22965
|
-
format: (data) => data.avgLoss !== null ?
|
|
23262
|
+
format: (data) => data.avgLoss !== null ? `${data.avgLoss.toFixed(2)}%` : "N/A",
|
|
22966
23263
|
isVisible: () => true,
|
|
22967
23264
|
},
|
|
22968
23265
|
{
|
|
22969
23266
|
key: "maxDrawdown",
|
|
22970
23267
|
label: "Max DD",
|
|
22971
|
-
format: (data) => data.maxDrawdown !== null ?
|
|
23268
|
+
format: (data) => data.maxDrawdown !== null ? `${(-data.maxDrawdown).toFixed(2)}%` : "N/A",
|
|
22972
23269
|
isVisible: () => true,
|
|
22973
23270
|
},
|
|
22974
23271
|
{
|
|
@@ -22992,31 +23289,31 @@ const heat_columns = [
|
|
|
22992
23289
|
{
|
|
22993
23290
|
key: "avgPeakPnl",
|
|
22994
23291
|
label: "Avg Peak PNL",
|
|
22995
|
-
format: (data) => data.avgPeakPnl !== null ?
|
|
23292
|
+
format: (data) => data.avgPeakPnl !== null ? `${data.avgPeakPnl.toFixed(2)}%` : "N/A",
|
|
22996
23293
|
isVisible: () => true,
|
|
22997
23294
|
},
|
|
22998
23295
|
{
|
|
22999
23296
|
key: "avgFallPnl",
|
|
23000
23297
|
label: "Avg DD PNL",
|
|
23001
|
-
format: (data) => data.avgFallPnl !== null ?
|
|
23298
|
+
format: (data) => data.avgFallPnl !== null ? `${data.avgFallPnl.toFixed(2)}%` : "N/A",
|
|
23002
23299
|
isVisible: () => true,
|
|
23003
23300
|
},
|
|
23004
23301
|
{
|
|
23005
23302
|
key: "peakProfitPnl",
|
|
23006
23303
|
label: "Peak Profit PNL",
|
|
23007
|
-
format: (data) => data.peakProfitPnl !== null ?
|
|
23304
|
+
format: (data) => data.peakProfitPnl !== null ? `${data.peakProfitPnl.toFixed(2)}%` : "N/A",
|
|
23008
23305
|
isVisible: () => true,
|
|
23009
23306
|
},
|
|
23010
23307
|
{
|
|
23011
23308
|
key: "maxDrawdownPnl",
|
|
23012
23309
|
label: "Max DD PNL",
|
|
23013
|
-
format: (data) => data.maxDrawdownPnl !== null ?
|
|
23310
|
+
format: (data) => data.maxDrawdownPnl !== null ? `${data.maxDrawdownPnl.toFixed(2)}%` : "N/A",
|
|
23014
23311
|
isVisible: () => true,
|
|
23015
23312
|
},
|
|
23016
23313
|
{
|
|
23017
23314
|
key: "medianPnl",
|
|
23018
23315
|
label: "Median PNL",
|
|
23019
|
-
format: (data) => data.medianPnl !== null ?
|
|
23316
|
+
format: (data) => data.medianPnl !== null ? `${data.medianPnl.toFixed(2)}%` : "N/A",
|
|
23020
23317
|
isVisible: () => true,
|
|
23021
23318
|
},
|
|
23022
23319
|
{
|
|
@@ -23041,7 +23338,7 @@ const heat_columns = [
|
|
|
23041
23338
|
key: "avgConsecutiveWinPnl",
|
|
23042
23339
|
label: "Avg Win Streak PNL",
|
|
23043
23340
|
format: (data) => data.avgConsecutiveWinPnl !== null
|
|
23044
|
-
?
|
|
23341
|
+
? `${data.avgConsecutiveWinPnl.toFixed(2)}%`
|
|
23045
23342
|
: "N/A",
|
|
23046
23343
|
isVisible: () => true,
|
|
23047
23344
|
},
|
|
@@ -23049,7 +23346,7 @@ const heat_columns = [
|
|
|
23049
23346
|
key: "avgConsecutiveLossPnl",
|
|
23050
23347
|
label: "Avg Loss Streak PNL",
|
|
23051
23348
|
format: (data) => data.avgConsecutiveLossPnl !== null
|
|
23052
|
-
?
|
|
23349
|
+
? `${data.avgConsecutiveLossPnl.toFixed(2)}%`
|
|
23053
23350
|
: "N/A",
|
|
23054
23351
|
isVisible: () => true,
|
|
23055
23352
|
},
|
|
@@ -23062,7 +23359,7 @@ const heat_columns = [
|
|
|
23062
23359
|
{
|
|
23063
23360
|
key: "trendStrength",
|
|
23064
23361
|
label: "Trend %/d",
|
|
23065
|
-
format: (data) => data.trendStrength !== null ?
|
|
23362
|
+
format: (data) => data.trendStrength !== null ? `${data.trendStrength.toFixed(2)}%` : "N/A",
|
|
23066
23363
|
isVisible: () => true,
|
|
23067
23364
|
},
|
|
23068
23365
|
{
|
|
@@ -23115,25 +23412,25 @@ const heat_columns = [
|
|
|
23115
23412
|
{
|
|
23116
23413
|
key: "medianStepSize",
|
|
23117
23414
|
label: "Median Step",
|
|
23118
|
-
format: (data) => data.medianStepSize !== null ?
|
|
23415
|
+
format: (data) => data.medianStepSize !== null ? `${data.medianStepSize.toFixed(2)}%` : "N/A",
|
|
23119
23416
|
isVisible: () => true,
|
|
23120
23417
|
},
|
|
23121
23418
|
{
|
|
23122
23419
|
key: "sortinoRatio",
|
|
23123
23420
|
label: "Sortino",
|
|
23124
|
-
format: (data) => data.sortinoRatio !== null ?
|
|
23421
|
+
format: (data) => data.sortinoRatio !== null ? data.sortinoRatio.toFixed(3) : "N/A",
|
|
23125
23422
|
isVisible: () => true,
|
|
23126
23423
|
},
|
|
23127
23424
|
{
|
|
23128
23425
|
key: "calmarRatio",
|
|
23129
23426
|
label: "Calmar",
|
|
23130
|
-
format: (data) => data.calmarRatio !== null ?
|
|
23427
|
+
format: (data) => data.calmarRatio !== null ? data.calmarRatio.toFixed(3) : "N/A",
|
|
23131
23428
|
isVisible: () => true,
|
|
23132
23429
|
},
|
|
23133
23430
|
{
|
|
23134
23431
|
key: "recoveryFactor",
|
|
23135
23432
|
label: "Recovery",
|
|
23136
|
-
format: (data) => data.recoveryFactor !== null ?
|
|
23433
|
+
format: (data) => data.recoveryFactor !== null ? data.recoveryFactor.toFixed(3) : "N/A",
|
|
23137
23434
|
isVisible: () => true,
|
|
23138
23435
|
},
|
|
23139
23436
|
];
|
|
@@ -30309,28 +30606,28 @@ class HeatmapStorage {
|
|
|
30309
30606
|
`# Portfolio Heatmap: ${strategyName}`,
|
|
30310
30607
|
"",
|
|
30311
30608
|
`**Total Symbols:** ${data.totalSymbols}`,
|
|
30312
|
-
`**Portfolio PNL:** ${data.portfolioTotalPnl !== null ?
|
|
30313
|
-
`**Pooled Sharpe:** ${data.portfolioSharpeRatio !== null ?
|
|
30314
|
-
`**Annualized Sharpe:** ${data.portfolioAnnualizedSharpeRatio !== null ?
|
|
30315
|
-
`**Certainty Ratio:** ${data.portfolioCertaintyRatio !== null ?
|
|
30316
|
-
`**Expected Yearly Returns:** ${data.portfolioExpectedYearlyReturns !== null ?
|
|
30609
|
+
`**Portfolio PNL:** ${data.portfolioTotalPnl !== null ? `${data.portfolioTotalPnl.toFixed(2)} %` : "N/A"}`,
|
|
30610
|
+
`**Pooled Sharpe:** ${data.portfolioSharpeRatio !== null ? data.portfolioSharpeRatio.toFixed(3) : "N/A"}`,
|
|
30611
|
+
`**Annualized Sharpe:** ${data.portfolioAnnualizedSharpeRatio !== null ? data.portfolioAnnualizedSharpeRatio.toFixed(3) : "N/A"}`,
|
|
30612
|
+
`**Certainty Ratio:** ${data.portfolioCertaintyRatio !== null ? data.portfolioCertaintyRatio.toFixed(3) : "N/A"}`,
|
|
30613
|
+
`**Expected Yearly Returns:** ${data.portfolioExpectedYearlyReturns !== null ? `${data.portfolioExpectedYearlyReturns.toFixed(2)} %` : "N/A"}`,
|
|
30317
30614
|
`**Trades Per Year:** ${data.portfolioTradesPerYear !== null ? data.portfolioTradesPerYear.toFixed(1) : "N/A"}`,
|
|
30318
30615
|
`**Total Trades:** ${data.portfolioTotalTrades}`,
|
|
30319
|
-
`**Avg Peak PNL:** ${data.portfolioAvgPeakPnl !== null ?
|
|
30320
|
-
`**Avg Max Drawdown PNL:** ${data.portfolioAvgFallPnl !== null ?
|
|
30321
|
-
`**Peak Profit PNL:** ${data.portfolioPeakProfitPnl !== null ?
|
|
30322
|
-
`**Max Drawdown PNL:** ${data.portfolioMaxDrawdownPnl !== null ?
|
|
30323
|
-
`**Median PNL:** ${data.portfolioMedianPnl !== null ?
|
|
30616
|
+
`**Avg Peak PNL:** ${data.portfolioAvgPeakPnl !== null ? `${data.portfolioAvgPeakPnl.toFixed(2)} %` : "N/A"}`,
|
|
30617
|
+
`**Avg Max Drawdown PNL:** ${data.portfolioAvgFallPnl !== null ? `${data.portfolioAvgFallPnl.toFixed(2)} %` : "N/A"}`,
|
|
30618
|
+
`**Peak Profit PNL:** ${data.portfolioPeakProfitPnl !== null ? `${data.portfolioPeakProfitPnl.toFixed(2)} %` : "N/A"}`,
|
|
30619
|
+
`**Max Drawdown PNL:** ${data.portfolioMaxDrawdownPnl !== null ? `${data.portfolioMaxDrawdownPnl.toFixed(2)} %` : "N/A"}`,
|
|
30620
|
+
`**Median PNL:** ${data.portfolioMedianPnl !== null ? `${data.portfolioMedianPnl.toFixed(2)} %` : "N/A"}`,
|
|
30324
30621
|
`**Avg Duration:** ${data.portfolioAvgDuration !== null ? `${data.portfolioAvgDuration.toFixed(1)} min` : "N/A"}`,
|
|
30325
30622
|
`**Avg Win Duration:** ${data.portfolioAvgWinDuration !== null ? `${data.portfolioAvgWinDuration.toFixed(1)} min` : "N/A"}`,
|
|
30326
30623
|
`**Avg Loss Duration:** ${data.portfolioAvgLossDuration !== null ? `${data.portfolioAvgLossDuration.toFixed(1)} min` : "N/A"}`,
|
|
30327
|
-
`**Avg Consecutive Win PNL:** ${data.portfolioAvgConsecutiveWinPnl !== null ?
|
|
30328
|
-
`**Avg Consecutive Loss PNL:** ${data.portfolioAvgConsecutiveLossPnl !== null ?
|
|
30329
|
-
`**Standard Deviation Per Trade:** ${data.portfolioStdDev !== null ?
|
|
30330
|
-
`**Sortino Ratio:** ${data.portfolioSortinoRatio !== null ?
|
|
30331
|
-
`**Calmar Ratio:** ${data.portfolioCalmarRatio !== null ?
|
|
30332
|
-
`**Recovery Factor:** ${data.portfolioRecoveryFactor !== null ?
|
|
30333
|
-
`**Expectancy:** ${data.portfolioExpectancy !== null ?
|
|
30624
|
+
`**Avg Consecutive Win PNL:** ${data.portfolioAvgConsecutiveWinPnl !== null ? `${data.portfolioAvgConsecutiveWinPnl.toFixed(2)} %` : "N/A"}`,
|
|
30625
|
+
`**Avg Consecutive Loss PNL:** ${data.portfolioAvgConsecutiveLossPnl !== null ? `${data.portfolioAvgConsecutiveLossPnl.toFixed(2)} %` : "N/A"}`,
|
|
30626
|
+
`**Standard Deviation Per Trade:** ${data.portfolioStdDev !== null ? `${data.portfolioStdDev.toFixed(2)} %` : "N/A"}`,
|
|
30627
|
+
`**Sortino Ratio:** ${data.portfolioSortinoRatio !== null ? data.portfolioSortinoRatio.toFixed(3) : "N/A"}`,
|
|
30628
|
+
`**Calmar Ratio:** ${data.portfolioCalmarRatio !== null ? data.portfolioCalmarRatio.toFixed(3) : "N/A"}`,
|
|
30629
|
+
`**Recovery Factor:** ${data.portfolioRecoveryFactor !== null ? data.portfolioRecoveryFactor.toFixed(3) : "N/A"}`,
|
|
30630
|
+
`**Expectancy:** ${data.portfolioExpectancy !== null ? `${data.portfolioExpectancy.toFixed(2)} %` : "N/A"}`,
|
|
30334
30631
|
"",
|
|
30335
30632
|
table,
|
|
30336
30633
|
"",
|
|
@@ -67632,4 +67929,4 @@ const validateSignal = (signal, currentPrice) => {
|
|
|
67632
67929
|
return !errors.length;
|
|
67633
67930
|
};
|
|
67634
67931
|
|
|
67635
|
-
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Cron, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Lookup, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistBreakevenInstance, PersistCandleAdapter, PersistCandleInstance, PersistIntervalAdapter, PersistIntervalInstance, PersistLogAdapter, PersistLogInstance, PersistMeasureAdapter, PersistMeasureInstance, PersistMemoryAdapter, PersistMemoryInstance, PersistNotificationAdapter, PersistNotificationInstance, PersistPartialAdapter, PersistPartialInstance, PersistRecentAdapter, PersistRecentInstance, PersistRiskAdapter, PersistRiskInstance, PersistScheduleAdapter, PersistScheduleInstance, PersistSessionAdapter, PersistSessionInstance, PersistSignalAdapter, PersistSignalInstance, PersistStateAdapter, PersistStateInstance, PersistStorageAdapter, PersistStorageInstance, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, beginContext, beginTime, cacheCandles, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getPriceScale, getRawCandles, getRiskSchema, getRuntimeInfo, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, intervalStepMs, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenAfterEnd, listenAfterEndOnce, listenBacktestProgress, listenBeforeStart, listenBeforeStartOnce, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toPlainString, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCandles, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, waitForReady, warmCandles, writeMemory };
|
|
67932
|
+
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Cron, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Lookup, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistBreakevenInstance, PersistCandleAdapter, PersistCandleInstance, PersistIntervalAdapter, PersistIntervalInstance, PersistLogAdapter, PersistLogInstance, PersistMeasureAdapter, PersistMeasureInstance, PersistMemoryAdapter, PersistMemoryInstance, PersistNotificationAdapter, PersistNotificationInstance, PersistPartialAdapter, PersistPartialInstance, PersistRecentAdapter, PersistRecentInstance, PersistRiskAdapter, PersistRiskInstance, PersistScheduleAdapter, PersistScheduleInstance, PersistSessionAdapter, PersistSessionInstance, PersistSignalAdapter, PersistSignalInstance, PersistStateAdapter, PersistStateInstance, PersistStorageAdapter, PersistStorageInstance, PersistStrategyAdapter, PersistStrategyInstance, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, beginContext, beginTime, cacheCandles, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getPriceScale, getRawCandles, getRiskSchema, getRuntimeInfo, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, intervalStepMs, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenAfterEnd, listenAfterEndOnce, listenBacktestProgress, listenBeforeStart, listenBeforeStartOnce, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toPlainString, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCandles, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, waitForReady, warmCandles, writeMemory };
|