backtest-kit 1.1.3 โ 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -17
- package/build/index.cjs +150 -54
- package/build/index.mjs +151 -55
- package/package.json +2 -1
- package/types.d.ts +60 -1
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
- ๐ **Flexible Architecture** - Plug your own exchanges and strategies
|
|
21
21
|
- ๐ **Markdown Reports** - Auto-generated trading reports with statistics (win rate, avg PNL)
|
|
22
22
|
- ๐ **Graceful Shutdown** - Live.background() waits for open positions to close before stopping
|
|
23
|
+
- ๐ **Strategy Dependency Injection** - addStrategy() enables DI pattern for trading strategies
|
|
24
|
+
- ๐งช **Comprehensive Test Coverage** - 30+ unit tests covering validation, PNL, callbacks, reports, and event system
|
|
23
25
|
|
|
24
26
|
## Installation
|
|
25
27
|
|
|
@@ -829,23 +831,6 @@ src/
|
|
|
829
831
|
|
|
830
832
|
## Advanced Examples
|
|
831
833
|
|
|
832
|
-
### Custom Persistence Adapter
|
|
833
|
-
|
|
834
|
-
```typescript
|
|
835
|
-
import { PersistSignalAdaper, PersistBase } from "backtest-kit";
|
|
836
|
-
|
|
837
|
-
class RedisPersist extends PersistBase {
|
|
838
|
-
async readValue(entityId) {
|
|
839
|
-
return JSON.parse(await redis.get(entityId));
|
|
840
|
-
}
|
|
841
|
-
async writeValue(entityId, entity) {
|
|
842
|
-
await redis.set(entityId, JSON.stringify(entity));
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
PersistSignalAdaper.usePersistSignalAdapter(RedisPersist);
|
|
847
|
-
```
|
|
848
|
-
|
|
849
834
|
### Multi-Symbol Live Trading
|
|
850
835
|
|
|
851
836
|
```typescript
|
package/build/index.cjs
CHANGED
|
@@ -1104,6 +1104,32 @@ class PersistSignalUtils {
|
|
|
1104
1104
|
*/
|
|
1105
1105
|
const PersistSignalAdaper = new PersistSignalUtils();
|
|
1106
1106
|
|
|
1107
|
+
/**
|
|
1108
|
+
* Global signal emitter for all trading events (live + backtest).
|
|
1109
|
+
* Emits all signal events regardless of execution mode.
|
|
1110
|
+
*/
|
|
1111
|
+
const signalEmitter = new functoolsKit.Subject();
|
|
1112
|
+
/**
|
|
1113
|
+
* Live trading signal emitter.
|
|
1114
|
+
* Emits only signals from live trading execution.
|
|
1115
|
+
*/
|
|
1116
|
+
const signalLiveEmitter = new functoolsKit.Subject();
|
|
1117
|
+
/**
|
|
1118
|
+
* Backtest signal emitter.
|
|
1119
|
+
* Emits only signals from backtest execution.
|
|
1120
|
+
*/
|
|
1121
|
+
const signalBacktestEmitter = new functoolsKit.Subject();
|
|
1122
|
+
/**
|
|
1123
|
+
* Error emitter for background execution errors.
|
|
1124
|
+
* Emits errors caught in background tasks (Live.background, Backtest.background).
|
|
1125
|
+
*/
|
|
1126
|
+
const errorEmitter = new functoolsKit.Subject();
|
|
1127
|
+
/**
|
|
1128
|
+
* Done emitter for background execution completion.
|
|
1129
|
+
* Emits when background tasks complete (Live.background, Backtest.background).
|
|
1130
|
+
*/
|
|
1131
|
+
const doneEmitter = new functoolsKit.Subject();
|
|
1132
|
+
|
|
1107
1133
|
const INTERVAL_MINUTES$1 = {
|
|
1108
1134
|
"1m": 1,
|
|
1109
1135
|
"3m": 3,
|
|
@@ -1155,6 +1181,9 @@ const VALIDATE_SIGNAL_FN = (signal) => {
|
|
|
1155
1181
|
}
|
|
1156
1182
|
};
|
|
1157
1183
|
const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
1184
|
+
if (self._isStopped) {
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1158
1187
|
const currentTime = self.params.execution.context.when.getTime();
|
|
1159
1188
|
{
|
|
1160
1189
|
const intervalMinutes = INTERVAL_MINUTES$1[self.params.interval];
|
|
@@ -1183,6 +1212,13 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
1183
1212
|
return signalRow;
|
|
1184
1213
|
}, {
|
|
1185
1214
|
defaultValue: null,
|
|
1215
|
+
fallback: (error) => {
|
|
1216
|
+
backtest$1.loggerService.warn("ClientStrategy exception thrown", {
|
|
1217
|
+
error: functoolsKit.errorData(error),
|
|
1218
|
+
message: functoolsKit.getErrorMessage(error),
|
|
1219
|
+
});
|
|
1220
|
+
errorEmitter.next(error);
|
|
1221
|
+
},
|
|
1186
1222
|
});
|
|
1187
1223
|
const GET_AVG_PRICE_FN = (candles) => {
|
|
1188
1224
|
const sumPriceVolume = candles.reduce((acc, c) => {
|
|
@@ -1241,6 +1277,7 @@ const WAIT_FOR_INIT_FN = async (self) => {
|
|
|
1241
1277
|
class ClientStrategy {
|
|
1242
1278
|
constructor(params) {
|
|
1243
1279
|
this.params = params;
|
|
1280
|
+
this._isStopped = false;
|
|
1244
1281
|
this._pendingSignal = null;
|
|
1245
1282
|
this._lastSignalTimestamp = null;
|
|
1246
1283
|
/**
|
|
@@ -1563,34 +1600,32 @@ class ClientStrategy {
|
|
|
1563
1600
|
}
|
|
1564
1601
|
return result;
|
|
1565
1602
|
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Stops the strategy from generating new signals.
|
|
1605
|
+
*
|
|
1606
|
+
* Sets internal flag to prevent getSignal from being called.
|
|
1607
|
+
* Does NOT close active pending signals - they continue monitoring until TP/SL/time_expired.
|
|
1608
|
+
*
|
|
1609
|
+
* Use case: Graceful shutdown in live trading without forcing position closure.
|
|
1610
|
+
*
|
|
1611
|
+
* @returns Promise that resolves immediately when stop flag is set
|
|
1612
|
+
*
|
|
1613
|
+
* @example
|
|
1614
|
+
* ```typescript
|
|
1615
|
+
* // In Live.background() cancellation
|
|
1616
|
+
* await strategy.stop();
|
|
1617
|
+
* // Existing signal will continue until natural close
|
|
1618
|
+
* ```
|
|
1619
|
+
*/
|
|
1620
|
+
stop() {
|
|
1621
|
+
this.params.logger.debug("ClientStrategy stop", {
|
|
1622
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
1623
|
+
});
|
|
1624
|
+
this._isStopped = true;
|
|
1625
|
+
return Promise.resolve();
|
|
1626
|
+
}
|
|
1566
1627
|
}
|
|
1567
1628
|
|
|
1568
|
-
/**
|
|
1569
|
-
* Global signal emitter for all trading events (live + backtest).
|
|
1570
|
-
* Emits all signal events regardless of execution mode.
|
|
1571
|
-
*/
|
|
1572
|
-
const signalEmitter = new functoolsKit.Subject();
|
|
1573
|
-
/**
|
|
1574
|
-
* Live trading signal emitter.
|
|
1575
|
-
* Emits only signals from live trading execution.
|
|
1576
|
-
*/
|
|
1577
|
-
const signalLiveEmitter = new functoolsKit.Subject();
|
|
1578
|
-
/**
|
|
1579
|
-
* Backtest signal emitter.
|
|
1580
|
-
* Emits only signals from backtest execution.
|
|
1581
|
-
*/
|
|
1582
|
-
const signalBacktestEmitter = new functoolsKit.Subject();
|
|
1583
|
-
/**
|
|
1584
|
-
* Error emitter for background execution errors.
|
|
1585
|
-
* Emits errors caught in background tasks (Live.background, Backtest.background).
|
|
1586
|
-
*/
|
|
1587
|
-
const errorEmitter = new functoolsKit.Subject();
|
|
1588
|
-
/**
|
|
1589
|
-
* Done emitter for background execution completion.
|
|
1590
|
-
* Emits when background tasks complete (Live.background, Backtest.background).
|
|
1591
|
-
*/
|
|
1592
|
-
const doneEmitter = new functoolsKit.Subject();
|
|
1593
|
-
|
|
1594
1629
|
/**
|
|
1595
1630
|
* Connection service routing strategy operations to correct ClientStrategy instance.
|
|
1596
1631
|
*
|
|
@@ -1689,6 +1724,36 @@ class StrategyConnectionService {
|
|
|
1689
1724
|
}
|
|
1690
1725
|
return tick;
|
|
1691
1726
|
};
|
|
1727
|
+
/**
|
|
1728
|
+
* Stops the specified strategy from generating new signals.
|
|
1729
|
+
*
|
|
1730
|
+
* Delegates to ClientStrategy.stop() which sets internal flag to prevent
|
|
1731
|
+
* getSignal from being called on subsequent ticks.
|
|
1732
|
+
*
|
|
1733
|
+
* @param strategyName - Name of strategy to stop
|
|
1734
|
+
* @returns Promise that resolves when stop flag is set
|
|
1735
|
+
*/
|
|
1736
|
+
this.stop = async (strategyName) => {
|
|
1737
|
+
this.loggerService.log("strategyConnectionService stop", {
|
|
1738
|
+
strategyName,
|
|
1739
|
+
});
|
|
1740
|
+
const strategy = this.getStrategy(strategyName);
|
|
1741
|
+
await strategy.stop();
|
|
1742
|
+
};
|
|
1743
|
+
/**
|
|
1744
|
+
* Clears the memoized ClientStrategy instance from cache.
|
|
1745
|
+
*
|
|
1746
|
+
* Forces re-initialization of strategy on next getStrategy call.
|
|
1747
|
+
* Useful for resetting strategy state or releasing resources.
|
|
1748
|
+
*
|
|
1749
|
+
* @param strategyName - Name of strategy to clear from cache
|
|
1750
|
+
*/
|
|
1751
|
+
this.clear = async (strategyName) => {
|
|
1752
|
+
this.loggerService.log("strategyConnectionService clear", {
|
|
1753
|
+
strategyName,
|
|
1754
|
+
});
|
|
1755
|
+
this.getStrategy.clear(strategyName);
|
|
1756
|
+
};
|
|
1692
1757
|
}
|
|
1693
1758
|
}
|
|
1694
1759
|
|
|
@@ -2032,6 +2097,35 @@ class StrategyGlobalService {
|
|
|
2032
2097
|
backtest,
|
|
2033
2098
|
});
|
|
2034
2099
|
};
|
|
2100
|
+
/**
|
|
2101
|
+
* Stops the strategy from generating new signals.
|
|
2102
|
+
*
|
|
2103
|
+
* Delegates to StrategyConnectionService.stop() to set internal flag.
|
|
2104
|
+
* Does not require execution context.
|
|
2105
|
+
*
|
|
2106
|
+
* @param strategyName - Name of strategy to stop
|
|
2107
|
+
* @returns Promise that resolves when stop flag is set
|
|
2108
|
+
*/
|
|
2109
|
+
this.stop = async (strategyName) => {
|
|
2110
|
+
this.loggerService.log("strategyGlobalService stop", {
|
|
2111
|
+
strategyName,
|
|
2112
|
+
});
|
|
2113
|
+
return await this.strategyConnectionService.stop(strategyName);
|
|
2114
|
+
};
|
|
2115
|
+
/**
|
|
2116
|
+
* Clears the memoized ClientStrategy instance from cache.
|
|
2117
|
+
*
|
|
2118
|
+
* Delegates to StrategyConnectionService.clear() to remove strategy from cache.
|
|
2119
|
+
* Forces re-initialization of strategy on next operation.
|
|
2120
|
+
*
|
|
2121
|
+
* @param strategyName - Name of strategy to clear from cache
|
|
2122
|
+
*/
|
|
2123
|
+
this.clear = async (strategyName) => {
|
|
2124
|
+
this.loggerService.log("strategyGlobalService clear", {
|
|
2125
|
+
strategyName,
|
|
2126
|
+
});
|
|
2127
|
+
return await this.strategyConnectionService.clear(strategyName);
|
|
2128
|
+
};
|
|
2035
2129
|
}
|
|
2036
2130
|
}
|
|
2037
2131
|
|
|
@@ -2262,7 +2356,7 @@ class FrameSchemaService {
|
|
|
2262
2356
|
register(key, value) {
|
|
2263
2357
|
this.loggerService.log(`frameSchemaService register`, { key });
|
|
2264
2358
|
this.validateShallow(value);
|
|
2265
|
-
this._registry.register(key, value);
|
|
2359
|
+
this._registry = this._registry.register(key, value);
|
|
2266
2360
|
}
|
|
2267
2361
|
/**
|
|
2268
2362
|
* Overrides an existing frame schema with partial updates.
|
|
@@ -2273,7 +2367,8 @@ class FrameSchemaService {
|
|
|
2273
2367
|
*/
|
|
2274
2368
|
override(key, value) {
|
|
2275
2369
|
this.loggerService.log(`frameSchemaService override`, { key });
|
|
2276
|
-
this._registry.override(key, value);
|
|
2370
|
+
this._registry = this._registry.override(key, value);
|
|
2371
|
+
return this._registry.get(key);
|
|
2277
2372
|
}
|
|
2278
2373
|
/**
|
|
2279
2374
|
* Retrieves a frame schema by name.
|
|
@@ -2720,10 +2815,17 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
2720
2815
|
return functoolsKit.str.newline(`# Backtest Report: ${strategyName}`, "", "No signals closed yet.");
|
|
2721
2816
|
}
|
|
2722
2817
|
const header = columns$1.map((col) => col.label);
|
|
2818
|
+
const separator = columns$1.map(() => "---");
|
|
2723
2819
|
const rows = this._signalList.map((closedSignal) => columns$1.map((col) => col.format(closedSignal)));
|
|
2724
|
-
const tableData = [header, ...rows];
|
|
2725
|
-
const table = functoolsKit.str.
|
|
2726
|
-
|
|
2820
|
+
const tableData = [header, separator, ...rows];
|
|
2821
|
+
const table = functoolsKit.str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
|
|
2822
|
+
// Calculate statistics
|
|
2823
|
+
const totalSignals = this._signalList.length;
|
|
2824
|
+
const winCount = this._signalList.filter((s) => s.pnl.pnlPercentage > 0).length;
|
|
2825
|
+
const lossCount = this._signalList.filter((s) => s.pnl.pnlPercentage < 0).length;
|
|
2826
|
+
const avgPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0) / totalSignals;
|
|
2827
|
+
const totalPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0);
|
|
2828
|
+
return functoolsKit.str.newline(`# Backtest Report: ${strategyName}`, "", table, "", `**Total signals:** ${totalSignals}`, `**Closed signals:** ${totalSignals}`, `**Win rate:** ${((winCount / totalSignals) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`, `**Average PNL:** ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`, `**Total PNL:** ${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`);
|
|
2727
2829
|
}
|
|
2728
2830
|
/**
|
|
2729
2831
|
* Saves strategy report to disk.
|
|
@@ -3118,9 +3220,10 @@ class ReportStorage {
|
|
|
3118
3220
|
return functoolsKit.str.newline(`# Live Trading Report: ${strategyName}`, "", "No events recorded yet.");
|
|
3119
3221
|
}
|
|
3120
3222
|
const header = columns.map((col) => col.label);
|
|
3223
|
+
const separator = columns.map(() => "---");
|
|
3121
3224
|
const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
|
|
3122
|
-
const tableData = [header, ...rows];
|
|
3123
|
-
const table = functoolsKit.str.
|
|
3225
|
+
const tableData = [header, separator, ...rows];
|
|
3226
|
+
const table = functoolsKit.str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
|
|
3124
3227
|
// Calculate statistics
|
|
3125
3228
|
const closedEvents = this._eventList.filter((e) => e.action === "closed");
|
|
3126
3229
|
const totalClosed = closedEvents.length;
|
|
@@ -3129,11 +3232,14 @@ class ReportStorage {
|
|
|
3129
3232
|
const avgPnl = totalClosed > 0
|
|
3130
3233
|
? closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0) / totalClosed
|
|
3131
3234
|
: 0;
|
|
3132
|
-
|
|
3133
|
-
|
|
3235
|
+
const totalPnl = closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0);
|
|
3236
|
+
return functoolsKit.str.newline(`# Live Trading Report: ${strategyName}`, "", table, "", `**Total events:** ${this._eventList.length}`, `**Closed signals:** ${totalClosed}`, totalClosed > 0
|
|
3237
|
+
? `**Win rate:** ${((winCount / totalClosed) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`
|
|
3134
3238
|
: "", totalClosed > 0
|
|
3135
|
-
?
|
|
3136
|
-
: "",
|
|
3239
|
+
? `**Average PNL:** ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`
|
|
3240
|
+
: "", totalClosed > 0
|
|
3241
|
+
? `**Total PNL:** ${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`
|
|
3242
|
+
: "");
|
|
3137
3243
|
}
|
|
3138
3244
|
/**
|
|
3139
3245
|
* Saves strategy report to disk.
|
|
@@ -4164,6 +4270,7 @@ class BacktestUtils {
|
|
|
4164
4270
|
context,
|
|
4165
4271
|
});
|
|
4166
4272
|
backtest$1.backtestMarkdownService.clear(context.strategyName);
|
|
4273
|
+
backtest$1.strategyGlobalService.clear(context.strategyName);
|
|
4167
4274
|
return backtest$1.backtestGlobalService.run(symbol, context);
|
|
4168
4275
|
};
|
|
4169
4276
|
/**
|
|
@@ -4192,14 +4299,9 @@ class BacktestUtils {
|
|
|
4192
4299
|
symbol,
|
|
4193
4300
|
context,
|
|
4194
4301
|
});
|
|
4195
|
-
const iterator = this.run(symbol, context);
|
|
4196
4302
|
let isStopped = false;
|
|
4197
4303
|
const task = async () => {
|
|
4198
|
-
|
|
4199
|
-
const { done } = await iterator.next();
|
|
4200
|
-
if (done) {
|
|
4201
|
-
break;
|
|
4202
|
-
}
|
|
4304
|
+
for await (const _ of this.run(symbol, context)) {
|
|
4203
4305
|
if (isStopped) {
|
|
4204
4306
|
break;
|
|
4205
4307
|
}
|
|
@@ -4213,6 +4315,7 @@ class BacktestUtils {
|
|
|
4213
4315
|
};
|
|
4214
4316
|
task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
4215
4317
|
return () => {
|
|
4318
|
+
backtest$1.strategyGlobalService.stop(context.strategyName);
|
|
4216
4319
|
isStopped = true;
|
|
4217
4320
|
};
|
|
4218
4321
|
};
|
|
@@ -4329,6 +4432,7 @@ class LiveUtils {
|
|
|
4329
4432
|
context,
|
|
4330
4433
|
});
|
|
4331
4434
|
backtest$1.liveMarkdownService.clear(context.strategyName);
|
|
4435
|
+
backtest$1.strategyGlobalService.clear(context.strategyName);
|
|
4332
4436
|
return backtest$1.liveGlobalService.run(symbol, context);
|
|
4333
4437
|
};
|
|
4334
4438
|
/**
|
|
@@ -4357,19 +4461,10 @@ class LiveUtils {
|
|
|
4357
4461
|
symbol,
|
|
4358
4462
|
context,
|
|
4359
4463
|
});
|
|
4360
|
-
const iterator = this.run(symbol, context);
|
|
4361
4464
|
let isStopped = false;
|
|
4362
|
-
let lastValue = null;
|
|
4363
4465
|
const task = async () => {
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
if (value) {
|
|
4367
|
-
lastValue = value;
|
|
4368
|
-
}
|
|
4369
|
-
if (done) {
|
|
4370
|
-
break;
|
|
4371
|
-
}
|
|
4372
|
-
if (lastValue?.action === "closed" && isStopped) {
|
|
4466
|
+
for await (const signal of this.run(symbol, context)) {
|
|
4467
|
+
if (signal?.action === "closed" && isStopped) {
|
|
4373
4468
|
break;
|
|
4374
4469
|
}
|
|
4375
4470
|
}
|
|
@@ -4382,6 +4477,7 @@ class LiveUtils {
|
|
|
4382
4477
|
};
|
|
4383
4478
|
task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
4384
4479
|
return () => {
|
|
4480
|
+
backtest$1.strategyGlobalService.stop(context.strategyName);
|
|
4385
4481
|
isStopped = true;
|
|
4386
4482
|
};
|
|
4387
4483
|
};
|
package/build/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createActivator } from 'di-kit';
|
|
2
2
|
import { scoped } from 'di-scoped';
|
|
3
|
-
import { memoize, makeExtendable, singleshot, getErrorMessage, not, trycatch, retry, randomString,
|
|
3
|
+
import { memoize, makeExtendable, singleshot, getErrorMessage, not, trycatch, retry, Subject, randomString, errorData, ToolRegistry, sleep, str, queued } from 'functools-kit';
|
|
4
4
|
import fs, { mkdir, writeFile } from 'fs/promises';
|
|
5
5
|
import path, { join } from 'path';
|
|
6
6
|
import crypto from 'crypto';
|
|
@@ -1102,6 +1102,32 @@ class PersistSignalUtils {
|
|
|
1102
1102
|
*/
|
|
1103
1103
|
const PersistSignalAdaper = new PersistSignalUtils();
|
|
1104
1104
|
|
|
1105
|
+
/**
|
|
1106
|
+
* Global signal emitter for all trading events (live + backtest).
|
|
1107
|
+
* Emits all signal events regardless of execution mode.
|
|
1108
|
+
*/
|
|
1109
|
+
const signalEmitter = new Subject();
|
|
1110
|
+
/**
|
|
1111
|
+
* Live trading signal emitter.
|
|
1112
|
+
* Emits only signals from live trading execution.
|
|
1113
|
+
*/
|
|
1114
|
+
const signalLiveEmitter = new Subject();
|
|
1115
|
+
/**
|
|
1116
|
+
* Backtest signal emitter.
|
|
1117
|
+
* Emits only signals from backtest execution.
|
|
1118
|
+
*/
|
|
1119
|
+
const signalBacktestEmitter = new Subject();
|
|
1120
|
+
/**
|
|
1121
|
+
* Error emitter for background execution errors.
|
|
1122
|
+
* Emits errors caught in background tasks (Live.background, Backtest.background).
|
|
1123
|
+
*/
|
|
1124
|
+
const errorEmitter = new Subject();
|
|
1125
|
+
/**
|
|
1126
|
+
* Done emitter for background execution completion.
|
|
1127
|
+
* Emits when background tasks complete (Live.background, Backtest.background).
|
|
1128
|
+
*/
|
|
1129
|
+
const doneEmitter = new Subject();
|
|
1130
|
+
|
|
1105
1131
|
const INTERVAL_MINUTES$1 = {
|
|
1106
1132
|
"1m": 1,
|
|
1107
1133
|
"3m": 3,
|
|
@@ -1153,6 +1179,9 @@ const VALIDATE_SIGNAL_FN = (signal) => {
|
|
|
1153
1179
|
}
|
|
1154
1180
|
};
|
|
1155
1181
|
const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
1182
|
+
if (self._isStopped) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1156
1185
|
const currentTime = self.params.execution.context.when.getTime();
|
|
1157
1186
|
{
|
|
1158
1187
|
const intervalMinutes = INTERVAL_MINUTES$1[self.params.interval];
|
|
@@ -1181,6 +1210,13 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
1181
1210
|
return signalRow;
|
|
1182
1211
|
}, {
|
|
1183
1212
|
defaultValue: null,
|
|
1213
|
+
fallback: (error) => {
|
|
1214
|
+
backtest$1.loggerService.warn("ClientStrategy exception thrown", {
|
|
1215
|
+
error: errorData(error),
|
|
1216
|
+
message: getErrorMessage(error),
|
|
1217
|
+
});
|
|
1218
|
+
errorEmitter.next(error);
|
|
1219
|
+
},
|
|
1184
1220
|
});
|
|
1185
1221
|
const GET_AVG_PRICE_FN = (candles) => {
|
|
1186
1222
|
const sumPriceVolume = candles.reduce((acc, c) => {
|
|
@@ -1239,6 +1275,7 @@ const WAIT_FOR_INIT_FN = async (self) => {
|
|
|
1239
1275
|
class ClientStrategy {
|
|
1240
1276
|
constructor(params) {
|
|
1241
1277
|
this.params = params;
|
|
1278
|
+
this._isStopped = false;
|
|
1242
1279
|
this._pendingSignal = null;
|
|
1243
1280
|
this._lastSignalTimestamp = null;
|
|
1244
1281
|
/**
|
|
@@ -1561,34 +1598,32 @@ class ClientStrategy {
|
|
|
1561
1598
|
}
|
|
1562
1599
|
return result;
|
|
1563
1600
|
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Stops the strategy from generating new signals.
|
|
1603
|
+
*
|
|
1604
|
+
* Sets internal flag to prevent getSignal from being called.
|
|
1605
|
+
* Does NOT close active pending signals - they continue monitoring until TP/SL/time_expired.
|
|
1606
|
+
*
|
|
1607
|
+
* Use case: Graceful shutdown in live trading without forcing position closure.
|
|
1608
|
+
*
|
|
1609
|
+
* @returns Promise that resolves immediately when stop flag is set
|
|
1610
|
+
*
|
|
1611
|
+
* @example
|
|
1612
|
+
* ```typescript
|
|
1613
|
+
* // In Live.background() cancellation
|
|
1614
|
+
* await strategy.stop();
|
|
1615
|
+
* // Existing signal will continue until natural close
|
|
1616
|
+
* ```
|
|
1617
|
+
*/
|
|
1618
|
+
stop() {
|
|
1619
|
+
this.params.logger.debug("ClientStrategy stop", {
|
|
1620
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
1621
|
+
});
|
|
1622
|
+
this._isStopped = true;
|
|
1623
|
+
return Promise.resolve();
|
|
1624
|
+
}
|
|
1564
1625
|
}
|
|
1565
1626
|
|
|
1566
|
-
/**
|
|
1567
|
-
* Global signal emitter for all trading events (live + backtest).
|
|
1568
|
-
* Emits all signal events regardless of execution mode.
|
|
1569
|
-
*/
|
|
1570
|
-
const signalEmitter = new Subject();
|
|
1571
|
-
/**
|
|
1572
|
-
* Live trading signal emitter.
|
|
1573
|
-
* Emits only signals from live trading execution.
|
|
1574
|
-
*/
|
|
1575
|
-
const signalLiveEmitter = new Subject();
|
|
1576
|
-
/**
|
|
1577
|
-
* Backtest signal emitter.
|
|
1578
|
-
* Emits only signals from backtest execution.
|
|
1579
|
-
*/
|
|
1580
|
-
const signalBacktestEmitter = new Subject();
|
|
1581
|
-
/**
|
|
1582
|
-
* Error emitter for background execution errors.
|
|
1583
|
-
* Emits errors caught in background tasks (Live.background, Backtest.background).
|
|
1584
|
-
*/
|
|
1585
|
-
const errorEmitter = new Subject();
|
|
1586
|
-
/**
|
|
1587
|
-
* Done emitter for background execution completion.
|
|
1588
|
-
* Emits when background tasks complete (Live.background, Backtest.background).
|
|
1589
|
-
*/
|
|
1590
|
-
const doneEmitter = new Subject();
|
|
1591
|
-
|
|
1592
1627
|
/**
|
|
1593
1628
|
* Connection service routing strategy operations to correct ClientStrategy instance.
|
|
1594
1629
|
*
|
|
@@ -1687,6 +1722,36 @@ class StrategyConnectionService {
|
|
|
1687
1722
|
}
|
|
1688
1723
|
return tick;
|
|
1689
1724
|
};
|
|
1725
|
+
/**
|
|
1726
|
+
* Stops the specified strategy from generating new signals.
|
|
1727
|
+
*
|
|
1728
|
+
* Delegates to ClientStrategy.stop() which sets internal flag to prevent
|
|
1729
|
+
* getSignal from being called on subsequent ticks.
|
|
1730
|
+
*
|
|
1731
|
+
* @param strategyName - Name of strategy to stop
|
|
1732
|
+
* @returns Promise that resolves when stop flag is set
|
|
1733
|
+
*/
|
|
1734
|
+
this.stop = async (strategyName) => {
|
|
1735
|
+
this.loggerService.log("strategyConnectionService stop", {
|
|
1736
|
+
strategyName,
|
|
1737
|
+
});
|
|
1738
|
+
const strategy = this.getStrategy(strategyName);
|
|
1739
|
+
await strategy.stop();
|
|
1740
|
+
};
|
|
1741
|
+
/**
|
|
1742
|
+
* Clears the memoized ClientStrategy instance from cache.
|
|
1743
|
+
*
|
|
1744
|
+
* Forces re-initialization of strategy on next getStrategy call.
|
|
1745
|
+
* Useful for resetting strategy state or releasing resources.
|
|
1746
|
+
*
|
|
1747
|
+
* @param strategyName - Name of strategy to clear from cache
|
|
1748
|
+
*/
|
|
1749
|
+
this.clear = async (strategyName) => {
|
|
1750
|
+
this.loggerService.log("strategyConnectionService clear", {
|
|
1751
|
+
strategyName,
|
|
1752
|
+
});
|
|
1753
|
+
this.getStrategy.clear(strategyName);
|
|
1754
|
+
};
|
|
1690
1755
|
}
|
|
1691
1756
|
}
|
|
1692
1757
|
|
|
@@ -2030,6 +2095,35 @@ class StrategyGlobalService {
|
|
|
2030
2095
|
backtest,
|
|
2031
2096
|
});
|
|
2032
2097
|
};
|
|
2098
|
+
/**
|
|
2099
|
+
* Stops the strategy from generating new signals.
|
|
2100
|
+
*
|
|
2101
|
+
* Delegates to StrategyConnectionService.stop() to set internal flag.
|
|
2102
|
+
* Does not require execution context.
|
|
2103
|
+
*
|
|
2104
|
+
* @param strategyName - Name of strategy to stop
|
|
2105
|
+
* @returns Promise that resolves when stop flag is set
|
|
2106
|
+
*/
|
|
2107
|
+
this.stop = async (strategyName) => {
|
|
2108
|
+
this.loggerService.log("strategyGlobalService stop", {
|
|
2109
|
+
strategyName,
|
|
2110
|
+
});
|
|
2111
|
+
return await this.strategyConnectionService.stop(strategyName);
|
|
2112
|
+
};
|
|
2113
|
+
/**
|
|
2114
|
+
* Clears the memoized ClientStrategy instance from cache.
|
|
2115
|
+
*
|
|
2116
|
+
* Delegates to StrategyConnectionService.clear() to remove strategy from cache.
|
|
2117
|
+
* Forces re-initialization of strategy on next operation.
|
|
2118
|
+
*
|
|
2119
|
+
* @param strategyName - Name of strategy to clear from cache
|
|
2120
|
+
*/
|
|
2121
|
+
this.clear = async (strategyName) => {
|
|
2122
|
+
this.loggerService.log("strategyGlobalService clear", {
|
|
2123
|
+
strategyName,
|
|
2124
|
+
});
|
|
2125
|
+
return await this.strategyConnectionService.clear(strategyName);
|
|
2126
|
+
};
|
|
2033
2127
|
}
|
|
2034
2128
|
}
|
|
2035
2129
|
|
|
@@ -2260,7 +2354,7 @@ class FrameSchemaService {
|
|
|
2260
2354
|
register(key, value) {
|
|
2261
2355
|
this.loggerService.log(`frameSchemaService register`, { key });
|
|
2262
2356
|
this.validateShallow(value);
|
|
2263
|
-
this._registry.register(key, value);
|
|
2357
|
+
this._registry = this._registry.register(key, value);
|
|
2264
2358
|
}
|
|
2265
2359
|
/**
|
|
2266
2360
|
* Overrides an existing frame schema with partial updates.
|
|
@@ -2271,7 +2365,8 @@ class FrameSchemaService {
|
|
|
2271
2365
|
*/
|
|
2272
2366
|
override(key, value) {
|
|
2273
2367
|
this.loggerService.log(`frameSchemaService override`, { key });
|
|
2274
|
-
this._registry.override(key, value);
|
|
2368
|
+
this._registry = this._registry.override(key, value);
|
|
2369
|
+
return this._registry.get(key);
|
|
2275
2370
|
}
|
|
2276
2371
|
/**
|
|
2277
2372
|
* Retrieves a frame schema by name.
|
|
@@ -2718,10 +2813,17 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
2718
2813
|
return str.newline(`# Backtest Report: ${strategyName}`, "", "No signals closed yet.");
|
|
2719
2814
|
}
|
|
2720
2815
|
const header = columns$1.map((col) => col.label);
|
|
2816
|
+
const separator = columns$1.map(() => "---");
|
|
2721
2817
|
const rows = this._signalList.map((closedSignal) => columns$1.map((col) => col.format(closedSignal)));
|
|
2722
|
-
const tableData = [header, ...rows];
|
|
2723
|
-
const table = str.
|
|
2724
|
-
|
|
2818
|
+
const tableData = [header, separator, ...rows];
|
|
2819
|
+
const table = str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
|
|
2820
|
+
// Calculate statistics
|
|
2821
|
+
const totalSignals = this._signalList.length;
|
|
2822
|
+
const winCount = this._signalList.filter((s) => s.pnl.pnlPercentage > 0).length;
|
|
2823
|
+
const lossCount = this._signalList.filter((s) => s.pnl.pnlPercentage < 0).length;
|
|
2824
|
+
const avgPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0) / totalSignals;
|
|
2825
|
+
const totalPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0);
|
|
2826
|
+
return str.newline(`# Backtest Report: ${strategyName}`, "", table, "", `**Total signals:** ${totalSignals}`, `**Closed signals:** ${totalSignals}`, `**Win rate:** ${((winCount / totalSignals) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`, `**Average PNL:** ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`, `**Total PNL:** ${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`);
|
|
2725
2827
|
}
|
|
2726
2828
|
/**
|
|
2727
2829
|
* Saves strategy report to disk.
|
|
@@ -3116,9 +3218,10 @@ class ReportStorage {
|
|
|
3116
3218
|
return str.newline(`# Live Trading Report: ${strategyName}`, "", "No events recorded yet.");
|
|
3117
3219
|
}
|
|
3118
3220
|
const header = columns.map((col) => col.label);
|
|
3221
|
+
const separator = columns.map(() => "---");
|
|
3119
3222
|
const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
|
|
3120
|
-
const tableData = [header, ...rows];
|
|
3121
|
-
const table = str.
|
|
3223
|
+
const tableData = [header, separator, ...rows];
|
|
3224
|
+
const table = str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
|
|
3122
3225
|
// Calculate statistics
|
|
3123
3226
|
const closedEvents = this._eventList.filter((e) => e.action === "closed");
|
|
3124
3227
|
const totalClosed = closedEvents.length;
|
|
@@ -3127,11 +3230,14 @@ class ReportStorage {
|
|
|
3127
3230
|
const avgPnl = totalClosed > 0
|
|
3128
3231
|
? closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0) / totalClosed
|
|
3129
3232
|
: 0;
|
|
3130
|
-
|
|
3131
|
-
|
|
3233
|
+
const totalPnl = closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0);
|
|
3234
|
+
return str.newline(`# Live Trading Report: ${strategyName}`, "", table, "", `**Total events:** ${this._eventList.length}`, `**Closed signals:** ${totalClosed}`, totalClosed > 0
|
|
3235
|
+
? `**Win rate:** ${((winCount / totalClosed) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`
|
|
3132
3236
|
: "", totalClosed > 0
|
|
3133
|
-
?
|
|
3134
|
-
: "",
|
|
3237
|
+
? `**Average PNL:** ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`
|
|
3238
|
+
: "", totalClosed > 0
|
|
3239
|
+
? `**Total PNL:** ${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`
|
|
3240
|
+
: "");
|
|
3135
3241
|
}
|
|
3136
3242
|
/**
|
|
3137
3243
|
* Saves strategy report to disk.
|
|
@@ -4162,6 +4268,7 @@ class BacktestUtils {
|
|
|
4162
4268
|
context,
|
|
4163
4269
|
});
|
|
4164
4270
|
backtest$1.backtestMarkdownService.clear(context.strategyName);
|
|
4271
|
+
backtest$1.strategyGlobalService.clear(context.strategyName);
|
|
4165
4272
|
return backtest$1.backtestGlobalService.run(symbol, context);
|
|
4166
4273
|
};
|
|
4167
4274
|
/**
|
|
@@ -4190,14 +4297,9 @@ class BacktestUtils {
|
|
|
4190
4297
|
symbol,
|
|
4191
4298
|
context,
|
|
4192
4299
|
});
|
|
4193
|
-
const iterator = this.run(symbol, context);
|
|
4194
4300
|
let isStopped = false;
|
|
4195
4301
|
const task = async () => {
|
|
4196
|
-
|
|
4197
|
-
const { done } = await iterator.next();
|
|
4198
|
-
if (done) {
|
|
4199
|
-
break;
|
|
4200
|
-
}
|
|
4302
|
+
for await (const _ of this.run(symbol, context)) {
|
|
4201
4303
|
if (isStopped) {
|
|
4202
4304
|
break;
|
|
4203
4305
|
}
|
|
@@ -4211,6 +4313,7 @@ class BacktestUtils {
|
|
|
4211
4313
|
};
|
|
4212
4314
|
task().catch((error) => errorEmitter.next(new Error(getErrorMessage(error))));
|
|
4213
4315
|
return () => {
|
|
4316
|
+
backtest$1.strategyGlobalService.stop(context.strategyName);
|
|
4214
4317
|
isStopped = true;
|
|
4215
4318
|
};
|
|
4216
4319
|
};
|
|
@@ -4327,6 +4430,7 @@ class LiveUtils {
|
|
|
4327
4430
|
context,
|
|
4328
4431
|
});
|
|
4329
4432
|
backtest$1.liveMarkdownService.clear(context.strategyName);
|
|
4433
|
+
backtest$1.strategyGlobalService.clear(context.strategyName);
|
|
4330
4434
|
return backtest$1.liveGlobalService.run(symbol, context);
|
|
4331
4435
|
};
|
|
4332
4436
|
/**
|
|
@@ -4355,19 +4459,10 @@ class LiveUtils {
|
|
|
4355
4459
|
symbol,
|
|
4356
4460
|
context,
|
|
4357
4461
|
});
|
|
4358
|
-
const iterator = this.run(symbol, context);
|
|
4359
4462
|
let isStopped = false;
|
|
4360
|
-
let lastValue = null;
|
|
4361
4463
|
const task = async () => {
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
if (value) {
|
|
4365
|
-
lastValue = value;
|
|
4366
|
-
}
|
|
4367
|
-
if (done) {
|
|
4368
|
-
break;
|
|
4369
|
-
}
|
|
4370
|
-
if (lastValue?.action === "closed" && isStopped) {
|
|
4464
|
+
for await (const signal of this.run(symbol, context)) {
|
|
4465
|
+
if (signal?.action === "closed" && isStopped) {
|
|
4371
4466
|
break;
|
|
4372
4467
|
}
|
|
4373
4468
|
}
|
|
@@ -4380,6 +4475,7 @@ class LiveUtils {
|
|
|
4380
4475
|
};
|
|
4381
4476
|
task().catch((error) => errorEmitter.next(new Error(getErrorMessage(error))));
|
|
4382
4477
|
return () => {
|
|
4478
|
+
backtest$1.strategyGlobalService.stop(context.strategyName);
|
|
4383
4479
|
isStopped = true;
|
|
4384
4480
|
};
|
|
4385
4481
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backtest-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "A TypeScript library for trading system backtest",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Petr Tripolsky",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "rollup -c",
|
|
42
|
+
"test": "npm run build && node ./test/index.mjs",
|
|
42
43
|
"build:docs": "rimraf docs && mkdir docs && node ./scripts/dts-docs.cjs ./types.d.ts ./docs",
|
|
43
44
|
"docs:gpt": "npm run build && node ./scripts/gpt-docs.mjs",
|
|
44
45
|
"docs:uml": "npm run build && node ./scripts/uml.mjs",
|
package/types.d.ts
CHANGED
|
@@ -536,6 +536,27 @@ interface IStrategy {
|
|
|
536
536
|
* @returns Promise resolving to closed result (always completes signal)
|
|
537
537
|
*/
|
|
538
538
|
backtest: (candles: ICandleData[]) => Promise<IStrategyBacktestResult>;
|
|
539
|
+
/**
|
|
540
|
+
* Stops the strategy from generating new signals.
|
|
541
|
+
*
|
|
542
|
+
* Sets internal flag to prevent getSignal from being called on subsequent ticks.
|
|
543
|
+
* Does NOT force-close active pending signals - they continue monitoring until natural closure (TP/SL/time_expired).
|
|
544
|
+
*
|
|
545
|
+
* Use case: Graceful shutdown in live trading mode without abandoning open positions.
|
|
546
|
+
*
|
|
547
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
548
|
+
* @returns Promise that resolves immediately when stop flag is set
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* ```typescript
|
|
552
|
+
* // Graceful shutdown in Live.background() cancellation
|
|
553
|
+
* const cancel = await Live.background("BTCUSDT", { ... });
|
|
554
|
+
*
|
|
555
|
+
* // Later: stop new signals, let existing ones close naturally
|
|
556
|
+
* await cancel();
|
|
557
|
+
* ```
|
|
558
|
+
*/
|
|
559
|
+
stop: (symbol: string) => Promise<void>;
|
|
539
560
|
}
|
|
540
561
|
/**
|
|
541
562
|
* Unique strategy identifier.
|
|
@@ -1737,6 +1758,25 @@ declare class StrategyConnectionService implements IStrategy {
|
|
|
1737
1758
|
* @returns Promise resolving to backtest result (signal or idle)
|
|
1738
1759
|
*/
|
|
1739
1760
|
backtest: (candles: ICandleData[]) => Promise<IStrategyBacktestResult>;
|
|
1761
|
+
/**
|
|
1762
|
+
* Stops the specified strategy from generating new signals.
|
|
1763
|
+
*
|
|
1764
|
+
* Delegates to ClientStrategy.stop() which sets internal flag to prevent
|
|
1765
|
+
* getSignal from being called on subsequent ticks.
|
|
1766
|
+
*
|
|
1767
|
+
* @param strategyName - Name of strategy to stop
|
|
1768
|
+
* @returns Promise that resolves when stop flag is set
|
|
1769
|
+
*/
|
|
1770
|
+
stop: (strategyName: StrategyName) => Promise<void>;
|
|
1771
|
+
/**
|
|
1772
|
+
* Clears the memoized ClientStrategy instance from cache.
|
|
1773
|
+
*
|
|
1774
|
+
* Forces re-initialization of strategy on next getStrategy call.
|
|
1775
|
+
* Useful for resetting strategy state or releasing resources.
|
|
1776
|
+
*
|
|
1777
|
+
* @param strategyName - Name of strategy to clear from cache
|
|
1778
|
+
*/
|
|
1779
|
+
clear: (strategyName: StrategyName) => Promise<void>;
|
|
1740
1780
|
}
|
|
1741
1781
|
|
|
1742
1782
|
/**
|
|
@@ -1912,6 +1952,25 @@ declare class StrategyGlobalService {
|
|
|
1912
1952
|
* @returns Closed signal result with PNL
|
|
1913
1953
|
*/
|
|
1914
1954
|
backtest: (symbol: string, candles: ICandleData[], when: Date, backtest: boolean) => Promise<IStrategyBacktestResult>;
|
|
1955
|
+
/**
|
|
1956
|
+
* Stops the strategy from generating new signals.
|
|
1957
|
+
*
|
|
1958
|
+
* Delegates to StrategyConnectionService.stop() to set internal flag.
|
|
1959
|
+
* Does not require execution context.
|
|
1960
|
+
*
|
|
1961
|
+
* @param strategyName - Name of strategy to stop
|
|
1962
|
+
* @returns Promise that resolves when stop flag is set
|
|
1963
|
+
*/
|
|
1964
|
+
stop: (strategyName: StrategyName) => Promise<void>;
|
|
1965
|
+
/**
|
|
1966
|
+
* Clears the memoized ClientStrategy instance from cache.
|
|
1967
|
+
*
|
|
1968
|
+
* Delegates to StrategyConnectionService.clear() to remove strategy from cache.
|
|
1969
|
+
* Forces re-initialization of strategy on next operation.
|
|
1970
|
+
*
|
|
1971
|
+
* @param strategyName - Name of strategy to clear from cache
|
|
1972
|
+
*/
|
|
1973
|
+
clear: (strategyName: StrategyName) => Promise<void>;
|
|
1915
1974
|
}
|
|
1916
1975
|
|
|
1917
1976
|
/**
|
|
@@ -2066,7 +2125,7 @@ declare class FrameSchemaService {
|
|
|
2066
2125
|
* @param value - Partial schema updates
|
|
2067
2126
|
* @throws Error if frame name doesn't exist
|
|
2068
2127
|
*/
|
|
2069
|
-
override(key: FrameName, value: Partial<IFrameSchema>):
|
|
2128
|
+
override(key: FrameName, value: Partial<IFrameSchema>): IFrameSchema;
|
|
2070
2129
|
/**
|
|
2071
2130
|
* Retrieves a frame schema by name.
|
|
2072
2131
|
*
|