backtest-kit 12.8.0 → 13.1.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 +862 -189
- package/build/index.mjs +859 -190
- package/package.json +86 -86
- package/types.d.ts +435 -2
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
|
*
|
|
@@ -6605,6 +6817,119 @@ const validateScheduledSignal = (signal, currentPrice) => {
|
|
|
6605
6817
|
}
|
|
6606
6818
|
};
|
|
6607
6819
|
|
|
6820
|
+
/**
|
|
6821
|
+
* Validates ISignalDto returned by getSignal, branching on the same logic as ClientStrategy GET_SIGNAL_FN.
|
|
6822
|
+
*
|
|
6823
|
+
* When priceOpen is provided:
|
|
6824
|
+
* - If currentPrice already reached priceOpen (shouldActivateImmediately) →
|
|
6825
|
+
* validates as pending: currentPrice must be between SL and TP
|
|
6826
|
+
* - Otherwise → validates as scheduled: priceOpen must be between SL and TP
|
|
6827
|
+
*
|
|
6828
|
+
* When priceOpen is absent:
|
|
6829
|
+
* - Validates as pending: currentPrice must be between SL and TP
|
|
6830
|
+
*
|
|
6831
|
+
* Checks:
|
|
6832
|
+
* - currentPrice is a finite positive number
|
|
6833
|
+
* - Common signal fields via validateCommonSignal (position, prices, TP/SL relationships, minuteEstimatedTime)
|
|
6834
|
+
* - Position-specific immediate-close protection (pending) or activation-close protection (scheduled)
|
|
6835
|
+
*
|
|
6836
|
+
* @param signal - Signal DTO returned by getSignal
|
|
6837
|
+
* @param currentPrice - Current market price at the moment of signal creation
|
|
6838
|
+
* @returns true if signal is valid, false if validation errors were found (errors logged to console.error)
|
|
6839
|
+
*/
|
|
6840
|
+
const validateSignal = (signal, currentPrice) => {
|
|
6841
|
+
const errors = [];
|
|
6842
|
+
// ЗАЩИТА ОТ NaN/Infinity: currentPrice должна быть конечным числом
|
|
6843
|
+
{
|
|
6844
|
+
if (typeof currentPrice !== "number") {
|
|
6845
|
+
errors.push(`currentPrice must be a number type, got ${currentPrice} (${typeof currentPrice})`);
|
|
6846
|
+
}
|
|
6847
|
+
if (!isFinite(currentPrice)) {
|
|
6848
|
+
errors.push(`currentPrice must be a finite number, got ${currentPrice} (${typeof currentPrice})`);
|
|
6849
|
+
}
|
|
6850
|
+
if (isFinite(currentPrice) && currentPrice <= 0) {
|
|
6851
|
+
errors.push(`currentPrice must be positive, got ${currentPrice}`);
|
|
6852
|
+
}
|
|
6853
|
+
}
|
|
6854
|
+
if (errors.length > 0) {
|
|
6855
|
+
console.error(`Invalid signal for ${signal.position} position (${signal.symbol || "empty symbol"}):\n${errors.join("\n")}`);
|
|
6856
|
+
return false;
|
|
6857
|
+
}
|
|
6858
|
+
try {
|
|
6859
|
+
validateCommonSignal(signal);
|
|
6860
|
+
}
|
|
6861
|
+
catch (error) {
|
|
6862
|
+
console.error(getErrorMessage(error));
|
|
6863
|
+
return false;
|
|
6864
|
+
}
|
|
6865
|
+
// Определяем режим валидации по той же логике что в GET_SIGNAL_FN:
|
|
6866
|
+
// - нет priceOpen → pending (открывается по currentPrice)
|
|
6867
|
+
// - priceOpen задан и уже достигнут (shouldActivateImmediately) → pending
|
|
6868
|
+
// - priceOpen задан и ещё не достигнут → scheduled
|
|
6869
|
+
const hasPriceOpen = signal.priceOpen !== undefined;
|
|
6870
|
+
const shouldActivateImmediately = hasPriceOpen && ((signal.position === "long" && currentPrice <= signal.priceOpen) ||
|
|
6871
|
+
(signal.position === "short" && currentPrice >= signal.priceOpen));
|
|
6872
|
+
const isScheduled = hasPriceOpen && !shouldActivateImmediately;
|
|
6873
|
+
if (isScheduled) {
|
|
6874
|
+
// Scheduled: priceOpen должен быть между SL и TP (активация не даст моментального закрытия)
|
|
6875
|
+
if (signal.position === "long") {
|
|
6876
|
+
if (isFinite(signal.priceOpen)) {
|
|
6877
|
+
if (signal.priceOpen <= signal.priceStopLoss) {
|
|
6878
|
+
errors.push(`Long scheduled: priceOpen (${signal.priceOpen}) <= priceStopLoss (${signal.priceStopLoss}). ` +
|
|
6879
|
+
`Signal would be immediately cancelled on activation. Cannot activate position that is already stopped out.`);
|
|
6880
|
+
}
|
|
6881
|
+
if (signal.priceOpen >= signal.priceTakeProfit) {
|
|
6882
|
+
errors.push(`Long scheduled: priceOpen (${signal.priceOpen}) >= priceTakeProfit (${signal.priceTakeProfit}). ` +
|
|
6883
|
+
`Signal would close immediately on activation. This is logically impossible for LONG position.`);
|
|
6884
|
+
}
|
|
6885
|
+
}
|
|
6886
|
+
}
|
|
6887
|
+
if (signal.position === "short") {
|
|
6888
|
+
if (isFinite(signal.priceOpen)) {
|
|
6889
|
+
if (signal.priceOpen >= signal.priceStopLoss) {
|
|
6890
|
+
errors.push(`Short scheduled: priceOpen (${signal.priceOpen}) >= priceStopLoss (${signal.priceStopLoss}). ` +
|
|
6891
|
+
`Signal would be immediately cancelled on activation. Cannot activate position that is already stopped out.`);
|
|
6892
|
+
}
|
|
6893
|
+
if (signal.priceOpen <= signal.priceTakeProfit) {
|
|
6894
|
+
errors.push(`Short scheduled: priceOpen (${signal.priceOpen}) <= priceTakeProfit (${signal.priceTakeProfit}). ` +
|
|
6895
|
+
`Signal would close immediately on activation. This is logically impossible for SHORT position.`);
|
|
6896
|
+
}
|
|
6897
|
+
}
|
|
6898
|
+
}
|
|
6899
|
+
}
|
|
6900
|
+
else {
|
|
6901
|
+
// Pending: currentPrice должна быть между SL и TP (позиция не закроется сразу после открытия)
|
|
6902
|
+
if (signal.position === "long") {
|
|
6903
|
+
if (isFinite(currentPrice)) {
|
|
6904
|
+
if (currentPrice <= signal.priceStopLoss) {
|
|
6905
|
+
errors.push(`Long immediate: currentPrice (${currentPrice}) <= priceStopLoss (${signal.priceStopLoss}). ` +
|
|
6906
|
+
`Signal would be immediately closed by stop loss. Cannot open position that is already stopped out.`);
|
|
6907
|
+
}
|
|
6908
|
+
if (currentPrice >= signal.priceTakeProfit) {
|
|
6909
|
+
errors.push(`Long immediate: currentPrice (${currentPrice}) >= priceTakeProfit (${signal.priceTakeProfit}). ` +
|
|
6910
|
+
`Signal would be immediately closed by take profit. The profit opportunity has already passed.`);
|
|
6911
|
+
}
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6914
|
+
if (signal.position === "short") {
|
|
6915
|
+
if (isFinite(currentPrice)) {
|
|
6916
|
+
if (currentPrice >= signal.priceStopLoss) {
|
|
6917
|
+
errors.push(`Short immediate: currentPrice (${currentPrice}) >= priceStopLoss (${signal.priceStopLoss}). ` +
|
|
6918
|
+
`Signal would be immediately closed by stop loss. Cannot open position that is already stopped out.`);
|
|
6919
|
+
}
|
|
6920
|
+
if (currentPrice <= signal.priceTakeProfit) {
|
|
6921
|
+
errors.push(`Short immediate: currentPrice (${currentPrice}) <= priceTakeProfit (${signal.priceTakeProfit}). ` +
|
|
6922
|
+
`Signal would be immediately closed by take profit. The profit opportunity has already passed.`);
|
|
6923
|
+
}
|
|
6924
|
+
}
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6927
|
+
if (errors.length > 0) {
|
|
6928
|
+
console.error(`Invalid signal for ${signal.position} position (${signal.symbol || "empty symbol"}):\n${errors.join("\n")}`);
|
|
6929
|
+
}
|
|
6930
|
+
return !errors.length;
|
|
6931
|
+
};
|
|
6932
|
+
|
|
6608
6933
|
const INTERVAL_MINUTES$8 = {
|
|
6609
6934
|
"1m": 1,
|
|
6610
6935
|
"3m": 3,
|
|
@@ -6771,6 +7096,9 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
6771
7096
|
{
|
|
6772
7097
|
self._commitQueue = [];
|
|
6773
7098
|
}
|
|
7099
|
+
// Persist the now-empty queue so a crash after draining does not replay commits
|
|
7100
|
+
// that were already forwarded to the broker on the next restart.
|
|
7101
|
+
await PERSIST_STRATEGY_FN(self);
|
|
6774
7102
|
if (!self._pendingSignal) {
|
|
6775
7103
|
return;
|
|
6776
7104
|
}
|
|
@@ -7068,11 +7396,27 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
7068
7396
|
self._lastSignalTimestamp = alignedTime;
|
|
7069
7397
|
}
|
|
7070
7398
|
const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7399
|
+
// PRIORITY: a user-queued createPending/createScheduled DTO (set out of async-hooks
|
|
7400
|
+
// context) takes precedence over params.getSignal. When present it is consumed once
|
|
7401
|
+
// here — the slot is cleared and the snapshot rewritten — and then flows through the
|
|
7402
|
+
// exact same pipeline (risk check, priceOpen branching, onSignalSync on open) as a
|
|
7403
|
+
// signal returned by getSignal would.
|
|
7404
|
+
let signal;
|
|
7405
|
+
{
|
|
7406
|
+
if (!self._userSignal) {
|
|
7407
|
+
const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
|
|
7408
|
+
signal = await Promise.race([
|
|
7409
|
+
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when, currentPrice),
|
|
7410
|
+
sleep(timeoutMs).then(() => TIMEOUT_SYMBOL$1),
|
|
7411
|
+
]);
|
|
7412
|
+
}
|
|
7413
|
+
if (self._userSignal) {
|
|
7414
|
+
signal = self._userSignal;
|
|
7415
|
+
self._userSignal = null;
|
|
7416
|
+
await PERSIST_STRATEGY_FN(self);
|
|
7417
|
+
}
|
|
7418
|
+
self._userSignal = null;
|
|
7419
|
+
}
|
|
7076
7420
|
if (typeof signal === "symbol") {
|
|
7077
7421
|
throw new Error(`Timeout for ${self.params.method.context.strategyName} symbol=${self.params.execution.context.symbol}`);
|
|
7078
7422
|
}
|
|
@@ -7227,6 +7571,21 @@ const WAIT_FOR_INIT_FN$4 = async (self) => {
|
|
|
7227
7571
|
self._lastPendingId = recentSignal.id;
|
|
7228
7572
|
}
|
|
7229
7573
|
}
|
|
7574
|
+
// Read deferred strategy state (commit queue + deferred user actions) so any
|
|
7575
|
+
// confirmed-but-not-yet-forwarded broker operation survives a live crash and is
|
|
7576
|
+
// re-drained on the next tick. Read here, before the pending restore which may
|
|
7577
|
+
// early-return on exchange/strategy mismatch.
|
|
7578
|
+
const strategyData = await PersistStrategyAdapter.readStrategyData(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
7579
|
+
if (strategyData) {
|
|
7580
|
+
// Deferred user actions are restored unconditionally: a deferred close belongs to
|
|
7581
|
+
// an already-cleared _pendingSignal, and cancel/activate belong to the scheduled
|
|
7582
|
+
// signal — none of them are tied to the currently-pending signal's id. A queued
|
|
7583
|
+
// createSignal is a not-yet-consumed signal source and likewise stands on its own.
|
|
7584
|
+
self._userSignal = strategyData.createSignal;
|
|
7585
|
+
self._closedSignal = strategyData.closedSignal;
|
|
7586
|
+
self._cancelledSignal = strategyData.cancelledSignal;
|
|
7587
|
+
self._activatedSignal = strategyData.activatedSignal;
|
|
7588
|
+
}
|
|
7230
7589
|
// Restore pending signal
|
|
7231
7590
|
const pendingSignal = await PersistSignalAdapter.readSignalData(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
7232
7591
|
if (pendingSignal) {
|
|
@@ -7243,6 +7602,14 @@ const WAIT_FOR_INIT_FN$4 = async (self) => {
|
|
|
7243
7602
|
pendingSignal.minuteEstimatedTime = Infinity;
|
|
7244
7603
|
}
|
|
7245
7604
|
self._pendingSignal = pendingSignal;
|
|
7605
|
+
// Restore the commit queue only if the snapshot belongs to this exact pending
|
|
7606
|
+
// signal (pendingSignalId === restored id). The queue holds confirmed-but-not-yet
|
|
7607
|
+
// forwarded broker ops (average-buy / partial-* / trailing-* / breakeven) that are
|
|
7608
|
+
// always tied to the active position; a mismatch means the snapshot is stale and
|
|
7609
|
+
// the queue is dropped to avoid replaying ops against the wrong position.
|
|
7610
|
+
if (strategyData && strategyData.pendingSignalId === pendingSignal.id) {
|
|
7611
|
+
self._commitQueue = strategyData.commitQueue ?? [];
|
|
7612
|
+
}
|
|
7246
7613
|
// Call onActive callback for restored signal
|
|
7247
7614
|
const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
|
|
7248
7615
|
const currentTime = self.params.execution.context.when.getTime();
|
|
@@ -7276,6 +7643,30 @@ const WAIT_FOR_DISPOSE_FN$1 = async (self) => {
|
|
|
7276
7643
|
self.params.logger.debug("ClientStrategy dispose");
|
|
7277
7644
|
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
7645
|
};
|
|
7646
|
+
/**
|
|
7647
|
+
* Persists the deferred strategy state snapshot (commit queue + deferred user actions)
|
|
7648
|
+
* to disk in live mode.
|
|
7649
|
+
*
|
|
7650
|
+
* These fields carry confirmed-but-not-yet-forwarded broker operations and survive the
|
|
7651
|
+
* gap between ticks (drained at the start of the next tick). Without persistence a live
|
|
7652
|
+
* crash in that window silently loses the pending broker operation while _pendingSignal
|
|
7653
|
+
* (already mutated and saved) claims it happened. Skipped in backtest mode.
|
|
7654
|
+
*
|
|
7655
|
+
* @param self - ClientStrategy instance
|
|
7656
|
+
*/
|
|
7657
|
+
const PERSIST_STRATEGY_FN = async (self) => {
|
|
7658
|
+
if (self.params.backtest) {
|
|
7659
|
+
return;
|
|
7660
|
+
}
|
|
7661
|
+
await PersistStrategyAdapter.writeStrategyData({
|
|
7662
|
+
pendingSignalId: self._pendingSignal?.id ?? null,
|
|
7663
|
+
createSignal: self._userSignal,
|
|
7664
|
+
commitQueue: self._commitQueue,
|
|
7665
|
+
closedSignal: self._closedSignal,
|
|
7666
|
+
cancelledSignal: self._cancelledSignal,
|
|
7667
|
+
activatedSignal: self._activatedSignal,
|
|
7668
|
+
}, self.params.symbol, self.params.strategyName, self.params.exchangeName);
|
|
7669
|
+
};
|
|
7279
7670
|
const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
|
|
7280
7671
|
// Initialize partial array if not present
|
|
7281
7672
|
if (!signal._partial)
|
|
@@ -8900,14 +9291,15 @@ const CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, signal, averagePrice, c
|
|
|
8900
9291
|
const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, averagePrice, closeTimestamp) => {
|
|
8901
9292
|
const syncCloseAllowed = await CALL_SIGNAL_SYNC_CLOSE_FN(closeTimestamp, averagePrice, "closed", closedSignal, self);
|
|
8902
9293
|
if (!syncCloseAllowed) {
|
|
8903
|
-
|
|
9294
|
+
// Sync close rejected (e.g. broker rejected the order) — keep _closedSignal intact
|
|
9295
|
+
// and return null so the candle loop re-attempts on the next candle. Mirrors live
|
|
9296
|
+
// tick, which keeps _closedSignal and returns idle on a rejected user close,
|
|
9297
|
+
// re-trying on the following tick.
|
|
9298
|
+
self.params.logger.info("ClientStrategy backtest: user-closed signal rejected by sync, will retry on next candle", {
|
|
8904
9299
|
symbol: self.params.execution.context.symbol,
|
|
8905
9300
|
signalId: closedSignal.id,
|
|
8906
9301
|
});
|
|
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.`);
|
|
9302
|
+
return null;
|
|
8911
9303
|
}
|
|
8912
9304
|
self._closedSignal = null;
|
|
8913
9305
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", closedSignal, averagePrice);
|
|
@@ -9159,7 +9551,15 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles, frameEnd
|
|
|
9159
9551
|
}
|
|
9160
9552
|
// КРИТИЧНО: Проверяем был ли сигнал закрыт пользователем через closePending()
|
|
9161
9553
|
if (self._closedSignal) {
|
|
9162
|
-
|
|
9554
|
+
const userCloseResult = await CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN(self, self._closedSignal, averagePrice, currentCandleTimestamp);
|
|
9555
|
+
// Sync close accepted — position closed, return. If rejected, _closedSignal is
|
|
9556
|
+
// kept and we skip this candle: live tick returns idle on a rejected user close
|
|
9557
|
+
// (it does NOT fall into the TP/SL or active-monitoring path), so the candle is
|
|
9558
|
+
// skipped here and the close is re-attempted on the next candle.
|
|
9559
|
+
if (userCloseResult) {
|
|
9560
|
+
return userCloseResult;
|
|
9561
|
+
}
|
|
9562
|
+
continue;
|
|
9163
9563
|
}
|
|
9164
9564
|
let shouldClose = false;
|
|
9165
9565
|
let closeReason;
|
|
@@ -9210,7 +9610,19 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles, frameEnd
|
|
|
9210
9610
|
else {
|
|
9211
9611
|
closePrice = averagePrice; // time_expired uses VWAP
|
|
9212
9612
|
}
|
|
9213
|
-
|
|
9613
|
+
const closeResult = await CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN(self, signal, closePrice, closeReason, currentCandleTimestamp);
|
|
9614
|
+
// Sync close accepted — position closed, return.
|
|
9615
|
+
if (closeResult) {
|
|
9616
|
+
return closeResult;
|
|
9617
|
+
}
|
|
9618
|
+
// Sync close rejected (e.g. broker rejected the order) — _pendingSignal is left
|
|
9619
|
+
// intact (not cleared on rejection). Do NOT return/continue: fall through to the
|
|
9620
|
+
// active-monitoring block below so this candle is processed exactly like live
|
|
9621
|
+
// tick, which runs RETURN_PENDING_SIGNAL_ACTIVE_FN when CLOSE_PENDING_SIGNAL_FN
|
|
9622
|
+
// returns null (updates _peak/_fall, fires active ping / breakeven / partial
|
|
9623
|
+
// callbacks, drains the commit queue). The close is re-attempted on the next
|
|
9624
|
+
// candle; for time_expired this eventually reaches the loop-exhausted close
|
|
9625
|
+
// (which throws if still rejected).
|
|
9214
9626
|
}
|
|
9215
9627
|
// Call onPartialProfit/onPartialLoss callbacks during backtest candle processing
|
|
9216
9628
|
// Calculate percentage of path to TP/SL
|
|
@@ -9384,6 +9796,13 @@ class ClientStrategy {
|
|
|
9384
9796
|
this._cancelledSignal = null;
|
|
9385
9797
|
this._closedSignal = null;
|
|
9386
9798
|
this._activatedSignal = null;
|
|
9799
|
+
/**
|
|
9800
|
+
* User-supplied signal DTO to be consumed by the next GET_SIGNAL_FN tick instead of
|
|
9801
|
+
* params.getSignal. Set via createSignal. When non-null, params.getSignal is NOT called
|
|
9802
|
+
* and the existing pipeline (priceOpen decides pending vs scheduled, onSignalSync on open)
|
|
9803
|
+
* is reused.
|
|
9804
|
+
*/
|
|
9805
|
+
this._userSignal = null;
|
|
9387
9806
|
/** Queue for commit events to be processed in tick()/backtest() with proper timestamp */
|
|
9388
9807
|
this._commitQueue = [];
|
|
9389
9808
|
/**
|
|
@@ -9632,7 +10051,7 @@ class ClientStrategy {
|
|
|
9632
10051
|
async getStopped(symbol) {
|
|
9633
10052
|
this.params.logger.debug("ClientStrategy getStopped", {
|
|
9634
10053
|
symbol,
|
|
9635
|
-
strategyName: this.params.
|
|
10054
|
+
strategyName: this.params.strategyName,
|
|
9636
10055
|
});
|
|
9637
10056
|
return this._isStopped;
|
|
9638
10057
|
}
|
|
@@ -10315,6 +10734,8 @@ class ClientStrategy {
|
|
|
10315
10734
|
if (this._cancelledSignal) {
|
|
10316
10735
|
const cancelledSignal = this._cancelledSignal;
|
|
10317
10736
|
this._cancelledSignal = null; // Clear after emitting
|
|
10737
|
+
// Persist the cleared deferred state so the drained flag is not replayed on restart
|
|
10738
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10318
10739
|
this.params.logger.info("ClientStrategy tick: scheduled signal was cancelled", {
|
|
10319
10740
|
symbol: this.params.execution.context.symbol,
|
|
10320
10741
|
signalId: cancelledSignal.id,
|
|
@@ -10373,6 +10794,8 @@ class ClientStrategy {
|
|
|
10373
10794
|
return await RETURN_IDLE_FN(this, currentPrice);
|
|
10374
10795
|
}
|
|
10375
10796
|
this._closedSignal = null; // Clear only after sync confirmed
|
|
10797
|
+
// Persist the cleared deferred state so the drained flag is not replayed on restart
|
|
10798
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10376
10799
|
this.params.logger.info("ClientStrategy tick: pending signal was closed", {
|
|
10377
10800
|
symbol: this.params.execution.context.symbol,
|
|
10378
10801
|
signalId: closedSignal.id,
|
|
@@ -10428,6 +10851,8 @@ class ClientStrategy {
|
|
|
10428
10851
|
const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
|
|
10429
10852
|
const activatedSignal = this._activatedSignal;
|
|
10430
10853
|
this._activatedSignal = null; // Clear after emitting
|
|
10854
|
+
// Persist the cleared deferred state so the drained flag is not replayed on restart
|
|
10855
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10431
10856
|
this.params.logger.info("ClientStrategy tick: scheduled signal was activated", {
|
|
10432
10857
|
symbol: this.params.execution.context.symbol,
|
|
10433
10858
|
signalId: activatedSignal.id,
|
|
@@ -10685,18 +11110,20 @@ class ClientStrategy {
|
|
|
10685
11110
|
const currentPrice = await this.params.exchange.getAveragePrice(symbol);
|
|
10686
11111
|
const closedSignal = this._closedSignal;
|
|
10687
11112
|
const closeTimestamp = this.params.execution.context.when.getTime();
|
|
10688
|
-
// Sync close: if external system rejects —
|
|
11113
|
+
// Sync close: if external system rejects — keep the close pending and re-attempt
|
|
11114
|
+
// it inside the candle loop below (PROCESS_PENDING_SIGNAL_CANDLES_FN handles
|
|
11115
|
+
// _closedSignal per candle). Mirrors live tick, which keeps _closedSignal and
|
|
11116
|
+
// retries on the next tick instead of failing.
|
|
10689
11117
|
const syncCloseAllowed = await CALL_SIGNAL_SYNC_CLOSE_FN(closeTimestamp, currentPrice, "closed", closedSignal, this);
|
|
10690
11118
|
if (!syncCloseAllowed) {
|
|
10691
|
-
this.params.logger.info("ClientStrategy backtest: user-closed signal rejected by sync, will retry", {
|
|
11119
|
+
this.params.logger.info("ClientStrategy backtest: user-closed signal rejected by sync, will retry in candle loop", {
|
|
10692
11120
|
symbol: this.params.execution.context.symbol,
|
|
10693
11121
|
signalId: closedSignal.id,
|
|
10694
11122
|
});
|
|
10695
|
-
// Restore _pendingSignal so
|
|
10696
|
-
|
|
11123
|
+
// Restore _pendingSignal so the candle loop processes the position normally;
|
|
11124
|
+
// _closedSignal is kept so the loop re-attempts the close on each candle.
|
|
10697
11125
|
this._pendingSignal = closedSignal;
|
|
10698
|
-
|
|
10699
|
-
`Retry backtest() with new candle data.`);
|
|
11126
|
+
return await PROCESS_PENDING_SIGNAL_CANDLES_FN(this, this._pendingSignal, candles, frameEndTime);
|
|
10700
11127
|
}
|
|
10701
11128
|
this._closedSignal = null; // Clear only after sync confirmed
|
|
10702
11129
|
// Emit commit with correct timestamp from backtest context
|
|
@@ -10878,7 +11305,7 @@ class ClientStrategy {
|
|
|
10878
11305
|
if (backtest) {
|
|
10879
11306
|
return;
|
|
10880
11307
|
}
|
|
10881
|
-
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.
|
|
11308
|
+
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
10882
11309
|
}
|
|
10883
11310
|
/**
|
|
10884
11311
|
* Cancels the scheduled signal without stopping the strategy.
|
|
@@ -10922,7 +11349,9 @@ class ClientStrategy {
|
|
|
10922
11349
|
// Commit will be emitted in backtest() with correct candle timestamp
|
|
10923
11350
|
return;
|
|
10924
11351
|
}
|
|
10925
|
-
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.
|
|
11352
|
+
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
11353
|
+
// Persist deferred _cancelledSignal so a crash before the next tick does not lose it
|
|
11354
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10926
11355
|
// Commit will be emitted in tick() with correct currentTime
|
|
10927
11356
|
}
|
|
10928
11357
|
/**
|
|
@@ -10973,7 +11402,9 @@ class ClientStrategy {
|
|
|
10973
11402
|
// Commit will be emitted AFTER successful risk check in PROCESS_SCHEDULED_SIGNAL_CANDLES_FN
|
|
10974
11403
|
return;
|
|
10975
11404
|
}
|
|
10976
|
-
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.
|
|
11405
|
+
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
11406
|
+
// Persist deferred _activatedSignal so a crash before the next tick does not lose it
|
|
11407
|
+
await PERSIST_STRATEGY_FN(this);
|
|
10977
11408
|
// Commit will be emitted AFTER successful risk check in tick()
|
|
10978
11409
|
}
|
|
10979
11410
|
/**
|
|
@@ -11018,8 +11449,92 @@ class ClientStrategy {
|
|
|
11018
11449
|
return;
|
|
11019
11450
|
}
|
|
11020
11451
|
await PersistSignalAdapter.writeSignalData(this._pendingSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
11452
|
+
// Persist deferred _closedSignal so a crash before the next tick does not lose it
|
|
11453
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11021
11454
|
// Commit will be emitted in tick() with correct currentTime
|
|
11022
11455
|
}
|
|
11456
|
+
/**
|
|
11457
|
+
* Queues a user-supplied signal DTO to be consumed by the next tick instead of
|
|
11458
|
+
* params.getSignal.
|
|
11459
|
+
*
|
|
11460
|
+
* Works OUT of the async-hooks execution context (uses this.params.symbol directly).
|
|
11461
|
+
* priceOpen decides the outcome in the existing pipeline: when omitted the position opens
|
|
11462
|
+
* immediately at currentPrice; when provided the pipeline opens immediately if priceOpen is
|
|
11463
|
+
* already reached, otherwise registers a scheduled (priceOpen-awaiting) signal. On the next
|
|
11464
|
+
* tick GET_SIGNAL_FN consumes _userSignal and runs the normal pipeline, so onSignalSync
|
|
11465
|
+
* delivery is checked by OPEN_NEW_PENDING_SIGNAL_FN exactly as for a getSignal-produced signal.
|
|
11466
|
+
*
|
|
11467
|
+
* Validation (BEFORE any state mutation — a rejected call stores nothing):
|
|
11468
|
+
* - The DTO must pass the intrinsic shape/price checks.
|
|
11469
|
+
* - There must be NO signal already in flight: no active pending signal, no scheduled signal,
|
|
11470
|
+
* no already-queued createSignal, and no deferred activate/close/cancel awaiting drain.
|
|
11471
|
+
* Creating a new signal on top of an existing one is rejected.
|
|
11472
|
+
*
|
|
11473
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
11474
|
+
* @param currentPrice - Current market price (priceOpen fallback for immediate signals)
|
|
11475
|
+
* @param dto - Signal DTO to open (priceOpen optional)
|
|
11476
|
+
* @returns Promise that resolves when the DTO is queued (and persisted in live mode)
|
|
11477
|
+
* @throws {Error} If the DTO is invalid or a signal/deferred action is already in flight
|
|
11478
|
+
*/
|
|
11479
|
+
async createSignal(symbol, currentPrice, dto) {
|
|
11480
|
+
this.params.logger.debug("ClientStrategy createSignal", { symbol, currentPrice });
|
|
11481
|
+
// Validate BEFORE mutating state — a bad DTO or a busy strategy must store nothing.
|
|
11482
|
+
// Reuse validateSignal (the canonical getSignal-output validator, branching
|
|
11483
|
+
// pending-vs-scheduled by the same rule as GET_SIGNAL_FN). It forwards to
|
|
11484
|
+
// validateCommonSignal which requires priceOpen and minuteEstimatedTime, so normalize the
|
|
11485
|
+
// DTO to the defaults GET_SIGNAL_FN would apply (priceOpen → currentPrice for an immediate
|
|
11486
|
+
// signal, minuteEstimatedTime → CC_MAX_SIGNAL_LIFETIME_MINUTES). The original dto stored in
|
|
11487
|
+
// _userSignal is left untouched so GET_SIGNAL_FN applies its own defaults on consume.
|
|
11488
|
+
if (!validateSignal({
|
|
11489
|
+
...dto,
|
|
11490
|
+
priceOpen: dto.priceOpen ?? currentPrice,
|
|
11491
|
+
minuteEstimatedTime: dto.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
|
|
11492
|
+
}, currentPrice)) {
|
|
11493
|
+
throw new Error(`ClientStrategy createSignal: invalid signal DTO for symbol=${symbol}`);
|
|
11494
|
+
}
|
|
11495
|
+
// Reject if any signal is already in flight or any deferred action is awaiting drain.
|
|
11496
|
+
if (this._pendingSignal) {
|
|
11497
|
+
throw new Error(`ClientStrategy createSignal: a pending signal already exists for symbol=${symbol}`);
|
|
11498
|
+
}
|
|
11499
|
+
if (this._scheduledSignal) {
|
|
11500
|
+
throw new Error(`ClientStrategy createSignal: a scheduled signal already exists for symbol=${symbol}`);
|
|
11501
|
+
}
|
|
11502
|
+
if (this._userSignal) {
|
|
11503
|
+
throw new Error(`ClientStrategy createSignal: a signal is already queued for creation for symbol=${symbol}`);
|
|
11504
|
+
}
|
|
11505
|
+
if (this._activatedSignal) {
|
|
11506
|
+
throw new Error(`ClientStrategy createSignal: a scheduled activation is pending for symbol=${symbol}`);
|
|
11507
|
+
}
|
|
11508
|
+
if (this._closedSignal) {
|
|
11509
|
+
throw new Error(`ClientStrategy createSignal: a pending close is awaiting for symbol=${symbol}`);
|
|
11510
|
+
}
|
|
11511
|
+
if (this._cancelledSignal) {
|
|
11512
|
+
throw new Error(`ClientStrategy createSignal: a scheduled cancel is awaiting for symbol=${symbol}`);
|
|
11513
|
+
}
|
|
11514
|
+
this._userSignal = dto;
|
|
11515
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11516
|
+
}
|
|
11517
|
+
/**
|
|
11518
|
+
* Returns the deferred strategy-state snapshot exactly as it would be written to persist on
|
|
11519
|
+
* this iteration: the in-memory _userSignal, _commitQueue and deferred user-action flags,
|
|
11520
|
+
* plus the current pending signal id.
|
|
11521
|
+
*
|
|
11522
|
+
* Synchronous in-memory read (no disk access), so it works OUT of the async-hooks context.
|
|
11523
|
+
*
|
|
11524
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
11525
|
+
* @returns The current StrategyData snapshot held in memory
|
|
11526
|
+
*/
|
|
11527
|
+
async getStatus(symbol) {
|
|
11528
|
+
this.params.logger.debug("ClientStrategy getStatus", { symbol });
|
|
11529
|
+
return {
|
|
11530
|
+
pendingSignalId: this._pendingSignal?.id ?? null,
|
|
11531
|
+
createSignal: this._userSignal,
|
|
11532
|
+
commitQueue: this._commitQueue,
|
|
11533
|
+
closedSignal: this._closedSignal,
|
|
11534
|
+
cancelledSignal: this._cancelledSignal,
|
|
11535
|
+
activatedSignal: this._activatedSignal,
|
|
11536
|
+
};
|
|
11537
|
+
}
|
|
11023
11538
|
/**
|
|
11024
11539
|
* Validates preconditions for partialProfit without mutating state.
|
|
11025
11540
|
*
|
|
@@ -11188,10 +11703,10 @@ class ClientStrategy {
|
|
|
11188
11703
|
});
|
|
11189
11704
|
// Call onWrite callback for testing persist storage
|
|
11190
11705
|
if (this.params.callbacks?.onWrite) {
|
|
11191
|
-
this.params.callbacks.onWrite(this.params.
|
|
11706
|
+
this.params.callbacks.onWrite(this.params.symbol, TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice), backtest);
|
|
11192
11707
|
}
|
|
11193
11708
|
if (!backtest) {
|
|
11194
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
11709
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11195
11710
|
}
|
|
11196
11711
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11197
11712
|
this._commitQueue.push({
|
|
@@ -11201,6 +11716,8 @@ class ClientStrategy {
|
|
|
11201
11716
|
percentToClose,
|
|
11202
11717
|
currentPrice,
|
|
11203
11718
|
});
|
|
11719
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
11720
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11204
11721
|
return true;
|
|
11205
11722
|
}
|
|
11206
11723
|
/**
|
|
@@ -11371,10 +11888,10 @@ class ClientStrategy {
|
|
|
11371
11888
|
});
|
|
11372
11889
|
// Call onWrite callback for testing persist storage
|
|
11373
11890
|
if (this.params.callbacks?.onWrite) {
|
|
11374
|
-
this.params.callbacks.onWrite(this.params.
|
|
11891
|
+
this.params.callbacks.onWrite(this.params.symbol, TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice), backtest);
|
|
11375
11892
|
}
|
|
11376
11893
|
if (!backtest) {
|
|
11377
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
11894
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11378
11895
|
}
|
|
11379
11896
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11380
11897
|
this._commitQueue.push({
|
|
@@ -11384,6 +11901,8 @@ class ClientStrategy {
|
|
|
11384
11901
|
percentToClose,
|
|
11385
11902
|
currentPrice,
|
|
11386
11903
|
});
|
|
11904
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
11905
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11387
11906
|
return true;
|
|
11388
11907
|
}
|
|
11389
11908
|
/**
|
|
@@ -11570,10 +12089,10 @@ class ClientStrategy {
|
|
|
11570
12089
|
// Call onWrite callback for testing persist storage
|
|
11571
12090
|
if (this.params.callbacks?.onWrite) {
|
|
11572
12091
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice);
|
|
11573
|
-
this.params.callbacks.onWrite(this.params.
|
|
12092
|
+
this.params.callbacks.onWrite(this.params.symbol, publicSignal, backtest);
|
|
11574
12093
|
}
|
|
11575
12094
|
if (!backtest) {
|
|
11576
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
12095
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11577
12096
|
}
|
|
11578
12097
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11579
12098
|
this._commitQueue.push({
|
|
@@ -11582,6 +12101,8 @@ class ClientStrategy {
|
|
|
11582
12101
|
backtest,
|
|
11583
12102
|
currentPrice,
|
|
11584
12103
|
});
|
|
12104
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
12105
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11585
12106
|
return true;
|
|
11586
12107
|
}
|
|
11587
12108
|
/**
|
|
@@ -11819,10 +12340,10 @@ class ClientStrategy {
|
|
|
11819
12340
|
// Call onWrite callback for testing persist storage
|
|
11820
12341
|
if (this.params.callbacks?.onWrite) {
|
|
11821
12342
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice);
|
|
11822
|
-
this.params.callbacks.onWrite(this.params.
|
|
12343
|
+
this.params.callbacks.onWrite(this.params.symbol, publicSignal, backtest);
|
|
11823
12344
|
}
|
|
11824
12345
|
if (!backtest) {
|
|
11825
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
12346
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
11826
12347
|
}
|
|
11827
12348
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
11828
12349
|
this._commitQueue.push({
|
|
@@ -11832,6 +12353,8 @@ class ClientStrategy {
|
|
|
11832
12353
|
percentShift,
|
|
11833
12354
|
currentPrice,
|
|
11834
12355
|
});
|
|
12356
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
12357
|
+
await PERSIST_STRATEGY_FN(this);
|
|
11835
12358
|
return true;
|
|
11836
12359
|
}
|
|
11837
12360
|
/**
|
|
@@ -12055,10 +12578,10 @@ class ClientStrategy {
|
|
|
12055
12578
|
// Call onWrite callback for testing persist storage
|
|
12056
12579
|
if (this.params.callbacks?.onWrite) {
|
|
12057
12580
|
const publicSignal = TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice);
|
|
12058
|
-
this.params.callbacks.onWrite(this.params.
|
|
12581
|
+
this.params.callbacks.onWrite(this.params.symbol, publicSignal, backtest);
|
|
12059
12582
|
}
|
|
12060
12583
|
if (!backtest) {
|
|
12061
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
12584
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
12062
12585
|
}
|
|
12063
12586
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
12064
12587
|
this._commitQueue.push({
|
|
@@ -12068,6 +12591,8 @@ class ClientStrategy {
|
|
|
12068
12591
|
percentShift,
|
|
12069
12592
|
currentPrice,
|
|
12070
12593
|
});
|
|
12594
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
12595
|
+
await PERSIST_STRATEGY_FN(this);
|
|
12071
12596
|
return true;
|
|
12072
12597
|
}
|
|
12073
12598
|
/**
|
|
@@ -12146,10 +12671,10 @@ class ClientStrategy {
|
|
|
12146
12671
|
});
|
|
12147
12672
|
// Call onWrite callback for testing persist storage
|
|
12148
12673
|
if (this.params.callbacks?.onWrite) {
|
|
12149
|
-
this.params.callbacks.onWrite(this.params.
|
|
12674
|
+
this.params.callbacks.onWrite(this.params.symbol, TO_PUBLIC_SIGNAL("pending", this._pendingSignal, currentPrice), backtest);
|
|
12150
12675
|
}
|
|
12151
12676
|
if (!backtest) {
|
|
12152
|
-
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.
|
|
12677
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.symbol, this.params.strategyName, this.params.exchangeName);
|
|
12153
12678
|
}
|
|
12154
12679
|
// Queue commit event for processing in tick()/backtest() with proper timestamp
|
|
12155
12680
|
this._commitQueue.push({
|
|
@@ -12160,6 +12685,8 @@ class ClientStrategy {
|
|
|
12160
12685
|
cost,
|
|
12161
12686
|
totalEntries: this._pendingSignal._entry?.length ?? 1,
|
|
12162
12687
|
});
|
|
12688
|
+
// Persist the queued commit so a crash before the next tick does not lose it
|
|
12689
|
+
await PERSIST_STRATEGY_FN(this);
|
|
12163
12690
|
return true;
|
|
12164
12691
|
}
|
|
12165
12692
|
}
|
|
@@ -12306,6 +12833,8 @@ class MergeRisk {
|
|
|
12306
12833
|
|
|
12307
12834
|
/** Default interval for strategies that do not specify one */
|
|
12308
12835
|
const STRATEGY_DEFAULT_INTERVAL = "1m";
|
|
12836
|
+
/** If not specified strategy will not open positions until createSignal is called manually */
|
|
12837
|
+
const STRATEGY_DEFAULT_SIGNAL = () => null;
|
|
12309
12838
|
/**
|
|
12310
12839
|
* If syncSubject listener or any registered action throws, it means the signal was not properly synchronized
|
|
12311
12840
|
* to the exchange (e.g. limit order failed to fill).
|
|
@@ -12739,7 +13268,7 @@ class StrategyConnectionService {
|
|
|
12739
13268
|
* @returns Configured ClientStrategy instance
|
|
12740
13269
|
*/
|
|
12741
13270
|
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$A(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
12742
|
-
const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
13271
|
+
const { riskName = "", riskList = [], getSignal = STRATEGY_DEFAULT_SIGNAL, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
12743
13272
|
return new ClientStrategy({
|
|
12744
13273
|
symbol,
|
|
12745
13274
|
interval,
|
|
@@ -13758,6 +14287,47 @@ class StrategyConnectionService {
|
|
|
13758
14287
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
13759
14288
|
await strategy.closePending(symbol, backtest, payload);
|
|
13760
14289
|
};
|
|
14290
|
+
/**
|
|
14291
|
+
* Queues a user-supplied signal DTO to be consumed by the next tick instead of getSignal.
|
|
14292
|
+
*
|
|
14293
|
+
* Delegates to ClientStrategy.createSignal(). Validated and rejected if a signal/deferred
|
|
14294
|
+
* action is already in flight. Works out of the async-hooks execution context.
|
|
14295
|
+
*
|
|
14296
|
+
* @param backtest - Whether running in backtest mode
|
|
14297
|
+
* @param symbol - Trading pair symbol
|
|
14298
|
+
* @param dto - Signal DTO to open
|
|
14299
|
+
* @param context - Context with strategyName, exchangeName, frameName
|
|
14300
|
+
* @returns Promise that resolves when the DTO is queued
|
|
14301
|
+
*/
|
|
14302
|
+
this.createSignal = async (backtest, symbol, dto, context) => {
|
|
14303
|
+
this.loggerService.log("strategyConnectionService createSignal", {
|
|
14304
|
+
symbol,
|
|
14305
|
+
context,
|
|
14306
|
+
backtest,
|
|
14307
|
+
});
|
|
14308
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
14309
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
14310
|
+
await strategy.createSignal(symbol, currentPrice, dto);
|
|
14311
|
+
};
|
|
14312
|
+
/**
|
|
14313
|
+
* Returns the in-memory deferred strategy-state snapshot for this iteration.
|
|
14314
|
+
*
|
|
14315
|
+
* Delegates to ClientStrategy.getStatus(). Synchronous in-memory read; works out of context.
|
|
14316
|
+
*
|
|
14317
|
+
* @param backtest - Whether running in backtest mode
|
|
14318
|
+
* @param symbol - Trading pair symbol
|
|
14319
|
+
* @param context - Context with strategyName, exchangeName, frameName
|
|
14320
|
+
* @returns Promise resolving to the current StrategyData snapshot
|
|
14321
|
+
*/
|
|
14322
|
+
this.getStatus = async (backtest, symbol, context) => {
|
|
14323
|
+
this.loggerService.log("strategyConnectionService getStatus", {
|
|
14324
|
+
symbol,
|
|
14325
|
+
context,
|
|
14326
|
+
backtest,
|
|
14327
|
+
});
|
|
14328
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
14329
|
+
return await strategy.getStatus(symbol);
|
|
14330
|
+
};
|
|
13761
14331
|
/**
|
|
13762
14332
|
* Checks whether `partialProfit` would succeed without executing it.
|
|
13763
14333
|
* Delegates to `ClientStrategy.validatePartialProfit()` — no throws, pure boolean result.
|
|
@@ -17361,6 +17931,47 @@ class StrategyCoreService {
|
|
|
17361
17931
|
await this.validate(context);
|
|
17362
17932
|
return await this.strategyConnectionService.closePending(backtest, symbol, context, payload);
|
|
17363
17933
|
};
|
|
17934
|
+
/**
|
|
17935
|
+
* Queues a user-supplied signal DTO to be consumed by the next tick instead of getSignal.
|
|
17936
|
+
*
|
|
17937
|
+
* Validates the context, then delegates to StrategyConnectionService.createSignal().
|
|
17938
|
+
* Rejected if a signal or deferred action is already in flight. Does not require execution context.
|
|
17939
|
+
*
|
|
17940
|
+
* @param backtest - Whether running in backtest mode
|
|
17941
|
+
* @param symbol - Trading pair symbol
|
|
17942
|
+
* @param dto - Signal DTO to open
|
|
17943
|
+
* @param context - Context with strategyName, exchangeName, frameName
|
|
17944
|
+
* @returns Promise that resolves when the DTO is queued
|
|
17945
|
+
*/
|
|
17946
|
+
this.createSignal = async (backtest, symbol, dto, context) => {
|
|
17947
|
+
this.loggerService.log("strategyCoreService createSignal", {
|
|
17948
|
+
symbol,
|
|
17949
|
+
context,
|
|
17950
|
+
backtest,
|
|
17951
|
+
});
|
|
17952
|
+
await this.validate(context);
|
|
17953
|
+
return await this.strategyConnectionService.createSignal(backtest, symbol, dto, context);
|
|
17954
|
+
};
|
|
17955
|
+
/**
|
|
17956
|
+
* Returns the in-memory deferred strategy-state snapshot for this iteration.
|
|
17957
|
+
*
|
|
17958
|
+
* Validates the context, then delegates to StrategyConnectionService.getStatus().
|
|
17959
|
+
* Does not require execution context.
|
|
17960
|
+
*
|
|
17961
|
+
* @param backtest - Whether running in backtest mode
|
|
17962
|
+
* @param symbol - Trading pair symbol
|
|
17963
|
+
* @param context - Context with strategyName, exchangeName, frameName
|
|
17964
|
+
* @returns Promise resolving to the current StrategyStatus snapshot
|
|
17965
|
+
*/
|
|
17966
|
+
this.getStatus = async (backtest, symbol, context) => {
|
|
17967
|
+
this.loggerService.log("strategyCoreService getStatus", {
|
|
17968
|
+
symbol,
|
|
17969
|
+
context,
|
|
17970
|
+
backtest,
|
|
17971
|
+
});
|
|
17972
|
+
await this.validate(context);
|
|
17973
|
+
return await this.strategyConnectionService.getStatus(backtest, symbol, context);
|
|
17974
|
+
};
|
|
17364
17975
|
/**
|
|
17365
17976
|
* Disposes the ClientStrategy instance for the given context.
|
|
17366
17977
|
*
|
|
@@ -18885,7 +19496,7 @@ class StrategySchemaService {
|
|
|
18885
19496
|
if (strategySchema.interval && typeof strategySchema.interval !== "string") {
|
|
18886
19497
|
throw new Error(`strategy schema validation failed: invalid interval for strategyName=${strategySchema.strategyName}`);
|
|
18887
19498
|
}
|
|
18888
|
-
if (typeof strategySchema.getSignal !== "function") {
|
|
19499
|
+
if (strategySchema.getSignal && typeof strategySchema.getSignal !== "function") {
|
|
18889
19500
|
throw new Error(`strategy schema validation failed: missing getSignal for strategyName=${strategySchema.strategyName}`);
|
|
18890
19501
|
}
|
|
18891
19502
|
};
|
|
@@ -22900,32 +23511,32 @@ const heat_columns = [
|
|
|
22900
23511
|
{
|
|
22901
23512
|
key: "totalPnl",
|
|
22902
23513
|
label: "Total PNL",
|
|
22903
|
-
format: (data) => data.totalPnl !== null ?
|
|
23514
|
+
format: (data) => data.totalPnl !== null ? `${data.totalPnl.toFixed(2)}%` : "N/A",
|
|
22904
23515
|
isVisible: () => true,
|
|
22905
23516
|
},
|
|
22906
23517
|
{
|
|
22907
23518
|
key: "sharpeRatio",
|
|
22908
23519
|
label: "Sharpe",
|
|
22909
|
-
format: (data) => data.sharpeRatio !== null ?
|
|
23520
|
+
format: (data) => data.sharpeRatio !== null ? data.sharpeRatio.toFixed(3) : "N/A",
|
|
22910
23521
|
isVisible: () => true,
|
|
22911
23522
|
},
|
|
22912
23523
|
{
|
|
22913
23524
|
key: "annualizedSharpeRatio",
|
|
22914
23525
|
label: "Ann Sharpe",
|
|
22915
|
-
format: (data) => data.annualizedSharpeRatio !== null ?
|
|
23526
|
+
format: (data) => data.annualizedSharpeRatio !== null ? data.annualizedSharpeRatio.toFixed(3) : "N/A",
|
|
22916
23527
|
isVisible: () => true,
|
|
22917
23528
|
},
|
|
22918
23529
|
{
|
|
22919
23530
|
key: "certaintyRatio",
|
|
22920
23531
|
label: "Certainty",
|
|
22921
|
-
format: (data) => data.certaintyRatio !== null ?
|
|
23532
|
+
format: (data) => data.certaintyRatio !== null ? data.certaintyRatio.toFixed(3) : "N/A",
|
|
22922
23533
|
isVisible: () => true,
|
|
22923
23534
|
},
|
|
22924
23535
|
{
|
|
22925
23536
|
key: "expectedYearlyReturns",
|
|
22926
23537
|
label: "Exp Yearly",
|
|
22927
23538
|
format: (data) => data.expectedYearlyReturns !== null
|
|
22928
|
-
?
|
|
23539
|
+
? `${data.expectedYearlyReturns.toFixed(2)}%`
|
|
22929
23540
|
: "N/A",
|
|
22930
23541
|
isVisible: () => true,
|
|
22931
23542
|
},
|
|
@@ -22938,37 +23549,37 @@ const heat_columns = [
|
|
|
22938
23549
|
{
|
|
22939
23550
|
key: "profitFactor",
|
|
22940
23551
|
label: "PF",
|
|
22941
|
-
format: (data) => data.profitFactor !== null ?
|
|
23552
|
+
format: (data) => data.profitFactor !== null ? data.profitFactor.toFixed(3) : "N/A",
|
|
22942
23553
|
isVisible: () => true,
|
|
22943
23554
|
},
|
|
22944
23555
|
{
|
|
22945
23556
|
key: "expectancy",
|
|
22946
23557
|
label: "Expect",
|
|
22947
|
-
format: (data) => data.expectancy !== null ?
|
|
23558
|
+
format: (data) => data.expectancy !== null ? `${data.expectancy.toFixed(2)}%` : "N/A",
|
|
22948
23559
|
isVisible: () => true,
|
|
22949
23560
|
},
|
|
22950
23561
|
{
|
|
22951
23562
|
key: "winRate",
|
|
22952
23563
|
label: "WR",
|
|
22953
|
-
format: (data) => data.winRate !== null ?
|
|
23564
|
+
format: (data) => data.winRate !== null ? `${data.winRate.toFixed(2)}%` : "N/A",
|
|
22954
23565
|
isVisible: () => true,
|
|
22955
23566
|
},
|
|
22956
23567
|
{
|
|
22957
23568
|
key: "avgWin",
|
|
22958
23569
|
label: "Avg Win",
|
|
22959
|
-
format: (data) => data.avgWin !== null ?
|
|
23570
|
+
format: (data) => data.avgWin !== null ? `${data.avgWin.toFixed(2)}%` : "N/A",
|
|
22960
23571
|
isVisible: () => true,
|
|
22961
23572
|
},
|
|
22962
23573
|
{
|
|
22963
23574
|
key: "avgLoss",
|
|
22964
23575
|
label: "Avg Loss",
|
|
22965
|
-
format: (data) => data.avgLoss !== null ?
|
|
23576
|
+
format: (data) => data.avgLoss !== null ? `${data.avgLoss.toFixed(2)}%` : "N/A",
|
|
22966
23577
|
isVisible: () => true,
|
|
22967
23578
|
},
|
|
22968
23579
|
{
|
|
22969
23580
|
key: "maxDrawdown",
|
|
22970
23581
|
label: "Max DD",
|
|
22971
|
-
format: (data) => data.maxDrawdown !== null ?
|
|
23582
|
+
format: (data) => data.maxDrawdown !== null ? `${(-data.maxDrawdown).toFixed(2)}%` : "N/A",
|
|
22972
23583
|
isVisible: () => true,
|
|
22973
23584
|
},
|
|
22974
23585
|
{
|
|
@@ -22992,31 +23603,31 @@ const heat_columns = [
|
|
|
22992
23603
|
{
|
|
22993
23604
|
key: "avgPeakPnl",
|
|
22994
23605
|
label: "Avg Peak PNL",
|
|
22995
|
-
format: (data) => data.avgPeakPnl !== null ?
|
|
23606
|
+
format: (data) => data.avgPeakPnl !== null ? `${data.avgPeakPnl.toFixed(2)}%` : "N/A",
|
|
22996
23607
|
isVisible: () => true,
|
|
22997
23608
|
},
|
|
22998
23609
|
{
|
|
22999
23610
|
key: "avgFallPnl",
|
|
23000
23611
|
label: "Avg DD PNL",
|
|
23001
|
-
format: (data) => data.avgFallPnl !== null ?
|
|
23612
|
+
format: (data) => data.avgFallPnl !== null ? `${data.avgFallPnl.toFixed(2)}%` : "N/A",
|
|
23002
23613
|
isVisible: () => true,
|
|
23003
23614
|
},
|
|
23004
23615
|
{
|
|
23005
23616
|
key: "peakProfitPnl",
|
|
23006
23617
|
label: "Peak Profit PNL",
|
|
23007
|
-
format: (data) => data.peakProfitPnl !== null ?
|
|
23618
|
+
format: (data) => data.peakProfitPnl !== null ? `${data.peakProfitPnl.toFixed(2)}%` : "N/A",
|
|
23008
23619
|
isVisible: () => true,
|
|
23009
23620
|
},
|
|
23010
23621
|
{
|
|
23011
23622
|
key: "maxDrawdownPnl",
|
|
23012
23623
|
label: "Max DD PNL",
|
|
23013
|
-
format: (data) => data.maxDrawdownPnl !== null ?
|
|
23624
|
+
format: (data) => data.maxDrawdownPnl !== null ? `${data.maxDrawdownPnl.toFixed(2)}%` : "N/A",
|
|
23014
23625
|
isVisible: () => true,
|
|
23015
23626
|
},
|
|
23016
23627
|
{
|
|
23017
23628
|
key: "medianPnl",
|
|
23018
23629
|
label: "Median PNL",
|
|
23019
|
-
format: (data) => data.medianPnl !== null ?
|
|
23630
|
+
format: (data) => data.medianPnl !== null ? `${data.medianPnl.toFixed(2)}%` : "N/A",
|
|
23020
23631
|
isVisible: () => true,
|
|
23021
23632
|
},
|
|
23022
23633
|
{
|
|
@@ -23041,7 +23652,7 @@ const heat_columns = [
|
|
|
23041
23652
|
key: "avgConsecutiveWinPnl",
|
|
23042
23653
|
label: "Avg Win Streak PNL",
|
|
23043
23654
|
format: (data) => data.avgConsecutiveWinPnl !== null
|
|
23044
|
-
?
|
|
23655
|
+
? `${data.avgConsecutiveWinPnl.toFixed(2)}%`
|
|
23045
23656
|
: "N/A",
|
|
23046
23657
|
isVisible: () => true,
|
|
23047
23658
|
},
|
|
@@ -23049,7 +23660,7 @@ const heat_columns = [
|
|
|
23049
23660
|
key: "avgConsecutiveLossPnl",
|
|
23050
23661
|
label: "Avg Loss Streak PNL",
|
|
23051
23662
|
format: (data) => data.avgConsecutiveLossPnl !== null
|
|
23052
|
-
?
|
|
23663
|
+
? `${data.avgConsecutiveLossPnl.toFixed(2)}%`
|
|
23053
23664
|
: "N/A",
|
|
23054
23665
|
isVisible: () => true,
|
|
23055
23666
|
},
|
|
@@ -23062,7 +23673,7 @@ const heat_columns = [
|
|
|
23062
23673
|
{
|
|
23063
23674
|
key: "trendStrength",
|
|
23064
23675
|
label: "Trend %/d",
|
|
23065
|
-
format: (data) => data.trendStrength !== null ?
|
|
23676
|
+
format: (data) => data.trendStrength !== null ? `${data.trendStrength.toFixed(2)}%` : "N/A",
|
|
23066
23677
|
isVisible: () => true,
|
|
23067
23678
|
},
|
|
23068
23679
|
{
|
|
@@ -23115,25 +23726,25 @@ const heat_columns = [
|
|
|
23115
23726
|
{
|
|
23116
23727
|
key: "medianStepSize",
|
|
23117
23728
|
label: "Median Step",
|
|
23118
|
-
format: (data) => data.medianStepSize !== null ?
|
|
23729
|
+
format: (data) => data.medianStepSize !== null ? `${data.medianStepSize.toFixed(2)}%` : "N/A",
|
|
23119
23730
|
isVisible: () => true,
|
|
23120
23731
|
},
|
|
23121
23732
|
{
|
|
23122
23733
|
key: "sortinoRatio",
|
|
23123
23734
|
label: "Sortino",
|
|
23124
|
-
format: (data) => data.sortinoRatio !== null ?
|
|
23735
|
+
format: (data) => data.sortinoRatio !== null ? data.sortinoRatio.toFixed(3) : "N/A",
|
|
23125
23736
|
isVisible: () => true,
|
|
23126
23737
|
},
|
|
23127
23738
|
{
|
|
23128
23739
|
key: "calmarRatio",
|
|
23129
23740
|
label: "Calmar",
|
|
23130
|
-
format: (data) => data.calmarRatio !== null ?
|
|
23741
|
+
format: (data) => data.calmarRatio !== null ? data.calmarRatio.toFixed(3) : "N/A",
|
|
23131
23742
|
isVisible: () => true,
|
|
23132
23743
|
},
|
|
23133
23744
|
{
|
|
23134
23745
|
key: "recoveryFactor",
|
|
23135
23746
|
label: "Recovery",
|
|
23136
|
-
format: (data) => data.recoveryFactor !== null ?
|
|
23747
|
+
format: (data) => data.recoveryFactor !== null ? data.recoveryFactor.toFixed(3) : "N/A",
|
|
23137
23748
|
isVisible: () => true,
|
|
23138
23749
|
},
|
|
23139
23750
|
];
|
|
@@ -30309,28 +30920,28 @@ class HeatmapStorage {
|
|
|
30309
30920
|
`# Portfolio Heatmap: ${strategyName}`,
|
|
30310
30921
|
"",
|
|
30311
30922
|
`**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 ?
|
|
30923
|
+
`**Portfolio PNL:** ${data.portfolioTotalPnl !== null ? `${data.portfolioTotalPnl.toFixed(2)} %` : "N/A"}`,
|
|
30924
|
+
`**Pooled Sharpe:** ${data.portfolioSharpeRatio !== null ? data.portfolioSharpeRatio.toFixed(3) : "N/A"}`,
|
|
30925
|
+
`**Annualized Sharpe:** ${data.portfolioAnnualizedSharpeRatio !== null ? data.portfolioAnnualizedSharpeRatio.toFixed(3) : "N/A"}`,
|
|
30926
|
+
`**Certainty Ratio:** ${data.portfolioCertaintyRatio !== null ? data.portfolioCertaintyRatio.toFixed(3) : "N/A"}`,
|
|
30927
|
+
`**Expected Yearly Returns:** ${data.portfolioExpectedYearlyReturns !== null ? `${data.portfolioExpectedYearlyReturns.toFixed(2)} %` : "N/A"}`,
|
|
30317
30928
|
`**Trades Per Year:** ${data.portfolioTradesPerYear !== null ? data.portfolioTradesPerYear.toFixed(1) : "N/A"}`,
|
|
30318
30929
|
`**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 ?
|
|
30930
|
+
`**Avg Peak PNL:** ${data.portfolioAvgPeakPnl !== null ? `${data.portfolioAvgPeakPnl.toFixed(2)} %` : "N/A"}`,
|
|
30931
|
+
`**Avg Max Drawdown PNL:** ${data.portfolioAvgFallPnl !== null ? `${data.portfolioAvgFallPnl.toFixed(2)} %` : "N/A"}`,
|
|
30932
|
+
`**Peak Profit PNL:** ${data.portfolioPeakProfitPnl !== null ? `${data.portfolioPeakProfitPnl.toFixed(2)} %` : "N/A"}`,
|
|
30933
|
+
`**Max Drawdown PNL:** ${data.portfolioMaxDrawdownPnl !== null ? `${data.portfolioMaxDrawdownPnl.toFixed(2)} %` : "N/A"}`,
|
|
30934
|
+
`**Median PNL:** ${data.portfolioMedianPnl !== null ? `${data.portfolioMedianPnl.toFixed(2)} %` : "N/A"}`,
|
|
30324
30935
|
`**Avg Duration:** ${data.portfolioAvgDuration !== null ? `${data.portfolioAvgDuration.toFixed(1)} min` : "N/A"}`,
|
|
30325
30936
|
`**Avg Win Duration:** ${data.portfolioAvgWinDuration !== null ? `${data.portfolioAvgWinDuration.toFixed(1)} min` : "N/A"}`,
|
|
30326
30937
|
`**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 ?
|
|
30938
|
+
`**Avg Consecutive Win PNL:** ${data.portfolioAvgConsecutiveWinPnl !== null ? `${data.portfolioAvgConsecutiveWinPnl.toFixed(2)} %` : "N/A"}`,
|
|
30939
|
+
`**Avg Consecutive Loss PNL:** ${data.portfolioAvgConsecutiveLossPnl !== null ? `${data.portfolioAvgConsecutiveLossPnl.toFixed(2)} %` : "N/A"}`,
|
|
30940
|
+
`**Standard Deviation Per Trade:** ${data.portfolioStdDev !== null ? `${data.portfolioStdDev.toFixed(2)} %` : "N/A"}`,
|
|
30941
|
+
`**Sortino Ratio:** ${data.portfolioSortinoRatio !== null ? data.portfolioSortinoRatio.toFixed(3) : "N/A"}`,
|
|
30942
|
+
`**Calmar Ratio:** ${data.portfolioCalmarRatio !== null ? data.portfolioCalmarRatio.toFixed(3) : "N/A"}`,
|
|
30943
|
+
`**Recovery Factor:** ${data.portfolioRecoveryFactor !== null ? data.portfolioRecoveryFactor.toFixed(3) : "N/A"}`,
|
|
30944
|
+
`**Expectancy:** ${data.portfolioExpectancy !== null ? `${data.portfolioExpectancy.toFixed(2)} %` : "N/A"}`,
|
|
30334
30945
|
"",
|
|
30335
30946
|
table,
|
|
30336
30947
|
"",
|
|
@@ -42599,6 +43210,8 @@ const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOve
|
|
|
42599
43210
|
const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
|
|
42600
43211
|
const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
|
|
42601
43212
|
const COMMIT_SIGNAL_NOTIFY_METHOD_NAME = "strategy.commitSignalNotify";
|
|
43213
|
+
const CREATE_SIGNAL_METHOD_NAME = "strategy.createSignal";
|
|
43214
|
+
const GET_STRATEGY_STATUS_METHOD_NAME = "strategy.getStrategyStatus";
|
|
42602
43215
|
/**
|
|
42603
43216
|
* Cancels the scheduled signal without stopping the strategy.
|
|
42604
43217
|
*
|
|
@@ -44663,6 +45276,69 @@ async function commitSignalNotify(symbol, payload = {}) {
|
|
|
44663
45276
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
44664
45277
|
await bt.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, { strategyName, exchangeName, frameName }, isBacktest);
|
|
44665
45278
|
}
|
|
45279
|
+
/**
|
|
45280
|
+
* Queues a user-supplied signal DTO to be consumed by the next tick instead of params.getSignal.
|
|
45281
|
+
*
|
|
45282
|
+
* priceOpen decides the outcome via the existing pipeline: omitted → opens immediately at the
|
|
45283
|
+
* current price; provided → opens immediately if already reached, otherwise registers a
|
|
45284
|
+
* scheduled (priceOpen-awaiting) signal. The DTO is validated (reusing validateSignal) and the
|
|
45285
|
+
* call is rejected if a signal or deferred action is already in flight.
|
|
45286
|
+
*
|
|
45287
|
+
* Automatically detects backtest/live mode from execution context.
|
|
45288
|
+
*
|
|
45289
|
+
* @param symbol - Trading pair symbol
|
|
45290
|
+
* @param dto - Signal DTO to open (priceOpen optional)
|
|
45291
|
+
* @returns Promise that resolves when the DTO is queued
|
|
45292
|
+
*
|
|
45293
|
+
* @example
|
|
45294
|
+
* ```typescript
|
|
45295
|
+
* import { createSignal } from "backtest-kit";
|
|
45296
|
+
*
|
|
45297
|
+
* // Open immediately at current price
|
|
45298
|
+
* await createSignal("BTCUSDT", { position: "long", priceTakeProfit: 110, priceStopLoss: 90 });
|
|
45299
|
+
* ```
|
|
45300
|
+
*/
|
|
45301
|
+
async function createSignal(symbol, dto) {
|
|
45302
|
+
bt.loggerService.info(CREATE_SIGNAL_METHOD_NAME, { symbol });
|
|
45303
|
+
if (!ExecutionContextService.hasContext()) {
|
|
45304
|
+
throw new Error("createSignal requires an execution context");
|
|
45305
|
+
}
|
|
45306
|
+
if (!MethodContextService.hasContext()) {
|
|
45307
|
+
throw new Error("createSignal requires a method context");
|
|
45308
|
+
}
|
|
45309
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
45310
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
45311
|
+
await bt.strategyCoreService.createSignal(isBacktest, symbol, dto, { exchangeName, frameName, strategyName });
|
|
45312
|
+
}
|
|
45313
|
+
/**
|
|
45314
|
+
* Returns the in-memory deferred strategy-state snapshot for the current iteration: the queued
|
|
45315
|
+
* createSignal, commit queue and deferred user-action flags, plus the current pending signal id.
|
|
45316
|
+
*
|
|
45317
|
+
* Automatically detects backtest/live mode from execution context.
|
|
45318
|
+
*
|
|
45319
|
+
* @param symbol - Trading pair symbol
|
|
45320
|
+
* @returns Promise resolving to the current StrategyStatus snapshot
|
|
45321
|
+
*
|
|
45322
|
+
* @example
|
|
45323
|
+
* ```typescript
|
|
45324
|
+
* import { getStrategyStatus } from "backtest-kit";
|
|
45325
|
+
*
|
|
45326
|
+
* const status = await getStrategyStatus("BTCUSDT");
|
|
45327
|
+
* console.log(status.pendingSignalId, status.commitQueue.length);
|
|
45328
|
+
* ```
|
|
45329
|
+
*/
|
|
45330
|
+
async function getStrategyStatus(symbol) {
|
|
45331
|
+
bt.loggerService.info(GET_STRATEGY_STATUS_METHOD_NAME, { symbol });
|
|
45332
|
+
if (!ExecutionContextService.hasContext()) {
|
|
45333
|
+
throw new Error("getStrategyStatus requires an execution context");
|
|
45334
|
+
}
|
|
45335
|
+
if (!MethodContextService.hasContext()) {
|
|
45336
|
+
throw new Error("getStrategyStatus requires a method context");
|
|
45337
|
+
}
|
|
45338
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
45339
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
45340
|
+
return await bt.strategyCoreService.getStatus(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
45341
|
+
}
|
|
44666
45342
|
|
|
44667
45343
|
const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
|
|
44668
45344
|
/**
|
|
@@ -46334,6 +47010,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPosi
|
|
|
46334
47010
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
46335
47011
|
const BACKTEST_METHOD_NAME_CANCEL_SCHEDULED = "Backtest.commitCancelScheduled";
|
|
46336
47012
|
const BACKTEST_METHOD_NAME_CLOSE_PENDING = "Backtest.commitClosePending";
|
|
47013
|
+
const BACKTEST_METHOD_NAME_CREATE_SIGNAL = "Backtest.createSignal";
|
|
47014
|
+
const BACKTEST_METHOD_NAME_GET_STRATEGY_STATUS = "Backtest.getStrategyStatus";
|
|
46337
47015
|
const BACKTEST_METHOD_NAME_PARTIAL_PROFIT = "BacktestUtils.commitPartialProfit";
|
|
46338
47016
|
const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.commitPartialLoss";
|
|
46339
47017
|
const BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST = "BacktestUtils.commitPartialProfitCost";
|
|
@@ -48038,6 +48716,53 @@ class BacktestUtils {
|
|
|
48038
48716
|
}
|
|
48039
48717
|
await bt.strategyCoreService.closePending(true, symbol, context, payload);
|
|
48040
48718
|
};
|
|
48719
|
+
/**
|
|
48720
|
+
* Queues a user-supplied signal DTO to be consumed by the next backtest tick instead of
|
|
48721
|
+
* params.getSignal.
|
|
48722
|
+
*
|
|
48723
|
+
* priceOpen decides the outcome via the existing pipeline: omitted → opens immediately at the
|
|
48724
|
+
* current price; provided → opens immediately if already reached, otherwise registers a
|
|
48725
|
+
* scheduled signal. Validated, and rejected if a signal or deferred action is already in flight.
|
|
48726
|
+
*
|
|
48727
|
+
* @param symbol - Trading pair symbol
|
|
48728
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
48729
|
+
* @param dto - Signal DTO to open (priceOpen optional)
|
|
48730
|
+
* @returns Promise that resolves when the DTO is queued
|
|
48731
|
+
*/
|
|
48732
|
+
this.createSignal = async (symbol, context, dto) => {
|
|
48733
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_CREATE_SIGNAL, {
|
|
48734
|
+
symbol,
|
|
48735
|
+
context,
|
|
48736
|
+
});
|
|
48737
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CREATE_SIGNAL);
|
|
48738
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CREATE_SIGNAL);
|
|
48739
|
+
{
|
|
48740
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
48741
|
+
riskName &&
|
|
48742
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_CREATE_SIGNAL);
|
|
48743
|
+
riskList &&
|
|
48744
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_CREATE_SIGNAL));
|
|
48745
|
+
actions &&
|
|
48746
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CREATE_SIGNAL));
|
|
48747
|
+
}
|
|
48748
|
+
await bt.strategyCoreService.createSignal(true, symbol, dto, context);
|
|
48749
|
+
};
|
|
48750
|
+
/**
|
|
48751
|
+
* Returns the in-memory deferred strategy-state snapshot for the current backtest iteration.
|
|
48752
|
+
*
|
|
48753
|
+
* @param symbol - Trading pair symbol
|
|
48754
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
48755
|
+
* @returns Promise resolving to the current StrategyStatus snapshot
|
|
48756
|
+
*/
|
|
48757
|
+
this.getStrategyStatus = async (symbol, context) => {
|
|
48758
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_STRATEGY_STATUS, {
|
|
48759
|
+
symbol,
|
|
48760
|
+
context,
|
|
48761
|
+
});
|
|
48762
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_STRATEGY_STATUS);
|
|
48763
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_STRATEGY_STATUS);
|
|
48764
|
+
return await bt.strategyCoreService.getStatus(true, symbol, context);
|
|
48765
|
+
};
|
|
48041
48766
|
/**
|
|
48042
48767
|
* Executes partial close at profit level (moving toward TP).
|
|
48043
48768
|
*
|
|
@@ -48996,6 +49721,8 @@ const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPart
|
|
|
48996
49721
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
48997
49722
|
const LIVE_METHOD_NAME_CANCEL_SCHEDULED = "Live.cancelScheduled";
|
|
48998
49723
|
const LIVE_METHOD_NAME_CLOSE_PENDING = "Live.closePending";
|
|
49724
|
+
const LIVE_METHOD_NAME_CREATE_SIGNAL = "Live.createSignal";
|
|
49725
|
+
const LIVE_METHOD_NAME_GET_STRATEGY_STATUS = "Live.getStrategyStatus";
|
|
48999
49726
|
const LIVE_METHOD_NAME_PARTIAL_PROFIT = "LiveUtils.commitPartialProfit";
|
|
49000
49727
|
const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.commitPartialLoss";
|
|
49001
49728
|
const LIVE_METHOD_NAME_PARTIAL_PROFIT_COST = "LiveUtils.commitPartialProfitCost";
|
|
@@ -50873,6 +51600,61 @@ class LiveUtils {
|
|
|
50873
51600
|
frameName: "",
|
|
50874
51601
|
}, payload);
|
|
50875
51602
|
};
|
|
51603
|
+
/**
|
|
51604
|
+
* Queues a user-supplied signal DTO to be consumed by the next live tick instead of
|
|
51605
|
+
* params.getSignal.
|
|
51606
|
+
*
|
|
51607
|
+
* priceOpen decides the outcome via the existing pipeline: omitted → opens immediately at the
|
|
51608
|
+
* current price; provided → opens immediately if already reached, otherwise registers a
|
|
51609
|
+
* scheduled signal. Validated, and rejected if a signal or deferred action is already in flight.
|
|
51610
|
+
*
|
|
51611
|
+
* @param symbol - Trading pair symbol
|
|
51612
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
51613
|
+
* @param dto - Signal DTO to open (priceOpen optional)
|
|
51614
|
+
* @returns Promise that resolves when the DTO is queued
|
|
51615
|
+
*/
|
|
51616
|
+
this.createSignal = async (symbol, context, dto) => {
|
|
51617
|
+
bt.loggerService.info(LIVE_METHOD_NAME_CREATE_SIGNAL, {
|
|
51618
|
+
symbol,
|
|
51619
|
+
context,
|
|
51620
|
+
});
|
|
51621
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CREATE_SIGNAL);
|
|
51622
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CREATE_SIGNAL);
|
|
51623
|
+
{
|
|
51624
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
51625
|
+
riskName &&
|
|
51626
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CREATE_SIGNAL);
|
|
51627
|
+
riskList &&
|
|
51628
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CREATE_SIGNAL));
|
|
51629
|
+
actions &&
|
|
51630
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_CREATE_SIGNAL));
|
|
51631
|
+
}
|
|
51632
|
+
await bt.strategyCoreService.createSignal(false, symbol, dto, {
|
|
51633
|
+
strategyName: context.strategyName,
|
|
51634
|
+
exchangeName: context.exchangeName,
|
|
51635
|
+
frameName: "",
|
|
51636
|
+
});
|
|
51637
|
+
};
|
|
51638
|
+
/**
|
|
51639
|
+
* Returns the in-memory deferred strategy-state snapshot for the current live iteration.
|
|
51640
|
+
*
|
|
51641
|
+
* @param symbol - Trading pair symbol
|
|
51642
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
51643
|
+
* @returns Promise resolving to the current StrategyStatus snapshot
|
|
51644
|
+
*/
|
|
51645
|
+
this.getStrategyStatus = async (symbol, context) => {
|
|
51646
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_STRATEGY_STATUS, {
|
|
51647
|
+
symbol,
|
|
51648
|
+
context,
|
|
51649
|
+
});
|
|
51650
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_STRATEGY_STATUS);
|
|
51651
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_STRATEGY_STATUS);
|
|
51652
|
+
return await bt.strategyCoreService.getStatus(false, symbol, {
|
|
51653
|
+
strategyName: context.strategyName,
|
|
51654
|
+
exchangeName: context.exchangeName,
|
|
51655
|
+
frameName: "",
|
|
51656
|
+
});
|
|
51657
|
+
};
|
|
50876
51658
|
/**
|
|
50877
51659
|
* Executes partial close at profit level (moving toward TP).
|
|
50878
51660
|
*
|
|
@@ -67519,117 +68301,4 @@ const percentValue = (yesterdayValue, todayValue) => {
|
|
|
67519
68301
|
return yesterdayValue / todayValue - 1;
|
|
67520
68302
|
};
|
|
67521
68303
|
|
|
67522
|
-
|
|
67523
|
-
* Validates ISignalDto returned by getSignal, branching on the same logic as ClientStrategy GET_SIGNAL_FN.
|
|
67524
|
-
*
|
|
67525
|
-
* When priceOpen is provided:
|
|
67526
|
-
* - If currentPrice already reached priceOpen (shouldActivateImmediately) →
|
|
67527
|
-
* validates as pending: currentPrice must be between SL and TP
|
|
67528
|
-
* - Otherwise → validates as scheduled: priceOpen must be between SL and TP
|
|
67529
|
-
*
|
|
67530
|
-
* When priceOpen is absent:
|
|
67531
|
-
* - Validates as pending: currentPrice must be between SL and TP
|
|
67532
|
-
*
|
|
67533
|
-
* Checks:
|
|
67534
|
-
* - currentPrice is a finite positive number
|
|
67535
|
-
* - Common signal fields via validateCommonSignal (position, prices, TP/SL relationships, minuteEstimatedTime)
|
|
67536
|
-
* - Position-specific immediate-close protection (pending) or activation-close protection (scheduled)
|
|
67537
|
-
*
|
|
67538
|
-
* @param signal - Signal DTO returned by getSignal
|
|
67539
|
-
* @param currentPrice - Current market price at the moment of signal creation
|
|
67540
|
-
* @returns true if signal is valid, false if validation errors were found (errors logged to console.error)
|
|
67541
|
-
*/
|
|
67542
|
-
const validateSignal = (signal, currentPrice) => {
|
|
67543
|
-
const errors = [];
|
|
67544
|
-
// ЗАЩИТА ОТ NaN/Infinity: currentPrice должна быть конечным числом
|
|
67545
|
-
{
|
|
67546
|
-
if (typeof currentPrice !== "number") {
|
|
67547
|
-
errors.push(`currentPrice must be a number type, got ${currentPrice} (${typeof currentPrice})`);
|
|
67548
|
-
}
|
|
67549
|
-
if (!isFinite(currentPrice)) {
|
|
67550
|
-
errors.push(`currentPrice must be a finite number, got ${currentPrice} (${typeof currentPrice})`);
|
|
67551
|
-
}
|
|
67552
|
-
if (isFinite(currentPrice) && currentPrice <= 0) {
|
|
67553
|
-
errors.push(`currentPrice must be positive, got ${currentPrice}`);
|
|
67554
|
-
}
|
|
67555
|
-
}
|
|
67556
|
-
if (errors.length > 0) {
|
|
67557
|
-
console.error(`Invalid signal for ${signal.position} position (${signal.symbol || "empty symbol"}):\n${errors.join("\n")}`);
|
|
67558
|
-
return false;
|
|
67559
|
-
}
|
|
67560
|
-
try {
|
|
67561
|
-
validateCommonSignal(signal);
|
|
67562
|
-
}
|
|
67563
|
-
catch (error) {
|
|
67564
|
-
console.error(getErrorMessage(error));
|
|
67565
|
-
return false;
|
|
67566
|
-
}
|
|
67567
|
-
// Определяем режим валидации по той же логике что в GET_SIGNAL_FN:
|
|
67568
|
-
// - нет priceOpen → pending (открывается по currentPrice)
|
|
67569
|
-
// - priceOpen задан и уже достигнут (shouldActivateImmediately) → pending
|
|
67570
|
-
// - priceOpen задан и ещё не достигнут → scheduled
|
|
67571
|
-
const hasPriceOpen = signal.priceOpen !== undefined;
|
|
67572
|
-
const shouldActivateImmediately = hasPriceOpen && ((signal.position === "long" && currentPrice <= signal.priceOpen) ||
|
|
67573
|
-
(signal.position === "short" && currentPrice >= signal.priceOpen));
|
|
67574
|
-
const isScheduled = hasPriceOpen && !shouldActivateImmediately;
|
|
67575
|
-
if (isScheduled) {
|
|
67576
|
-
// Scheduled: priceOpen должен быть между SL и TP (активация не даст моментального закрытия)
|
|
67577
|
-
if (signal.position === "long") {
|
|
67578
|
-
if (isFinite(signal.priceOpen)) {
|
|
67579
|
-
if (signal.priceOpen <= signal.priceStopLoss) {
|
|
67580
|
-
errors.push(`Long scheduled: priceOpen (${signal.priceOpen}) <= priceStopLoss (${signal.priceStopLoss}). ` +
|
|
67581
|
-
`Signal would be immediately cancelled on activation. Cannot activate position that is already stopped out.`);
|
|
67582
|
-
}
|
|
67583
|
-
if (signal.priceOpen >= signal.priceTakeProfit) {
|
|
67584
|
-
errors.push(`Long scheduled: priceOpen (${signal.priceOpen}) >= priceTakeProfit (${signal.priceTakeProfit}). ` +
|
|
67585
|
-
`Signal would close immediately on activation. This is logically impossible for LONG position.`);
|
|
67586
|
-
}
|
|
67587
|
-
}
|
|
67588
|
-
}
|
|
67589
|
-
if (signal.position === "short") {
|
|
67590
|
-
if (isFinite(signal.priceOpen)) {
|
|
67591
|
-
if (signal.priceOpen >= signal.priceStopLoss) {
|
|
67592
|
-
errors.push(`Short scheduled: priceOpen (${signal.priceOpen}) >= priceStopLoss (${signal.priceStopLoss}). ` +
|
|
67593
|
-
`Signal would be immediately cancelled on activation. Cannot activate position that is already stopped out.`);
|
|
67594
|
-
}
|
|
67595
|
-
if (signal.priceOpen <= signal.priceTakeProfit) {
|
|
67596
|
-
errors.push(`Short scheduled: priceOpen (${signal.priceOpen}) <= priceTakeProfit (${signal.priceTakeProfit}). ` +
|
|
67597
|
-
`Signal would close immediately on activation. This is logically impossible for SHORT position.`);
|
|
67598
|
-
}
|
|
67599
|
-
}
|
|
67600
|
-
}
|
|
67601
|
-
}
|
|
67602
|
-
else {
|
|
67603
|
-
// Pending: currentPrice должна быть между SL и TP (позиция не закроется сразу после открытия)
|
|
67604
|
-
if (signal.position === "long") {
|
|
67605
|
-
if (isFinite(currentPrice)) {
|
|
67606
|
-
if (currentPrice <= signal.priceStopLoss) {
|
|
67607
|
-
errors.push(`Long immediate: currentPrice (${currentPrice}) <= priceStopLoss (${signal.priceStopLoss}). ` +
|
|
67608
|
-
`Signal would be immediately closed by stop loss. Cannot open position that is already stopped out.`);
|
|
67609
|
-
}
|
|
67610
|
-
if (currentPrice >= signal.priceTakeProfit) {
|
|
67611
|
-
errors.push(`Long immediate: currentPrice (${currentPrice}) >= priceTakeProfit (${signal.priceTakeProfit}). ` +
|
|
67612
|
-
`Signal would be immediately closed by take profit. The profit opportunity has already passed.`);
|
|
67613
|
-
}
|
|
67614
|
-
}
|
|
67615
|
-
}
|
|
67616
|
-
if (signal.position === "short") {
|
|
67617
|
-
if (isFinite(currentPrice)) {
|
|
67618
|
-
if (currentPrice >= signal.priceStopLoss) {
|
|
67619
|
-
errors.push(`Short immediate: currentPrice (${currentPrice}) >= priceStopLoss (${signal.priceStopLoss}). ` +
|
|
67620
|
-
`Signal would be immediately closed by stop loss. Cannot open position that is already stopped out.`);
|
|
67621
|
-
}
|
|
67622
|
-
if (currentPrice <= signal.priceTakeProfit) {
|
|
67623
|
-
errors.push(`Short immediate: currentPrice (${currentPrice}) <= priceTakeProfit (${signal.priceTakeProfit}). ` +
|
|
67624
|
-
`Signal would be immediately closed by take profit. The profit opportunity has already passed.`);
|
|
67625
|
-
}
|
|
67626
|
-
}
|
|
67627
|
-
}
|
|
67628
|
-
}
|
|
67629
|
-
if (errors.length > 0) {
|
|
67630
|
-
console.error(`Invalid signal for ${signal.position} position (${signal.symbol || "empty symbol"}):\n${errors.join("\n")}`);
|
|
67631
|
-
}
|
|
67632
|
-
return !errors.length;
|
|
67633
|
-
};
|
|
67634
|
-
|
|
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 };
|
|
68304
|
+
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, createSignal, 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, getStrategyStatus, 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 };
|