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.cjs
CHANGED
|
@@ -1001,6 +1001,12 @@ const PERSIST_SCHEDULE_UTILS_METHOD_NAME_WRITE_DATA = "PersistScheduleUtils.writ
|
|
|
1001
1001
|
const PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_JSON = "PersistScheduleUtils.useJson";
|
|
1002
1002
|
const PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_DUMMY = "PersistScheduleUtils.useDummy";
|
|
1003
1003
|
const PERSIST_SCHEDULE_UTILS_METHOD_NAME_CLEAR = "PersistScheduleUtils.clear";
|
|
1004
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_PERSIST_STRATEGY_ADAPTER = "PersistStrategyUtils.usePersistStrategyAdapter";
|
|
1005
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_READ_DATA = "PersistStrategyUtils.readStrategyData";
|
|
1006
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_WRITE_DATA = "PersistStrategyUtils.writeStrategyData";
|
|
1007
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_JSON = "PersistStrategyUtils.useJson";
|
|
1008
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_DUMMY = "PersistStrategyUtils.useDummy";
|
|
1009
|
+
const PERSIST_STRATEGY_UTILS_METHOD_NAME_CLEAR = "PersistStrategyUtils.clear";
|
|
1004
1010
|
const PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_PERSIST_PARTIAL_ADAPTER = "PersistPartialUtils.usePersistPartialAdapter";
|
|
1005
1011
|
const PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA = "PersistPartialUtils.readPartialData";
|
|
1006
1012
|
const PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistPartialUtils.writePartialData";
|
|
@@ -1864,6 +1870,212 @@ class PersistScheduleUtils {
|
|
|
1864
1870
|
* ```
|
|
1865
1871
|
*/
|
|
1866
1872
|
const PersistScheduleAdapter = new PersistScheduleUtils();
|
|
1873
|
+
/**
|
|
1874
|
+
* Default file-based implementation of IPersistStrategyInstance.
|
|
1875
|
+
*
|
|
1876
|
+
* Features:
|
|
1877
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
1878
|
+
* - Uses fixed entity ID "strategy" within a per-context PersistBase
|
|
1879
|
+
* - Crash-safe via atomic writes
|
|
1880
|
+
*
|
|
1881
|
+
* @example
|
|
1882
|
+
* ```typescript
|
|
1883
|
+
* const instance = new PersistStrategyInstance("BTCUSDT", "my-strategy", "binance");
|
|
1884
|
+
* await instance.waitForInit(true);
|
|
1885
|
+
* await instance.writeStrategyData(snapshot);
|
|
1886
|
+
* const restored = await instance.readStrategyData();
|
|
1887
|
+
* ```
|
|
1888
|
+
*/
|
|
1889
|
+
class PersistStrategyInstance {
|
|
1890
|
+
/**
|
|
1891
|
+
* Creates new deferred strategy state persistence instance.
|
|
1892
|
+
*
|
|
1893
|
+
* @param symbol - Trading pair symbol
|
|
1894
|
+
* @param strategyName - Strategy identifier
|
|
1895
|
+
* @param exchangeName - Exchange identifier
|
|
1896
|
+
*/
|
|
1897
|
+
constructor(symbol, strategyName, exchangeName) {
|
|
1898
|
+
this.symbol = symbol;
|
|
1899
|
+
this.strategyName = strategyName;
|
|
1900
|
+
this.exchangeName = exchangeName;
|
|
1901
|
+
this._storage = new PersistBase(`${symbol}_${strategyName}_${exchangeName}`, `./dump/data/strategy/`);
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Initializes the underlying PersistBase storage.
|
|
1905
|
+
*
|
|
1906
|
+
* @param initial - Whether this is the first initialization
|
|
1907
|
+
* @returns Promise that resolves when initialization is complete
|
|
1908
|
+
*/
|
|
1909
|
+
async waitForInit(initial) {
|
|
1910
|
+
await this._storage.waitForInit(initial);
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Reads the persisted strategy state snapshot using the fixed STORAGE_KEY.
|
|
1914
|
+
*
|
|
1915
|
+
* @returns Promise resolving to strategy state snapshot or null if not found
|
|
1916
|
+
*/
|
|
1917
|
+
async readStrategyData() {
|
|
1918
|
+
if (await this._storage.hasValue(PersistStrategyInstance.STORAGE_KEY)) {
|
|
1919
|
+
const strategyData = await this._storage.readValue(PersistStrategyInstance.STORAGE_KEY);
|
|
1920
|
+
// JSON serializes Infinity as null, so an eternal-hold signal
|
|
1921
|
+
// (minuteEstimatedTime: Infinity) reads back as null — restore it.
|
|
1922
|
+
for (const signal of [strategyData.closedSignal, strategyData.cancelledSignal, strategyData.activatedSignal]) {
|
|
1923
|
+
if (signal && signal.minuteEstimatedTime == null) {
|
|
1924
|
+
signal.minuteEstimatedTime = Infinity;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
return strategyData;
|
|
1928
|
+
}
|
|
1929
|
+
return null;
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Writes the strategy state snapshot (or null to clear) using the fixed STORAGE_KEY.
|
|
1933
|
+
*
|
|
1934
|
+
* @param row - Strategy state snapshot to persist, or null to clear
|
|
1935
|
+
* @returns Promise that resolves when write is complete
|
|
1936
|
+
*/
|
|
1937
|
+
async writeStrategyData(row) {
|
|
1938
|
+
await this._storage.writeValue(PersistStrategyInstance.STORAGE_KEY, row);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
/** Fixed entity key for storing the strategy state snapshot */
|
|
1942
|
+
PersistStrategyInstance.STORAGE_KEY = "strategy";
|
|
1943
|
+
/**
|
|
1944
|
+
* No-op IPersistStrategyInstance implementation used by PersistStrategyUtils.useDummy().
|
|
1945
|
+
* All reads return null, all writes are discarded.
|
|
1946
|
+
*/
|
|
1947
|
+
class PersistStrategyDummyInstance {
|
|
1948
|
+
/**
|
|
1949
|
+
* No-op constructor.
|
|
1950
|
+
* Context arguments are accepted to satisfy TPersistStrategyInstanceCtor.
|
|
1951
|
+
*/
|
|
1952
|
+
constructor(_symbol, _strategyName, _exchangeName) { }
|
|
1953
|
+
/**
|
|
1954
|
+
* No-op initialization.
|
|
1955
|
+
* @returns Promise that resolves immediately
|
|
1956
|
+
*/
|
|
1957
|
+
async waitForInit(_initial) { }
|
|
1958
|
+
/**
|
|
1959
|
+
* Always returns null (no persisted strategy state).
|
|
1960
|
+
* @returns Promise resolving to null
|
|
1961
|
+
*/
|
|
1962
|
+
async readStrategyData() { return null; }
|
|
1963
|
+
/**
|
|
1964
|
+
* No-op write (discards strategy state).
|
|
1965
|
+
* @returns Promise that resolves immediately
|
|
1966
|
+
*/
|
|
1967
|
+
async writeStrategyData(_row) { }
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* Utility class for managing deferred strategy state persistence.
|
|
1971
|
+
*
|
|
1972
|
+
* Features:
|
|
1973
|
+
* - Memoized storage instances per strategy
|
|
1974
|
+
* - Custom adapter support
|
|
1975
|
+
* - Atomic read/write operations for the deferred state snapshot
|
|
1976
|
+
* - Crash-safe in-flight broker operation state management
|
|
1977
|
+
*
|
|
1978
|
+
* Used by ClientStrategy for live mode persistence of the commit queue and deferred
|
|
1979
|
+
* user actions (_commitQueue, _closedSignal, _cancelledSignal, _activatedSignal).
|
|
1980
|
+
*/
|
|
1981
|
+
class PersistStrategyUtils {
|
|
1982
|
+
constructor() {
|
|
1983
|
+
/**
|
|
1984
|
+
* Constructor used to create per-context strategy state instances.
|
|
1985
|
+
* Replaceable via usePersistStrategyAdapter() / useJson() / useDummy().
|
|
1986
|
+
*/
|
|
1987
|
+
this.PersistStrategyInstanceCtor = PersistStrategyInstance;
|
|
1988
|
+
/**
|
|
1989
|
+
* Memoized factory creating one IPersistStrategyInstance per (symbol, strategy, exchange) triple.
|
|
1990
|
+
*/
|
|
1991
|
+
this.getStrategyStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.PersistStrategyInstanceCtor, [symbol, strategyName, exchangeName]));
|
|
1992
|
+
/**
|
|
1993
|
+
* Reads persisted deferred strategy state for the given context.
|
|
1994
|
+
* Lazily initializes the instance on first access.
|
|
1995
|
+
*
|
|
1996
|
+
* @param symbol - Trading pair symbol
|
|
1997
|
+
* @param strategyName - Strategy identifier
|
|
1998
|
+
* @param exchangeName - Exchange identifier
|
|
1999
|
+
* @returns Promise resolving to strategy state snapshot or null if none persisted
|
|
2000
|
+
*/
|
|
2001
|
+
this.readStrategyData = async (symbol, strategyName, exchangeName) => {
|
|
2002
|
+
LOGGER_SERVICE$9.info(PERSIST_STRATEGY_UTILS_METHOD_NAME_READ_DATA);
|
|
2003
|
+
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
2004
|
+
const isInitial = !this.getStrategyStorage.has(key);
|
|
2005
|
+
const instance = this.getStrategyStorage(symbol, strategyName, exchangeName);
|
|
2006
|
+
await instance.waitForInit(isInitial);
|
|
2007
|
+
return instance.readStrategyData();
|
|
2008
|
+
};
|
|
2009
|
+
/**
|
|
2010
|
+
* Writes deferred strategy state (or null to clear) for the given context.
|
|
2011
|
+
* Lazily initializes the instance on first access.
|
|
2012
|
+
*
|
|
2013
|
+
* @param strategyRow - Strategy state snapshot to persist, or null to clear
|
|
2014
|
+
* @param symbol - Trading pair symbol
|
|
2015
|
+
* @param strategyName - Strategy identifier
|
|
2016
|
+
* @param exchangeName - Exchange identifier
|
|
2017
|
+
* @returns Promise that resolves when write is complete
|
|
2018
|
+
*/
|
|
2019
|
+
this.writeStrategyData = async (strategyRow, symbol, strategyName, exchangeName) => {
|
|
2020
|
+
LOGGER_SERVICE$9.info(PERSIST_STRATEGY_UTILS_METHOD_NAME_WRITE_DATA);
|
|
2021
|
+
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
2022
|
+
const isInitial = !this.getStrategyStorage.has(key);
|
|
2023
|
+
const instance = this.getStrategyStorage(symbol, strategyName, exchangeName);
|
|
2024
|
+
await instance.waitForInit(isInitial);
|
|
2025
|
+
return instance.writeStrategyData(strategyRow);
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Registers a custom IPersistStrategyInstance constructor.
|
|
2030
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
2031
|
+
*
|
|
2032
|
+
* @param Ctor - Custom IPersistStrategyInstance constructor
|
|
2033
|
+
*/
|
|
2034
|
+
usePersistStrategyAdapter(Ctor) {
|
|
2035
|
+
LOGGER_SERVICE$9.info(PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_PERSIST_STRATEGY_ADAPTER);
|
|
2036
|
+
this.PersistStrategyInstanceCtor = Ctor;
|
|
2037
|
+
this.getStrategyStorage.clear();
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* Clears the memoized instance cache.
|
|
2041
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
2042
|
+
*/
|
|
2043
|
+
clear() {
|
|
2044
|
+
LOGGER_SERVICE$9.log(PERSIST_STRATEGY_UTILS_METHOD_NAME_CLEAR);
|
|
2045
|
+
this.getStrategyStorage.clear();
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Switches to the default file-based PersistStrategyInstance.
|
|
2049
|
+
*/
|
|
2050
|
+
useJson() {
|
|
2051
|
+
LOGGER_SERVICE$9.log(PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_JSON);
|
|
2052
|
+
this.usePersistStrategyAdapter(PersistStrategyInstance);
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Switches to PersistStrategyDummyInstance (all operations are no-ops).
|
|
2056
|
+
*/
|
|
2057
|
+
useDummy() {
|
|
2058
|
+
LOGGER_SERVICE$9.log(PERSIST_STRATEGY_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2059
|
+
this.usePersistStrategyAdapter(PersistStrategyDummyInstance);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Global singleton instance of PersistStrategyUtils.
|
|
2064
|
+
* Used by ClientStrategy for deferred strategy state persistence.
|
|
2065
|
+
*
|
|
2066
|
+
* @example
|
|
2067
|
+
* ```typescript
|
|
2068
|
+
* // Custom adapter
|
|
2069
|
+
* PersistStrategyAdapter.usePersistStrategyAdapter(RedisPersist);
|
|
2070
|
+
*
|
|
2071
|
+
* // Read strategy state
|
|
2072
|
+
* const state = await PersistStrategyAdapter.readStrategyData("BTCUSDT", "my-strategy", "binance");
|
|
2073
|
+
*
|
|
2074
|
+
* // Write strategy state
|
|
2075
|
+
* await PersistStrategyAdapter.writeStrategyData(state, "BTCUSDT", "my-strategy", "binance");
|
|
2076
|
+
* ```
|
|
2077
|
+
*/
|
|
2078
|
+
const PersistStrategyAdapter = new PersistStrategyUtils();
|
|
1867
2079
|
/**
|
|
1868
2080
|
* Default file-based implementation of IPersistPartialInstance.
|
|
1869
2081
|
*
|
|
@@ -6791,6 +7003,9 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
6791
7003
|
{
|
|
6792
7004
|
self._commitQueue = [];
|
|
6793
7005
|
}
|
|
7006
|
+
// Persist the now-empty queue so a crash after draining does not replay commits
|
|
7007
|
+
// that were already forwarded to the broker on the next restart.
|
|
7008
|
+
await PERSIST_STRATEGY_FN(self);
|
|
6794
7009
|
if (!self._pendingSignal) {
|
|
6795
7010
|
return;
|
|
6796
7011
|
}
|
|
@@ -7247,6 +7462,19 @@ const WAIT_FOR_INIT_FN$4 = async (self) => {
|
|
|
7247
7462
|
self._lastPendingId = recentSignal.id;
|
|
7248
7463
|
}
|
|
7249
7464
|
}
|
|
7465
|
+
// Restore deferred strategy state (commit queue + deferred user actions) so any
|
|
7466
|
+
// confirmed-but-not-yet-forwarded broker operation survives a live crash and is
|
|
7467
|
+
// re-drained on the next tick. Placed before the pending/scheduled restore which
|
|
7468
|
+
// may early-return on exchange/strategy mismatch.
|
|
7469
|
+
{
|
|
7470
|
+
const strategyData = await PersistStrategyAdapter.readStrategyData(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
7471
|
+
if (strategyData) {
|
|
7472
|
+
self._commitQueue = strategyData.commitQueue ?? [];
|
|
7473
|
+
self._closedSignal = strategyData.closedSignal;
|
|
7474
|
+
self._cancelledSignal = strategyData.cancelledSignal;
|
|
7475
|
+
self._activatedSignal = strategyData.activatedSignal;
|
|
7476
|
+
}
|
|
7477
|
+
}
|
|
7250
7478
|
// Restore pending signal
|
|
7251
7479
|
const pendingSignal = await PersistSignalAdapter.readSignalData(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
7252
7480
|
if (pendingSignal) {
|
|
@@ -7296,6 +7524,28 @@ const WAIT_FOR_DISPOSE_FN$1 = async (self) => {
|
|
|
7296
7524
|
self.params.logger.debug("ClientStrategy dispose");
|
|
7297
7525
|
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);
|
|
7298
7526
|
};
|
|
7527
|
+
/**
|
|
7528
|
+
* Persists the deferred strategy state snapshot (commit queue + deferred user actions)
|
|
7529
|
+
* to disk in live mode.
|
|
7530
|
+
*
|
|
7531
|
+
* These fields carry confirmed-but-not-yet-forwarded broker operations and survive the
|
|
7532
|
+
* gap between ticks (drained at the start of the next tick). Without persistence a live
|
|
7533
|
+
* crash in that window silently loses the pending broker operation while _pendingSignal
|
|
7534
|
+
* (already mutated and saved) claims it happened. Skipped in backtest mode.
|
|
7535
|
+
*
|
|
7536
|
+
* @param self - ClientStrategy instance
|
|
7537
|
+
*/
|
|
7538
|
+
const PERSIST_STRATEGY_FN = async (self) => {
|
|
7539
|
+
if (self.params.backtest) {
|
|
7540
|
+
return;
|
|
7541
|
+
}
|
|
7542
|
+
await PersistStrategyAdapter.writeStrategyData({
|
|
7543
|
+
commitQueue: self._commitQueue,
|
|
7544
|
+
closedSignal: self._closedSignal,
|
|
7545
|
+
cancelledSignal: self._cancelledSignal,
|
|
7546
|
+
activatedSignal: self._activatedSignal,
|
|
7547
|
+
}, self.params.symbol, self.params.strategyName, self.params.exchangeName);
|
|
7548
|
+
};
|
|
7299
7549
|
const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
|
|
7300
7550
|
// Initialize partial array if not present
|
|
7301
7551
|
if (!signal._partial)
|
|
@@ -8920,14 +9170,15 @@ const CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, signal, averagePrice, c
|
|
|
8920
9170
|
const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, averagePrice, closeTimestamp) => {
|
|
8921
9171
|
const syncCloseAllowed = await CALL_SIGNAL_SYNC_CLOSE_FN(closeTimestamp, averagePrice, "closed", closedSignal, self);
|
|
8922
9172
|
if (!syncCloseAllowed) {
|
|
8923
|
-
|
|
9173
|
+
// Sync close rejected (e.g. broker rejected the order) — keep _closedSignal intact
|
|
9174
|
+
// and return null so the candle loop re-attempts on the next candle. Mirrors live
|
|
9175
|
+
// tick, which keeps _closedSignal and returns idle on a rejected user close,
|
|
9176
|
+
// re-trying on the following tick.
|
|
9177
|
+
self.params.logger.info("ClientStrategy backtest: user-closed signal rejected by sync, will retry on next candle", {
|
|
8924
9178
|
symbol: self.params.execution.context.symbol,
|
|
8925
9179
|
signalId: closedSignal.id,
|
|
8926
9180
|
});
|
|
8927
|
-
|
|
8928
|
-
self._pendingSignal = closedSignal;
|
|
8929
|
-
throw new Error(`ClientStrategy backtest: signal close rejected by sync (signalId=${closedSignal.id}). ` +
|
|
8930
|
-
`Retry backtest() with new candle data.`);
|
|
9181
|
+
return null;
|
|
8931
9182
|
}
|
|
8932
9183
|
self._closedSignal = null;
|
|
8933
9184
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", closedSignal, averagePrice);
|
|
@@ -9179,7 +9430,15 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles, frameEnd
|
|
|
9179
9430
|
}
|
|
9180
9431
|
// КРИТИЧНО: Проверяем был ли сигнал закрыт пользователем через closePending()
|
|
9181
9432
|
if (self._closedSignal) {
|
|
9182
|
-
|
|
9433
|
+
const userCloseResult = await CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN(self, self._closedSignal, averagePrice, currentCandleTimestamp);
|
|
9434
|
+
// Sync close accepted — position closed, return. If rejected, _closedSignal is
|
|
9435
|
+
// kept and we skip this candle: live tick returns idle on a rejected user close
|
|
9436
|
+
// (it does NOT fall into the TP/SL or active-monitoring path), so the candle is
|
|
9437
|
+
// skipped here and the close is re-attempted on the next candle.
|
|
9438
|
+
if (userCloseResult) {
|
|
9439
|
+
return userCloseResult;
|
|
9440
|
+
}
|
|
9441
|
+
continue;
|
|
9183
9442
|
}
|
|
9184
9443
|
let shouldClose = false;
|
|
9185
9444
|
let closeReason;
|
|
@@ -9230,7 +9489,19 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles, frameEnd
|
|
|
9230
9489
|
else {
|
|
9231
9490
|
closePrice = averagePrice; // time_expired uses VWAP
|
|
9232
9491
|
}
|
|
9233
|
-
|
|
9492
|
+
const closeResult = await CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN(self, signal, closePrice, closeReason, currentCandleTimestamp);
|
|
9493
|
+
// Sync close accepted — position closed, return.
|
|
9494
|
+
if (closeResult) {
|
|
9495
|
+
return closeResult;
|
|
9496
|
+
}
|
|
9497
|
+
// Sync close rejected (e.g. broker rejected the order) — _pendingSignal is left
|
|
9498
|
+
// intact (not cleared on rejection). Do NOT return/continue: fall through to the
|
|
9499
|
+
// active-monitoring block below so this candle is processed exactly like live
|
|
9500
|
+
// tick, which runs RETURN_PENDING_SIGNAL_ACTIVE_FN when CLOSE_PENDING_SIGNAL_FN
|
|
9501
|
+
// returns null (updates _peak/_fall, fires active ping / breakeven / partial
|
|
9502
|
+
// callbacks, drains the commit queue). The close is re-attempted on the next
|
|
9503
|
+
// candle; for time_expired this eventually reaches the loop-exhausted close
|
|
9504
|
+
// (which throws if still rejected).
|
|
9234
9505
|
}
|
|
9235
9506
|
// Call onPartialProfit/onPartialLoss callbacks during backtest candle processing
|
|
9236
9507
|
// Calculate percentage of path to TP/SL
|
|
@@ -9652,7 +9923,7 @@ class ClientStrategy {
|
|
|
9652
9923
|
async getStopped(symbol) {
|
|
9653
9924
|
this.params.logger.debug("ClientStrategy getStopped", {
|
|
9654
9925
|
symbol,
|
|
9655
|
-
strategyName: this.params.
|
|
9926
|
+
strategyName: this.params.strategyName,
|
|
9656
9927
|
});
|
|
9657
9928
|
return this._isStopped;
|
|
9658
9929
|
}
|
|
@@ -10335,6 +10606,8 @@ class ClientStrategy {
|
|
|
10335
10606
|
if (this._cancelledSignal) {
|
|
10336
10607
|
const cancelledSignal = this._cancelledSignal;
|
|
10337
10608
|
this._cancelledSignal = null; // Clear after emitting
|
|
10609
|
+
// Persist the cleared deferred state so the drained flag is not replayed on restart
|
|
10610
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10338
10611
|
this.params.logger.info("ClientStrategy tick: scheduled signal was cancelled", {
|
|
10339
10612
|
symbol: this.params.execution.context.symbol,
|
|
10340
10613
|
signalId: cancelledSignal.id,
|
|
@@ -10393,6 +10666,8 @@ class ClientStrategy {
|
|
|
10393
10666
|
return await RETURN_IDLE_FN(this, currentPrice);
|
|
10394
10667
|
}
|
|
10395
10668
|
this._closedSignal = null; // Clear only after sync confirmed
|
|
10669
|
+
// Persist the cleared deferred state so the drained flag is not replayed on restart
|
|
10670
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10396
10671
|
this.params.logger.info("ClientStrategy tick: pending signal was closed", {
|
|
10397
10672
|
symbol: this.params.execution.context.symbol,
|
|
10398
10673
|
signalId: closedSignal.id,
|
|
@@ -10448,6 +10723,8 @@ class ClientStrategy {
|
|
|
10448
10723
|
const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
|
|
10449
10724
|
const activatedSignal = this._activatedSignal;
|
|
10450
10725
|
this._activatedSignal = null; // Clear after emitting
|
|
10726
|
+
// Persist the cleared deferred state so the drained flag is not replayed on restart
|
|
10727
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10451
10728
|
this.params.logger.info("ClientStrategy tick: scheduled signal was activated", {
|
|
10452
10729
|
symbol: this.params.execution.context.symbol,
|
|
10453
10730
|
signalId: activatedSignal.id,
|
|
@@ -10705,18 +10982,20 @@ class ClientStrategy {
|
|
|
10705
10982
|
const currentPrice = await this.params.exchange.getAveragePrice(symbol);
|
|
10706
10983
|
const closedSignal = this._closedSignal;
|
|
10707
10984
|
const closeTimestamp = this.params.execution.context.when.getTime();
|
|
10708
|
-
// Sync close: if external system rejects —
|
|
10985
|
+
// Sync close: if external system rejects — keep the close pending and re-attempt
|
|
10986
|
+
// it inside the candle loop below (PROCESS_PENDING_SIGNAL_CANDLES_FN handles
|
|
10987
|
+
// _closedSignal per candle). Mirrors live tick, which keeps _closedSignal and
|
|
10988
|
+
// retries on the next tick instead of failing.
|
|
10709
10989
|
const syncCloseAllowed = await CALL_SIGNAL_SYNC_CLOSE_FN(closeTimestamp, currentPrice, "closed", closedSignal, this);
|
|
10710
10990
|
if (!syncCloseAllowed) {
|
|
10711
|
-
this.params.logger.info("ClientStrategy backtest: user-closed signal rejected by sync, will retry", {
|
|
10991
|
+
this.params.logger.info("ClientStrategy backtest: user-closed signal rejected by sync, will retry in candle loop", {
|
|
10712
10992
|
symbol: this.params.execution.context.symbol,
|
|
10713
10993
|
signalId: closedSignal.id,
|
|
10714
10994
|
});
|
|
10715
|
-
// Restore _pendingSignal so
|
|
10716
|
-
|
|
10995
|
+
// Restore _pendingSignal so the candle loop processes the position normally;
|
|
10996
|
+
// _closedSignal is kept so the loop re-attempts the close on each candle.
|
|
10717
10997
|
this._pendingSignal = closedSignal;
|
|
10718
|
-
|
|
10719
|
-
`Retry backtest() with new candle data.`);
|
|
10998
|
+
return await PROCESS_PENDING_SIGNAL_CANDLES_FN(this, this._pendingSignal, candles, frameEndTime);
|
|
10720
10999
|
}
|
|
10721
11000
|
this._closedSignal = null; // Clear only after sync confirmed
|
|
10722
11001
|
// Emit commit with correct timestamp from backtest context
|
|
@@ -10898,7 +11177,7 @@ class ClientStrategy {
|
|
|
10898
11177
|
if (backtest) {
|
|
10899
11178
|
return;
|
|
10900
11179
|
}
|
|
10901
|
-
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.
|
|
11180
|
+
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
10902
11181
|
}
|
|
10903
11182
|
/**
|
|
10904
11183
|
* Cancels the scheduled signal without stopping the strategy.
|
|
@@ -10942,7 +11221,9 @@ class ClientStrategy {
|
|
|
10942
11221
|
// Commit will be emitted in backtest() with correct candle timestamp
|
|
10943
11222
|
return;
|
|
10944
11223
|
}
|
|
10945
|
-
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.
|
|
11224
|
+
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
11225
|
+
// Persist deferred _cancelledSignal so a crash before the next tick does not lose it
|
|
11226
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10946
11227
|
// Commit will be emitted in tick() with correct currentTime
|
|
10947
11228
|
}
|
|
10948
11229
|
/**
|
|
@@ -10993,7 +11274,9 @@ class ClientStrategy {
|
|
|
10993
11274
|
// Commit will be emitted AFTER successful risk check in PROCESS_SCHEDULED_SIGNAL_CANDLES_FN
|
|
10994
11275
|
return;
|
|
10995
11276
|
}
|
|
10996
|
-
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.
|
|
11277
|
+
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
11278
|
+
// Persist deferred _activatedSignal so a crash before the next tick does not lose it
|
|
11279
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10997
11280
|
// Commit will be emitted AFTER successful risk check in tick()
|
|
10998
11281
|
}
|
|
10999
11282
|
/**
|
|
@@ -11038,6 +11321,8 @@ class ClientStrategy {
|
|
|
11038
11321
|
return;
|
|
11039
11322
|
}
|
|
11040
11323
|
await PersistSignalAdapter.writeSignalData(this._pendingSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
11324
|
+
// Persist deferred _closedSignal so a crash before the next tick does not lose it
|
|
11325
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11041
11326
|
// Commit will be emitted in tick() with correct currentTime
|
|
11042
11327
|
}
|
|
11043
11328
|
/**
|
|
@@ -11208,10 +11493,10 @@ class ClientStrategy {
|
|
|
11208
11493
|
});
|
|
11209
11494
|
// Call onWrite callback for testing persist storage
|
|
11210
11495
|
if (this.params.callbacks?.onWrite) {
|
|
11211
|
-
this.params.callbacks.onWrite(this.params.
|
|
11496
|
+
this.params.callbacks.onWrite(this.params.symbol, TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice), backtest);
|
|
11212
11497
|
}
|
|
11213
11498
|
if (!backtest) {
|
|
11214
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
11499
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11215
11500
|
}
|
|
11216
11501
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11217
11502
|
this._commitQueue.push({
|
|
@@ -11221,6 +11506,8 @@ class ClientStrategy {
|
|
|
11221
11506
|
percentToClose,
|
|
11222
11507
|
currentPrice,
|
|
11223
11508
|
});
|
|
11509
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
11510
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11224
11511
|
return true;
|
|
11225
11512
|
}
|
|
11226
11513
|
/**
|
|
@@ -11391,10 +11678,10 @@ class ClientStrategy {
|
|
|
11391
11678
|
});
|
|
11392
11679
|
// Call onWrite callback for testing persist storage
|
|
11393
11680
|
if (this.params.callbacks?.onWrite) {
|
|
11394
|
-
this.params.callbacks.onWrite(this.params.
|
|
11681
|
+
this.params.callbacks.onWrite(this.params.symbol, TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice), backtest);
|
|
11395
11682
|
}
|
|
11396
11683
|
if (!backtest) {
|
|
11397
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
11684
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11398
11685
|
}
|
|
11399
11686
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11400
11687
|
this._commitQueue.push({
|
|
@@ -11404,6 +11691,8 @@ class ClientStrategy {
|
|
|
11404
11691
|
percentToClose,
|
|
11405
11692
|
currentPrice,
|
|
11406
11693
|
});
|
|
11694
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
11695
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11407
11696
|
return true;
|
|
11408
11697
|
}
|
|
11409
11698
|
/**
|
|
@@ -11590,10 +11879,10 @@ class ClientStrategy {
|
|
|
11590
11879
|
// Call onWrite callback for testing persist storage
|
|
11591
11880
|
if (this.params.callbacks?.onWrite) {
|
|
11592
11881
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice);
|
|
11593
|
-
this.params.callbacks.onWrite(this.params.
|
|
11882
|
+
this.params.callbacks.onWrite(this.params.symbol, publicSignal, backtest);
|
|
11594
11883
|
}
|
|
11595
11884
|
if (!backtest) {
|
|
11596
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
11885
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11597
11886
|
}
|
|
11598
11887
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11599
11888
|
this._commitQueue.push({
|
|
@@ -11602,6 +11891,8 @@ class ClientStrategy {
|
|
|
11602
11891
|
backtest,
|
|
11603
11892
|
currentPrice,
|
|
11604
11893
|
});
|
|
11894
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
11895
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11605
11896
|
return true;
|
|
11606
11897
|
}
|
|
11607
11898
|
/**
|
|
@@ -11839,10 +12130,10 @@ class ClientStrategy {
|
|
|
11839
12130
|
// Call onWrite callback for testing persist storage
|
|
11840
12131
|
if (this.params.callbacks?.onWrite) {
|
|
11841
12132
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice);
|
|
11842
|
-
this.params.callbacks.onWrite(this.params.
|
|
12133
|
+
this.params.callbacks.onWrite(this.params.symbol, publicSignal, backtest);
|
|
11843
12134
|
}
|
|
11844
12135
|
if (!backtest) {
|
|
11845
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
12136
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11846
12137
|
}
|
|
11847
12138
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11848
12139
|
this._commitQueue.push({
|
|
@@ -11852,6 +12143,8 @@ class ClientStrategy {
|
|
|
11852
12143
|
percentShift,
|
|
11853
12144
|
currentPrice,
|
|
11854
12145
|
});
|
|
12146
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
12147
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11855
12148
|
return true;
|
|
11856
12149
|
}
|
|
11857
12150
|
/**
|
|
@@ -12075,10 +12368,10 @@ class ClientStrategy {
|
|
|
12075
12368
|
// Call onWrite callback for testing persist storage
|
|
12076
12369
|
if (this.params.callbacks?.onWrite) {
|
|
12077
12370
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice);
|
|
12078
|
-
this.params.callbacks.onWrite(this.params.
|
|
12371
|
+
this.params.callbacks.onWrite(this.params.symbol, publicSignal, backtest);
|
|
12079
12372
|
}
|
|
12080
12373
|
if (!backtest) {
|
|
12081
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
12374
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
12082
12375
|
}
|
|
12083
12376
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
12084
12377
|
this._commitQueue.push({
|
|
@@ -12088,6 +12381,8 @@ class ClientStrategy {
|
|
|
12088
12381
|
percentShift,
|
|
12089
12382
|
currentPrice,
|
|
12090
12383
|
});
|
|
12384
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
12385
|
+
await PERSIST_STRATEGY_FN(this);
|
|
12091
12386
|
return true;
|
|
12092
12387
|
}
|
|
12093
12388
|
/**
|
|
@@ -12166,10 +12461,10 @@ class ClientStrategy {
|
|
|
12166
12461
|
});
|
|
12167
12462
|
// Call onWrite callback for testing persist storage
|
|
12168
12463
|
if (this.params.callbacks?.onWrite) {
|
|
12169
|
-
this.params.callbacks.onWrite(this.params.
|
|
12464
|
+
this.params.callbacks.onWrite(this.params.symbol, TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice), backtest);
|
|
12170
12465
|
}
|
|
12171
12466
|
if (!backtest) {
|
|
12172
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
12467
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
12173
12468
|
}
|
|
12174
12469
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
12175
12470
|
this._commitQueue.push({
|
|
@@ -12180,6 +12475,8 @@ class ClientStrategy {
|
|
|
12180
12475
|
cost,
|
|
12181
12476
|
totalEntries: this._pendingSignal._entry?.length ?? 1,
|
|
12182
12477
|
});
|
|
12478
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
12479
|
+
await PERSIST_STRATEGY_FN(this);
|
|
12183
12480
|
return true;
|
|
12184
12481
|
}
|
|
12185
12482
|
}
|
|
@@ -22920,32 +23217,32 @@ const heat_columns = [
|
|
|
22920
23217
|
{
|
|
22921
23218
|
key: "totalPnl",
|
|
22922
23219
|
label: "Total PNL",
|
|
22923
|
-
format: (data) => data.totalPnl !== null ?
|
|
23220
|
+
format: (data) => data.totalPnl !== null ? `${data.totalPnl.toFixed(2)}%` : "N/A",
|
|
22924
23221
|
isVisible: () => true,
|
|
22925
23222
|
},
|
|
22926
23223
|
{
|
|
22927
23224
|
key: "sharpeRatio",
|
|
22928
23225
|
label: "Sharpe",
|
|
22929
|
-
format: (data) => data.sharpeRatio !== null ?
|
|
23226
|
+
format: (data) => data.sharpeRatio !== null ? data.sharpeRatio.toFixed(3) : "N/A",
|
|
22930
23227
|
isVisible: () => true,
|
|
22931
23228
|
},
|
|
22932
23229
|
{
|
|
22933
23230
|
key: "annualizedSharpeRatio",
|
|
22934
23231
|
label: "Ann Sharpe",
|
|
22935
|
-
format: (data) => data.annualizedSharpeRatio !== null ?
|
|
23232
|
+
format: (data) => data.annualizedSharpeRatio !== null ? data.annualizedSharpeRatio.toFixed(3) : "N/A",
|
|
22936
23233
|
isVisible: () => true,
|
|
22937
23234
|
},
|
|
22938
23235
|
{
|
|
22939
23236
|
key: "certaintyRatio",
|
|
22940
23237
|
label: "Certainty",
|
|
22941
|
-
format: (data) => data.certaintyRatio !== null ?
|
|
23238
|
+
format: (data) => data.certaintyRatio !== null ? data.certaintyRatio.toFixed(3) : "N/A",
|
|
22942
23239
|
isVisible: () => true,
|
|
22943
23240
|
},
|
|
22944
23241
|
{
|
|
22945
23242
|
key: "expectedYearlyReturns",
|
|
22946
23243
|
label: "Exp Yearly",
|
|
22947
23244
|
format: (data) => data.expectedYearlyReturns !== null
|
|
22948
|
-
?
|
|
23245
|
+
? `${data.expectedYearlyReturns.toFixed(2)}%`
|
|
22949
23246
|
: "N/A",
|
|
22950
23247
|
isVisible: () => true,
|
|
22951
23248
|
},
|
|
@@ -22958,37 +23255,37 @@ const heat_columns = [
|
|
|
22958
23255
|
{
|
|
22959
23256
|
key: "profitFactor",
|
|
22960
23257
|
label: "PF",
|
|
22961
|
-
format: (data) => data.profitFactor !== null ?
|
|
23258
|
+
format: (data) => data.profitFactor !== null ? data.profitFactor.toFixed(3) : "N/A",
|
|
22962
23259
|
isVisible: () => true,
|
|
22963
23260
|
},
|
|
22964
23261
|
{
|
|
22965
23262
|
key: "expectancy",
|
|
22966
23263
|
label: "Expect",
|
|
22967
|
-
format: (data) => data.expectancy !== null ?
|
|
23264
|
+
format: (data) => data.expectancy !== null ? `${data.expectancy.toFixed(2)}%` : "N/A",
|
|
22968
23265
|
isVisible: () => true,
|
|
22969
23266
|
},
|
|
22970
23267
|
{
|
|
22971
23268
|
key: "winRate",
|
|
22972
23269
|
label: "WR",
|
|
22973
|
-
format: (data) => data.winRate !== null ?
|
|
23270
|
+
format: (data) => data.winRate !== null ? `${data.winRate.toFixed(2)}%` : "N/A",
|
|
22974
23271
|
isVisible: () => true,
|
|
22975
23272
|
},
|
|
22976
23273
|
{
|
|
22977
23274
|
key: "avgWin",
|
|
22978
23275
|
label: "Avg Win",
|
|
22979
|
-
format: (data) => data.avgWin !== null ?
|
|
23276
|
+
format: (data) => data.avgWin !== null ? `${data.avgWin.toFixed(2)}%` : "N/A",
|
|
22980
23277
|
isVisible: () => true,
|
|
22981
23278
|
},
|
|
22982
23279
|
{
|
|
22983
23280
|
key: "avgLoss",
|
|
22984
23281
|
label: "Avg Loss",
|
|
22985
|
-
format: (data) => data.avgLoss !== null ?
|
|
23282
|
+
format: (data) => data.avgLoss !== null ? `${data.avgLoss.toFixed(2)}%` : "N/A",
|
|
22986
23283
|
isVisible: () => true,
|
|
22987
23284
|
},
|
|
22988
23285
|
{
|
|
22989
23286
|
key: "maxDrawdown",
|
|
22990
23287
|
label: "Max DD",
|
|
22991
|
-
format: (data) => data.maxDrawdown !== null ?
|
|
23288
|
+
format: (data) => data.maxDrawdown !== null ? `${(-data.maxDrawdown).toFixed(2)}%` : "N/A",
|
|
22992
23289
|
isVisible: () => true,
|
|
22993
23290
|
},
|
|
22994
23291
|
{
|
|
@@ -23012,31 +23309,31 @@ const heat_columns = [
|
|
|
23012
23309
|
{
|
|
23013
23310
|
key: "avgPeakPnl",
|
|
23014
23311
|
label: "Avg Peak PNL",
|
|
23015
|
-
format: (data) => data.avgPeakPnl !== null ?
|
|
23312
|
+
format: (data) => data.avgPeakPnl !== null ? `${data.avgPeakPnl.toFixed(2)}%` : "N/A",
|
|
23016
23313
|
isVisible: () => true,
|
|
23017
23314
|
},
|
|
23018
23315
|
{
|
|
23019
23316
|
key: "avgFallPnl",
|
|
23020
23317
|
label: "Avg DD PNL",
|
|
23021
|
-
format: (data) => data.avgFallPnl !== null ?
|
|
23318
|
+
format: (data) => data.avgFallPnl !== null ? `${data.avgFallPnl.toFixed(2)}%` : "N/A",
|
|
23022
23319
|
isVisible: () => true,
|
|
23023
23320
|
},
|
|
23024
23321
|
{
|
|
23025
23322
|
key: "peakProfitPnl",
|
|
23026
23323
|
label: "Peak Profit PNL",
|
|
23027
|
-
format: (data) => data.peakProfitPnl !== null ?
|
|
23324
|
+
format: (data) => data.peakProfitPnl !== null ? `${data.peakProfitPnl.toFixed(2)}%` : "N/A",
|
|
23028
23325
|
isVisible: () => true,
|
|
23029
23326
|
},
|
|
23030
23327
|
{
|
|
23031
23328
|
key: "maxDrawdownPnl",
|
|
23032
23329
|
label: "Max DD PNL",
|
|
23033
|
-
format: (data) => data.maxDrawdownPnl !== null ?
|
|
23330
|
+
format: (data) => data.maxDrawdownPnl !== null ? `${data.maxDrawdownPnl.toFixed(2)}%` : "N/A",
|
|
23034
23331
|
isVisible: () => true,
|
|
23035
23332
|
},
|
|
23036
23333
|
{
|
|
23037
23334
|
key: "medianPnl",
|
|
23038
23335
|
label: "Median PNL",
|
|
23039
|
-
format: (data) => data.medianPnl !== null ?
|
|
23336
|
+
format: (data) => data.medianPnl !== null ? `${data.medianPnl.toFixed(2)}%` : "N/A",
|
|
23040
23337
|
isVisible: () => true,
|
|
23041
23338
|
},
|
|
23042
23339
|
{
|
|
@@ -23061,7 +23358,7 @@ const heat_columns = [
|
|
|
23061
23358
|
key: "avgConsecutiveWinPnl",
|
|
23062
23359
|
label: "Avg Win Streak PNL",
|
|
23063
23360
|
format: (data) => data.avgConsecutiveWinPnl !== null
|
|
23064
|
-
?
|
|
23361
|
+
? `${data.avgConsecutiveWinPnl.toFixed(2)}%`
|
|
23065
23362
|
: "N/A",
|
|
23066
23363
|
isVisible: () => true,
|
|
23067
23364
|
},
|
|
@@ -23069,7 +23366,7 @@ const heat_columns = [
|
|
|
23069
23366
|
key: "avgConsecutiveLossPnl",
|
|
23070
23367
|
label: "Avg Loss Streak PNL",
|
|
23071
23368
|
format: (data) => data.avgConsecutiveLossPnl !== null
|
|
23072
|
-
?
|
|
23369
|
+
? `${data.avgConsecutiveLossPnl.toFixed(2)}%`
|
|
23073
23370
|
: "N/A",
|
|
23074
23371
|
isVisible: () => true,
|
|
23075
23372
|
},
|
|
@@ -23082,7 +23379,7 @@ const heat_columns = [
|
|
|
23082
23379
|
{
|
|
23083
23380
|
key: "trendStrength",
|
|
23084
23381
|
label: "Trend %/d",
|
|
23085
|
-
format: (data) => data.trendStrength !== null ?
|
|
23382
|
+
format: (data) => data.trendStrength !== null ? `${data.trendStrength.toFixed(2)}%` : "N/A",
|
|
23086
23383
|
isVisible: () => true,
|
|
23087
23384
|
},
|
|
23088
23385
|
{
|
|
@@ -23135,25 +23432,25 @@ const heat_columns = [
|
|
|
23135
23432
|
{
|
|
23136
23433
|
key: "medianStepSize",
|
|
23137
23434
|
label: "Median Step",
|
|
23138
|
-
format: (data) => data.medianStepSize !== null ?
|
|
23435
|
+
format: (data) => data.medianStepSize !== null ? `${data.medianStepSize.toFixed(2)}%` : "N/A",
|
|
23139
23436
|
isVisible: () => true,
|
|
23140
23437
|
},
|
|
23141
23438
|
{
|
|
23142
23439
|
key: "sortinoRatio",
|
|
23143
23440
|
label: "Sortino",
|
|
23144
|
-
format: (data) => data.sortinoRatio !== null ?
|
|
23441
|
+
format: (data) => data.sortinoRatio !== null ? data.sortinoRatio.toFixed(3) : "N/A",
|
|
23145
23442
|
isVisible: () => true,
|
|
23146
23443
|
},
|
|
23147
23444
|
{
|
|
23148
23445
|
key: "calmarRatio",
|
|
23149
23446
|
label: "Calmar",
|
|
23150
|
-
format: (data) => data.calmarRatio !== null ?
|
|
23447
|
+
format: (data) => data.calmarRatio !== null ? data.calmarRatio.toFixed(3) : "N/A",
|
|
23151
23448
|
isVisible: () => true,
|
|
23152
23449
|
},
|
|
23153
23450
|
{
|
|
23154
23451
|
key: "recoveryFactor",
|
|
23155
23452
|
label: "Recovery",
|
|
23156
|
-
format: (data) => data.recoveryFactor !== null ?
|
|
23453
|
+
format: (data) => data.recoveryFactor !== null ? data.recoveryFactor.toFixed(3) : "N/A",
|
|
23157
23454
|
isVisible: () => true,
|
|
23158
23455
|
},
|
|
23159
23456
|
];
|
|
@@ -30329,28 +30626,28 @@ class HeatmapStorage {
|
|
|
30329
30626
|
`# Portfolio Heatmap: ${strategyName}`,
|
|
30330
30627
|
"",
|
|
30331
30628
|
`**Total Symbols:** ${data.totalSymbols}`,
|
|
30332
|
-
`**Portfolio PNL:** ${data.portfolioTotalPnl !== null ?
|
|
30333
|
-
`**Pooled Sharpe:** ${data.portfolioSharpeRatio !== null ?
|
|
30334
|
-
`**Annualized Sharpe:** ${data.portfolioAnnualizedSharpeRatio !== null ?
|
|
30335
|
-
`**Certainty Ratio:** ${data.portfolioCertaintyRatio !== null ?
|
|
30336
|
-
`**Expected Yearly Returns:** ${data.portfolioExpectedYearlyReturns !== null ?
|
|
30629
|
+
`**Portfolio PNL:** ${data.portfolioTotalPnl !== null ? `${data.portfolioTotalPnl.toFixed(2)} %` : "N/A"}`,
|
|
30630
|
+
`**Pooled Sharpe:** ${data.portfolioSharpeRatio !== null ? data.portfolioSharpeRatio.toFixed(3) : "N/A"}`,
|
|
30631
|
+
`**Annualized Sharpe:** ${data.portfolioAnnualizedSharpeRatio !== null ? data.portfolioAnnualizedSharpeRatio.toFixed(3) : "N/A"}`,
|
|
30632
|
+
`**Certainty Ratio:** ${data.portfolioCertaintyRatio !== null ? data.portfolioCertaintyRatio.toFixed(3) : "N/A"}`,
|
|
30633
|
+
`**Expected Yearly Returns:** ${data.portfolioExpectedYearlyReturns !== null ? `${data.portfolioExpectedYearlyReturns.toFixed(2)} %` : "N/A"}`,
|
|
30337
30634
|
`**Trades Per Year:** ${data.portfolioTradesPerYear !== null ? data.portfolioTradesPerYear.toFixed(1) : "N/A"}`,
|
|
30338
30635
|
`**Total Trades:** ${data.portfolioTotalTrades}`,
|
|
30339
|
-
`**Avg Peak PNL:** ${data.portfolioAvgPeakPnl !== null ?
|
|
30340
|
-
`**Avg Max Drawdown PNL:** ${data.portfolioAvgFallPnl !== null ?
|
|
30341
|
-
`**Peak Profit PNL:** ${data.portfolioPeakProfitPnl !== null ?
|
|
30342
|
-
`**Max Drawdown PNL:** ${data.portfolioMaxDrawdownPnl !== null ?
|
|
30343
|
-
`**Median PNL:** ${data.portfolioMedianPnl !== null ?
|
|
30636
|
+
`**Avg Peak PNL:** ${data.portfolioAvgPeakPnl !== null ? `${data.portfolioAvgPeakPnl.toFixed(2)} %` : "N/A"}`,
|
|
30637
|
+
`**Avg Max Drawdown PNL:** ${data.portfolioAvgFallPnl !== null ? `${data.portfolioAvgFallPnl.toFixed(2)} %` : "N/A"}`,
|
|
30638
|
+
`**Peak Profit PNL:** ${data.portfolioPeakProfitPnl !== null ? `${data.portfolioPeakProfitPnl.toFixed(2)} %` : "N/A"}`,
|
|
30639
|
+
`**Max Drawdown PNL:** ${data.portfolioMaxDrawdownPnl !== null ? `${data.portfolioMaxDrawdownPnl.toFixed(2)} %` : "N/A"}`,
|
|
30640
|
+
`**Median PNL:** ${data.portfolioMedianPnl !== null ? `${data.portfolioMedianPnl.toFixed(2)} %` : "N/A"}`,
|
|
30344
30641
|
`**Avg Duration:** ${data.portfolioAvgDuration !== null ? `${data.portfolioAvgDuration.toFixed(1)} min` : "N/A"}`,
|
|
30345
30642
|
`**Avg Win Duration:** ${data.portfolioAvgWinDuration !== null ? `${data.portfolioAvgWinDuration.toFixed(1)} min` : "N/A"}`,
|
|
30346
30643
|
`**Avg Loss Duration:** ${data.portfolioAvgLossDuration !== null ? `${data.portfolioAvgLossDuration.toFixed(1)} min` : "N/A"}`,
|
|
30347
|
-
`**Avg Consecutive Win PNL:** ${data.portfolioAvgConsecutiveWinPnl !== null ?
|
|
30348
|
-
`**Avg Consecutive Loss PNL:** ${data.portfolioAvgConsecutiveLossPnl !== null ?
|
|
30349
|
-
`**Standard Deviation Per Trade:** ${data.portfolioStdDev !== null ?
|
|
30350
|
-
`**Sortino Ratio:** ${data.portfolioSortinoRatio !== null ?
|
|
30351
|
-
`**Calmar Ratio:** ${data.portfolioCalmarRatio !== null ?
|
|
30352
|
-
`**Recovery Factor:** ${data.portfolioRecoveryFactor !== null ?
|
|
30353
|
-
`**Expectancy:** ${data.portfolioExpectancy !== null ?
|
|
30644
|
+
`**Avg Consecutive Win PNL:** ${data.portfolioAvgConsecutiveWinPnl !== null ? `${data.portfolioAvgConsecutiveWinPnl.toFixed(2)} %` : "N/A"}`,
|
|
30645
|
+
`**Avg Consecutive Loss PNL:** ${data.portfolioAvgConsecutiveLossPnl !== null ? `${data.portfolioAvgConsecutiveLossPnl.toFixed(2)} %` : "N/A"}`,
|
|
30646
|
+
`**Standard Deviation Per Trade:** ${data.portfolioStdDev !== null ? `${data.portfolioStdDev.toFixed(2)} %` : "N/A"}`,
|
|
30647
|
+
`**Sortino Ratio:** ${data.portfolioSortinoRatio !== null ? data.portfolioSortinoRatio.toFixed(3) : "N/A"}`,
|
|
30648
|
+
`**Calmar Ratio:** ${data.portfolioCalmarRatio !== null ? data.portfolioCalmarRatio.toFixed(3) : "N/A"}`,
|
|
30649
|
+
`**Recovery Factor:** ${data.portfolioRecoveryFactor !== null ? data.portfolioRecoveryFactor.toFixed(3) : "N/A"}`,
|
|
30650
|
+
`**Expectancy:** ${data.portfolioExpectancy !== null ? `${data.portfolioExpectancy.toFixed(2)} %` : "N/A"}`,
|
|
30354
30651
|
"",
|
|
30355
30652
|
table,
|
|
30356
30653
|
"",
|
|
@@ -67716,6 +68013,8 @@ exports.PersistStateAdapter = PersistStateAdapter;
|
|
|
67716
68013
|
exports.PersistStateInstance = PersistStateInstance;
|
|
67717
68014
|
exports.PersistStorageAdapter = PersistStorageAdapter;
|
|
67718
68015
|
exports.PersistStorageInstance = PersistStorageInstance;
|
|
68016
|
+
exports.PersistStrategyAdapter = PersistStrategyAdapter;
|
|
68017
|
+
exports.PersistStrategyInstance = PersistStrategyInstance;
|
|
67719
68018
|
exports.Position = Position;
|
|
67720
68019
|
exports.PositionSize = PositionSize;
|
|
67721
68020
|
exports.Recent = Recent;
|