backtest-kit 1.13.3 → 1.13.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/types.d.ts CHANGED
@@ -3,6 +3,14 @@ import * as functools_kit from 'functools-kit';
3
3
  import { Subject } from 'functools-kit';
4
4
  import { WriteStream } from 'fs';
5
5
 
6
+ /**
7
+ * Retrieves current backtest timeframe for given symbol.
8
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
9
+ * @returns Promise resolving to array of Date objects representing tick timestamps
10
+ * @throws Error if called outside of backtest execution context
11
+ */
12
+ declare function getCurrentTimeframe(symbol: string): Promise<Date[]>;
13
+
6
14
  /**
7
15
  * Type alias for enum objects with string key-value pairs
8
16
  */
@@ -116,222 +124,6 @@ interface ValidateArgs<T = Enum> {
116
124
  */
117
125
  declare function validate(args?: Partial<Args>): Promise<void>;
118
126
 
119
- /**
120
- * Stops the strategy from generating new signals.
121
- *
122
- * Sets internal flag to prevent strategy from opening new signals.
123
- * Current active signal (if any) will complete normally.
124
- * Backtest/Live mode will stop at the next safe point (idle state or after signal closes).
125
- *
126
- * Automatically detects backtest/live mode from execution context.
127
- *
128
- * @param symbol - Trading pair symbol
129
- * @param strategyName - Strategy name to stop
130
- * @returns Promise that resolves when stop flag is set
131
- *
132
- * @example
133
- * ```typescript
134
- * import { stop } from "backtest-kit";
135
- *
136
- * // Stop strategy after some condition
137
- * await stop("BTCUSDT", "my-strategy");
138
- * ```
139
- */
140
- declare function stop(symbol: string): Promise<void>;
141
- /**
142
- * Cancels the scheduled signal without stopping the strategy.
143
- *
144
- * Clears the scheduled signal (waiting for priceOpen activation).
145
- * Does NOT affect active pending signals or strategy operation.
146
- * Does NOT set stop flag - strategy can continue generating new signals.
147
- *
148
- * Automatically detects backtest/live mode from execution context.
149
- *
150
- * @param symbol - Trading pair symbol
151
- * @param strategyName - Strategy name
152
- * @param cancelId - Optional cancellation ID for tracking user-initiated cancellations
153
- * @returns Promise that resolves when scheduled signal is cancelled
154
- *
155
- * @example
156
- * ```typescript
157
- * import { cancel } from "backtest-kit";
158
- *
159
- * // Cancel scheduled signal with custom ID
160
- * await cancel("BTCUSDT", "my-strategy", "manual-cancel-001");
161
- * ```
162
- */
163
- declare function cancel(symbol: string, cancelId?: string): Promise<void>;
164
- /**
165
- * Executes partial close at profit level (moving toward TP).
166
- *
167
- * Closes a percentage of the active pending position at profit.
168
- * Price must be moving toward take profit (in profit direction).
169
- *
170
- * Automatically detects backtest/live mode from execution context.
171
- *
172
- * @param symbol - Trading pair symbol
173
- * @param percentToClose - Percentage of position to close (0-100, absolute value)
174
- * @returns Promise<boolean> - true if partial close executed, false if skipped
175
- *
176
- * @throws Error if currentPrice is not in profit direction:
177
- * - LONG: currentPrice must be > priceOpen
178
- * - SHORT: currentPrice must be < priceOpen
179
- *
180
- * @example
181
- * ```typescript
182
- * import { partialProfit } from "backtest-kit";
183
- *
184
- * // Close 30% of LONG position at profit
185
- * const success = await partialProfit("BTCUSDT", 30);
186
- * if (success) {
187
- * console.log('Partial profit executed');
188
- * }
189
- * ```
190
- */
191
- declare function partialProfit(symbol: string, percentToClose: number): Promise<boolean>;
192
- /**
193
- * Executes partial close at loss level (moving toward SL).
194
- *
195
- * Closes a percentage of the active pending position at loss.
196
- * Price must be moving toward stop loss (in loss direction).
197
- *
198
- * Automatically detects backtest/live mode from execution context.
199
- *
200
- * @param symbol - Trading pair symbol
201
- * @param percentToClose - Percentage of position to close (0-100, absolute value)
202
- * @returns Promise<boolean> - true if partial close executed, false if skipped
203
- *
204
- * @throws Error if currentPrice is not in loss direction:
205
- * - LONG: currentPrice must be < priceOpen
206
- * - SHORT: currentPrice must be > priceOpen
207
- *
208
- * @example
209
- * ```typescript
210
- * import { partialLoss } from "backtest-kit";
211
- *
212
- * // Close 40% of LONG position at loss
213
- * const success = await partialLoss("BTCUSDT", 40);
214
- * if (success) {
215
- * console.log('Partial loss executed');
216
- * }
217
- * ```
218
- */
219
- declare function partialLoss(symbol: string, percentToClose: number): Promise<boolean>;
220
- /**
221
- * Adjusts the trailing stop-loss distance for an active pending signal.
222
- *
223
- * CRITICAL: Always calculates from ORIGINAL SL, not from current trailing SL.
224
- * This prevents error accumulation on repeated calls.
225
- * Larger percentShift ABSORBS smaller one (updates only towards better protection).
226
- *
227
- * Updates the stop-loss distance by a percentage adjustment relative to the ORIGINAL SL distance.
228
- * Negative percentShift tightens the SL (reduces distance, moves closer to entry).
229
- * Positive percentShift loosens the SL (increases distance, moves away from entry).
230
- *
231
- * Absorption behavior:
232
- * - First call: sets trailing SL unconditionally
233
- * - Subsequent calls: updates only if new SL is BETTER (protects more profit)
234
- * - For LONG: only accepts HIGHER SL (never moves down, closer to entry wins)
235
- * - For SHORT: only accepts LOWER SL (never moves up, closer to entry wins)
236
- *
237
- * Automatically detects backtest/live mode from execution context.
238
- *
239
- * @param symbol - Trading pair symbol
240
- * @param percentShift - Percentage adjustment to ORIGINAL SL distance (-100 to 100)
241
- * @param currentPrice - Current market price to check for intrusion
242
- * @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected (absorption/intrusion/conflict)
243
- *
244
- * @example
245
- * ```typescript
246
- * import { trailingStop } from "backtest-kit";
247
- *
248
- * // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
249
- *
250
- * // First call: tighten by 5%
251
- * const success1 = await trailingStop("BTCUSDT", -5, 102);
252
- * // success1 = true, newDistance = 10% - 5% = 5%, newSL = 95
253
- *
254
- * // Second call: try weaker protection (smaller percentShift)
255
- * const success2 = await trailingStop("BTCUSDT", -3, 102);
256
- * // success2 = false (SKIPPED: newSL=97 < 95, worse protection, larger % absorbs smaller)
257
- *
258
- * // Third call: stronger protection (larger percentShift)
259
- * const success3 = await trailingStop("BTCUSDT", -7, 102);
260
- * // success3 = true (ACCEPTED: newDistance = 10% - 7% = 3%, newSL = 97 > 95, better protection)
261
- * ```
262
- */
263
- declare function trailingStop(symbol: string, percentShift: number, currentPrice: number): Promise<boolean>;
264
- /**
265
- * Adjusts the trailing take-profit distance for an active pending signal.
266
- *
267
- * CRITICAL: Always calculates from ORIGINAL TP, not from current trailing TP.
268
- * This prevents error accumulation on repeated calls.
269
- * Larger percentShift ABSORBS smaller one (updates only towards more conservative TP).
270
- *
271
- * Updates the take-profit distance by a percentage adjustment relative to the ORIGINAL TP distance.
272
- * Negative percentShift brings TP closer to entry (more conservative).
273
- * Positive percentShift moves TP further from entry (more aggressive).
274
- *
275
- * Absorption behavior:
276
- * - First call: sets trailing TP unconditionally
277
- * - Subsequent calls: updates only if new TP is MORE CONSERVATIVE (closer to entry)
278
- * - For LONG: only accepts LOWER TP (never moves up, closer to entry wins)
279
- * - For SHORT: only accepts HIGHER TP (never moves down, closer to entry wins)
280
- *
281
- * Automatically detects backtest/live mode from execution context.
282
- *
283
- * @param symbol - Trading pair symbol
284
- * @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
285
- * @param currentPrice - Current market price to check for intrusion
286
- * @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected (absorption/intrusion/conflict)
287
- *
288
- * @example
289
- * ```typescript
290
- * import { trailingTake } from "backtest-kit";
291
- *
292
- * // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
293
- *
294
- * // First call: bring TP closer by 3%
295
- * const success1 = await trailingTake("BTCUSDT", -3, 102);
296
- * // success1 = true, newDistance = 10% - 3% = 7%, newTP = 107
297
- *
298
- * // Second call: try to move TP further (less conservative)
299
- * const success2 = await trailingTake("BTCUSDT", 2, 102);
300
- * // success2 = false (SKIPPED: newTP=112 > 107, less conservative, larger % absorbs smaller)
301
- *
302
- * // Third call: even more conservative
303
- * const success3 = await trailingTake("BTCUSDT", -5, 102);
304
- * // success3 = true (ACCEPTED: newDistance = 10% - 5% = 5%, newTP = 105 < 107, more conservative)
305
- * ```
306
- */
307
- declare function trailingTake(symbol: string, percentShift: number, currentPrice: number): Promise<boolean>;
308
- /**
309
- * Moves stop-loss to breakeven when price reaches threshold.
310
- *
311
- * Moves SL to entry price (zero-risk position) when current price has moved
312
- * far enough in profit direction to cover transaction costs.
313
- * Threshold is calculated as: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2
314
- *
315
- * Automatically detects backtest/live mode from execution context.
316
- * Automatically fetches current price via getAveragePrice.
317
- *
318
- * @param symbol - Trading pair symbol
319
- * @returns Promise<boolean> - true if breakeven was set, false if conditions not met
320
- *
321
- * @example
322
- * ```typescript
323
- * import { breakeven } from "backtest-kit";
324
- *
325
- * // LONG: entry=100, slippage=0.1%, fee=0.1%, threshold=0.4%
326
- * // Try to move SL to breakeven (activates when price >= 100.4)
327
- * const moved = await breakeven("BTCUSDT");
328
- * if (moved) {
329
- * console.log("Position moved to breakeven!");
330
- * }
331
- * ```
332
- */
333
- declare function breakeven(symbol: string): Promise<boolean>;
334
-
335
127
  /**
336
128
  * Execution context containing runtime parameters for strategy/exchange operations.
337
129
  *
@@ -1197,2262 +989,1845 @@ interface IBreakeven {
1197
989
  }
1198
990
 
1199
991
  /**
1200
- * Contract for breakeven events.
1201
- *
1202
- * Emitted by breakevenSubject when a signal's stop-loss is moved to breakeven (entry price).
1203
- * Used for tracking risk reduction milestones and monitoring strategy safety.
1204
- *
1205
- * Events are emitted only once per signal (idempotent - protected by ClientBreakeven state).
1206
- * Breakeven is triggered when price moves far enough in profit direction to cover transaction costs.
1207
- *
1208
- * Consumers:
1209
- * - BreakevenMarkdownService: Accumulates events for report generation
1210
- * - User callbacks via listenBreakeven() / listenBreakevenOnce()
1211
- *
1212
- * @example
1213
- * ```typescript
1214
- * import { listenBreakeven } from "backtest-kit";
1215
- *
1216
- * // Listen to all breakeven events
1217
- * listenBreakeven((event) => {
1218
- * console.log(`[${event.backtest ? "Backtest" : "Live"}] Signal ${event.data.id} moved to breakeven`);
1219
- * console.log(`Symbol: ${event.symbol}, Price: ${event.currentPrice}`);
1220
- * console.log(`Position: ${event.data.position}, Entry: ${event.data.priceOpen}`);
1221
- * console.log(`Original SL: ${event.data.priceStopLoss}, New SL: ${event.data.priceOpen}`);
1222
- * });
1223
- *
1224
- * // Wait for specific signal to reach breakeven
1225
- * listenBreakevenOnce(
1226
- * (event) => event.data.id === "target-signal-id",
1227
- * (event) => console.log("Signal reached breakeven:", event.data.id)
1228
- * );
1229
- * ```
992
+ * Signal generation interval for throttling.
993
+ * Enforces minimum time between getSignal calls.
1230
994
  */
1231
- interface BreakevenContract {
1232
- /**
1233
- * Trading pair symbol (e.g., "BTCUSDT").
1234
- * Identifies which market this breakeven event belongs to.
1235
- */
1236
- symbol: string;
1237
- /**
1238
- * Strategy name that generated this signal.
1239
- * Identifies which strategy execution this breakeven event belongs to.
1240
- */
1241
- strategyName: StrategyName;
1242
- /**
1243
- * Exchange name where this signal is being executed.
1244
- * Identifies which exchange this breakeven event belongs to.
1245
- */
995
+ type SignalInterval = "1m" | "3m" | "5m" | "15m" | "30m" | "1h";
996
+ /**
997
+ * Signal data transfer object returned by getSignal.
998
+ * Will be validated and augmented with auto-generated id.
999
+ */
1000
+ interface ISignalDto {
1001
+ /** Optional signal ID (auto-generated if not provided) */
1002
+ id?: string;
1003
+ /** Trade direction: "long" (buy) or "short" (sell) */
1004
+ position: "long" | "short";
1005
+ /** Human-readable description of signal reason */
1006
+ note?: string;
1007
+ /** Entry price for the position */
1008
+ priceOpen?: number;
1009
+ /** Take profit target price (must be > priceOpen for long, < priceOpen for short) */
1010
+ priceTakeProfit: number;
1011
+ /** Stop loss exit price (must be < priceOpen for long, > priceOpen for short) */
1012
+ priceStopLoss: number;
1013
+ /** Expected duration in minutes before time_expired */
1014
+ minuteEstimatedTime: number;
1015
+ }
1016
+ /**
1017
+ * Complete signal with auto-generated id.
1018
+ * Used throughout the system after validation.
1019
+ */
1020
+ interface ISignalRow extends ISignalDto {
1021
+ /** Unique signal identifier (UUID v4 auto-generated) */
1022
+ id: string;
1023
+ /** Entry price for the position */
1024
+ priceOpen: number;
1025
+ /** Unique exchange identifier for execution */
1246
1026
  exchangeName: ExchangeName;
1247
- /**
1248
- * Frame name where this signal is being executed.
1249
- * Identifies which frame this breakeven event belongs to (empty string for live mode).
1250
- */
1027
+ /** Unique strategy identifier for execution */
1028
+ strategyName: StrategyName;
1029
+ /** Unique frame identifier for execution (empty string for live mode) */
1251
1030
  frameName: FrameName;
1031
+ /** Signal creation timestamp in milliseconds (when signal was first created/scheduled) */
1032
+ scheduledAt: number;
1033
+ /** Pending timestamp in milliseconds (when position became pending/active at priceOpen) */
1034
+ pendingAt: number;
1035
+ /** Trading pair symbol (e.g., "BTCUSDT") */
1036
+ symbol: string;
1037
+ /** Internal runtime marker for scheduled signals */
1038
+ _isScheduled: boolean;
1252
1039
  /**
1253
- * Complete signal row data with original prices.
1254
- * Contains all signal information including originalPriceStopLoss, originalPriceTakeProfit, and totalExecuted.
1255
- */
1256
- data: IPublicSignalRow;
1257
- /**
1258
- * Current market price at which breakeven was triggered.
1259
- * Used to verify threshold calculation.
1040
+ * History of partial closes for PNL calculation.
1041
+ * Each entry contains type (profit/loss), percent closed, and price.
1042
+ * Used to calculate weighted PNL: Σ(percent_i × pnl_i) for each partial + (remaining% × final_pnl)
1043
+ *
1044
+ * Computed values (derived from this array):
1045
+ * - _tpClosed: Sum of all "profit" type partial close percentages
1046
+ * - _slClosed: Sum of all "loss" type partial close percentages
1047
+ * - _totalClosed: Sum of all partial close percentages (profit + loss)
1260
1048
  */
1261
- currentPrice: number;
1049
+ _partial?: Array<{
1050
+ /** Type of partial close: profit (moving toward TP) or loss (moving toward SL) */
1051
+ type: "profit" | "loss";
1052
+ /** Percentage of position closed (0-100) */
1053
+ percent: number;
1054
+ /** Price at which this partial was executed */
1055
+ price: number;
1056
+ }>;
1262
1057
  /**
1263
- * Execution mode flag.
1264
- * - true: Event from backtest execution (historical candle data)
1265
- * - false: Event from live trading (real-time tick)
1058
+ * Trailing stop-loss price that overrides priceStopLoss when set.
1059
+ * Updated by trailing() method based on position type and percentage distance.
1060
+ * - For LONG: moves upward as price moves toward TP (never moves down)
1061
+ * - For SHORT: moves downward as price moves toward TP (never moves up)
1062
+ * When _trailingPriceStopLoss is set, it replaces priceStopLoss for TP/SL checks.
1063
+ * Original priceStopLoss is preserved in persistence but ignored during execution.
1266
1064
  */
1267
- backtest: boolean;
1065
+ _trailingPriceStopLoss?: number;
1268
1066
  /**
1269
- * Event timestamp in milliseconds since Unix epoch.
1270
- *
1271
- * Timing semantics:
1272
- * - Live mode: when.getTime() at the moment breakeven was set
1273
- * - Backtest mode: candle.timestamp of the candle that triggered breakeven
1274
- *
1275
- * @example
1276
- * ```typescript
1277
- * const eventDate = new Date(event.timestamp);
1278
- * console.log(`Breakeven set at: ${eventDate.toISOString()}`);
1279
- * ```
1067
+ * Trailing take-profit price that overrides priceTakeProfit when set.
1068
+ * Created and managed by trailingTake() method for dynamic TP adjustment.
1069
+ * Allows moving TP further from or closer to current price based on strategy.
1070
+ * Updated by trailingTake() method based on position type and percentage distance.
1071
+ * - For LONG: can move upward (further) or downward (closer) from entry
1072
+ * - For SHORT: can move downward (further) or upward (closer) from entry
1073
+ * When _trailingPriceTakeProfit is set, it replaces priceTakeProfit for TP/SL checks.
1074
+ * Original priceTakeProfit is preserved in persistence but ignored during execution.
1280
1075
  */
1281
- timestamp: number;
1076
+ _trailingPriceTakeProfit?: number;
1282
1077
  }
1283
-
1284
1078
  /**
1285
- * Contract for partial profit level events.
1286
- *
1287
- * Emitted by partialProfitSubject when a signal reaches a profit level milestone (10%, 20%, 30%, etc).
1288
- * Used for tracking partial take-profit execution and monitoring strategy performance.
1289
- *
1290
- * Events are emitted only once per level per signal (Set-based deduplication in ClientPartial).
1291
- * Multiple levels can be emitted in a single tick if price jumps significantly.
1292
- *
1293
- * Consumers:
1294
- * - PartialMarkdownService: Accumulates events for report generation
1295
- * - User callbacks via listenPartialProfit() / listenPartialProfitOnce()
1296
- *
1297
- * @example
1298
- * ```typescript
1299
- * import { listenPartialProfit } from "backtest-kit";
1300
- *
1301
- * // Listen to all partial profit events
1302
- * listenPartialProfit((event) => {
1303
- * console.log(`[${event.backtest ? "Backtest" : "Live"}] Signal ${event.data.id} reached ${event.level}% profit`);
1304
- * console.log(`Symbol: ${event.symbol}, Price: ${event.currentPrice}`);
1305
- * console.log(`Position: ${event.data.position}, Entry: ${event.data.priceOpen}`);
1306
- * });
1307
- *
1308
- * // Wait for first 50% profit level
1309
- * listenPartialProfitOnce(
1310
- * (event) => event.level === 50,
1311
- * (event) => console.log("50% profit reached:", event.data.id)
1312
- * );
1313
- * ```
1079
+ * Scheduled signal row for delayed entry at specific price.
1080
+ * Inherits from ISignalRow - represents a signal waiting for price to reach priceOpen.
1081
+ * Once price reaches priceOpen, will be converted to regular _pendingSignal.
1082
+ * Note: pendingAt will be set to scheduledAt until activation, then updated to actual pending time.
1314
1083
  */
1315
- interface PartialProfitContract {
1316
- /**
1317
- * Trading pair symbol (e.g., "BTCUSDT").
1318
- * Identifies which market this profit event belongs to.
1319
- */
1320
- symbol: string;
1321
- /**
1322
- * Strategy name that generated this signal.
1323
- * Identifies which strategy execution this profit event belongs to.
1324
- */
1325
- strategyName: StrategyName;
1326
- /**
1327
- * Exchange name where this signal is being executed.
1328
- * Identifies which exchange this profit event belongs to.
1329
- */
1330
- exchangeName: ExchangeName;
1084
+ interface IScheduledSignalRow extends ISignalRow {
1085
+ /** Entry price for the position */
1086
+ priceOpen: number;
1087
+ }
1088
+ /**
1089
+ * Public signal row with original stop-loss and take-profit prices.
1090
+ * Extends ISignalRow to include originalPriceStopLoss and originalPriceTakeProfit for external visibility.
1091
+ * Used in public APIs to show user the original SL/TP even if trailing SL/TP are active.
1092
+ * This allows users to see both the current effective SL/TP and the original values set at signal creation.
1093
+ * The original prices remain unchanged even if _trailingPriceStopLoss or _trailingPriceTakeProfit modify the effective values.
1094
+ * Useful for transparency in reporting and user interfaces.
1095
+ * Note: originalPriceStopLoss/originalPriceTakeProfit are identical to priceStopLoss/priceTakeProfit at signal creation time.
1096
+ */
1097
+ interface IPublicSignalRow extends ISignalRow {
1331
1098
  /**
1332
- * Frame name where this signal is being executed.
1333
- * Identifies which frame this profit event belongs to (empty string for live mode).
1099
+ * Original stop-loss price set at signal creation.
1100
+ * Remains unchanged even if trailing stop-loss modifies effective SL.
1101
+ * Used for user visibility of initial SL parameters.
1334
1102
  */
1335
- frameName: FrameName;
1103
+ originalPriceStopLoss: number;
1336
1104
  /**
1337
- * Complete signal row data with original prices.
1338
- * Contains all signal information including originalPriceStopLoss, originalPriceTakeProfit, and totalExecuted.
1105
+ * Original take-profit price set at signal creation.
1106
+ * Remains unchanged even if trailing take-profit modifies effective TP.
1107
+ * Used for user visibility of initial TP parameters.
1339
1108
  */
1340
- data: IPublicSignalRow;
1109
+ originalPriceTakeProfit: number;
1341
1110
  /**
1342
- * Current market price at which this profit level was reached.
1343
- * Used to calculate actual profit percentage.
1111
+ * Total executed percentage from partial closes.
1112
+ * Sum of all percent values from _partial array (both profit and loss types).
1113
+ * Represents the total portion of the position that has been closed through partial executions.
1114
+ * Range: 0-100. Value of 0 means no partial closes, 100 means position fully closed through partials.
1344
1115
  */
1345
- currentPrice: number;
1116
+ totalExecuted: number;
1117
+ }
1118
+ /**
1119
+ * Risk signal row for internal risk management.
1120
+ * Extends ISignalDto to include priceOpen, originalPriceStopLoss and originalPriceTakeProfit.
1121
+ * Used in risk validation to access entry price and original SL/TP.
1122
+ */
1123
+ interface IRiskSignalRow extends IPublicSignalRow {
1346
1124
  /**
1347
- * Profit level milestone reached (10, 20, 30, 40, 50, 60, 70, 80, 90, or 100).
1348
- * Represents percentage profit relative to entry price.
1349
- *
1350
- * @example
1351
- * ```typescript
1352
- * // If entry was $50000 and level is 20:
1353
- * // currentPrice >= $60000 (20% profit)
1354
- * ```
1125
+ * Entry price for the position.
1355
1126
  */
1356
- level: PartialLevel;
1127
+ priceOpen: number;
1357
1128
  /**
1358
- * Execution mode flag.
1359
- * - true: Event from backtest execution (historical candle data)
1360
- * - false: Event from live trading (real-time tick)
1129
+ * Original stop-loss price set at signal creation.
1361
1130
  */
1362
- backtest: boolean;
1131
+ originalPriceStopLoss: number;
1363
1132
  /**
1364
- * Event timestamp in milliseconds since Unix epoch.
1365
- *
1366
- * Timing semantics:
1367
- * - Live mode: when.getTime() at the moment profit level was detected
1368
- * - Backtest mode: candle.timestamp of the candle that triggered the level
1369
- *
1370
- * @example
1371
- * ```typescript
1372
- * const eventDate = new Date(event.timestamp);
1373
- * console.log(`Profit reached at: ${eventDate.toISOString()}`);
1374
- * ```
1133
+ * Original take-profit price set at signal creation.
1375
1134
  */
1376
- timestamp: number;
1135
+ originalPriceTakeProfit: number;
1377
1136
  }
1378
-
1379
1137
  /**
1380
- * Contract for partial loss level events.
1381
- *
1382
- * Emitted by partialLossSubject when a signal reaches a loss level milestone (-10%, -20%, -30%, etc).
1383
- * Used for tracking partial stop-loss execution and monitoring strategy drawdown.
1384
- *
1385
- * Events are emitted only once per level per signal (Set-based deduplication in ClientPartial).
1386
- * Multiple levels can be emitted in a single tick if price drops significantly.
1387
- *
1388
- * Consumers:
1389
- * - PartialMarkdownService: Accumulates events for report generation
1390
- * - User callbacks via listenPartialLoss() / listenPartialLossOnce()
1391
- *
1392
- * @example
1393
- * ```typescript
1394
- * import { listenPartialLoss } from "backtest-kit";
1395
- *
1396
- * // Listen to all partial loss events
1397
- * listenPartialLoss((event) => {
1398
- * console.log(`[${event.backtest ? "Backtest" : "Live"}] Signal ${event.data.id} reached -${event.level}% loss`);
1399
- * console.log(`Symbol: ${event.symbol}, Price: ${event.currentPrice}`);
1400
- * console.log(`Position: ${event.data.position}, Entry: ${event.data.priceOpen}`);
1401
- *
1402
- * // Alert on significant loss
1403
- * if (event.level >= 30 && !event.backtest) {
1404
- * console.warn("HIGH LOSS ALERT:", event.data.id);
1405
- * }
1406
- * });
1407
- *
1408
- * // Wait for first 20% loss level
1409
- * listenPartialLossOnce(
1410
- * (event) => event.level === 20,
1411
- * (event) => console.log("20% loss reached:", event.data.id)
1412
- * );
1413
- * ```
1138
+ * Scheduled signal row with cancellation ID.
1139
+ * Extends IScheduledSignalRow to include optional cancelId for user-initiated cancellations.
1414
1140
  */
1415
- interface PartialLossContract {
1416
- /**
1417
- * Trading pair symbol (e.g., "BTCUSDT").
1418
- * Identifies which market this loss event belongs to.
1419
- */
1420
- symbol: string;
1421
- /**
1422
- * Strategy name that generated this signal.
1423
- * Identifies which strategy execution this loss event belongs to.
1424
- */
1141
+ interface IScheduledSignalCancelRow extends IScheduledSignalRow {
1142
+ /** Cancellation ID (only for user-initiated cancellations) */
1143
+ cancelId?: string;
1144
+ }
1145
+ /**
1146
+ * Optional lifecycle callbacks for signal events.
1147
+ * Called when signals are opened, active, idle, closed, scheduled, or cancelled.
1148
+ */
1149
+ interface IStrategyCallbacks {
1150
+ /** Called on every tick with the result */
1151
+ onTick: (symbol: string, result: IStrategyTickResult, backtest: boolean) => void | Promise<void>;
1152
+ /** Called when new signal is opened (after validation) */
1153
+ onOpen: (symbol: string, data: IPublicSignalRow, currentPrice: number, backtest: boolean) => void | Promise<void>;
1154
+ /** Called when signal is being monitored (active state) */
1155
+ onActive: (symbol: string, data: IPublicSignalRow, currentPrice: number, backtest: boolean) => void | Promise<void>;
1156
+ /** Called when no active signal exists (idle state) */
1157
+ onIdle: (symbol: string, currentPrice: number, backtest: boolean) => void | Promise<void>;
1158
+ /** Called when signal is closed with final price */
1159
+ onClose: (symbol: string, data: IPublicSignalRow, priceClose: number, backtest: boolean) => void | Promise<void>;
1160
+ /** Called when scheduled signal is created (delayed entry) */
1161
+ onSchedule: (symbol: string, data: IPublicSignalRow, currentPrice: number, backtest: boolean) => void | Promise<void>;
1162
+ /** Called when scheduled signal is cancelled without opening position */
1163
+ onCancel: (symbol: string, data: IPublicSignalRow, currentPrice: number, backtest: boolean) => void | Promise<void>;
1164
+ /** Called when signal is written to persist storage (for testing) */
1165
+ onWrite: (symbol: string, data: IPublicSignalRow | null, backtest: boolean) => void;
1166
+ /** Called when signal is in partial profit state (price moved favorably but not reached TP yet) */
1167
+ onPartialProfit: (symbol: string, data: IPublicSignalRow, currentPrice: number, revenuePercent: number, backtest: boolean) => void | Promise<void>;
1168
+ /** Called when signal is in partial loss state (price moved against position but not hit SL yet) */
1169
+ onPartialLoss: (symbol: string, data: IPublicSignalRow, currentPrice: number, lossPercent: number, backtest: boolean) => void | Promise<void>;
1170
+ /** Called when signal reaches breakeven (stop-loss moved to entry price to protect capital) */
1171
+ onBreakeven: (symbol: string, data: IPublicSignalRow, currentPrice: number, backtest: boolean) => void | Promise<void>;
1172
+ /** Called every minute regardless of strategy interval (for custom monitoring like checking if signal should be cancelled) */
1173
+ onPing: (symbol: string, data: IPublicSignalRow, when: Date, backtest: boolean) => void | Promise<void>;
1174
+ }
1175
+ /**
1176
+ * Strategy schema registered via addStrategy().
1177
+ * Defines signal generation logic and configuration.
1178
+ */
1179
+ interface IStrategySchema {
1180
+ /** Unique strategy identifier for registration */
1425
1181
  strategyName: StrategyName;
1182
+ /** Optional developer note for documentation */
1183
+ note?: string;
1184
+ /** Minimum interval between getSignal calls (throttling) */
1185
+ interval: SignalInterval;
1426
1186
  /**
1427
- * Exchange name where this signal is being executed.
1428
- * Identifies which exchange this loss event belongs to.
1187
+ * Signal generation function (returns null if no signal, validated DTO if signal).
1188
+ * If priceOpen is provided - becomes scheduled signal waiting for price to reach entry point.
1189
+ * If priceOpen is omitted - opens immediately at current price.
1429
1190
  */
1191
+ getSignal: (symbol: string, when: Date) => Promise<ISignalDto | null>;
1192
+ /** Optional lifecycle event callbacks (onOpen, onClose) */
1193
+ callbacks?: Partial<IStrategyCallbacks>;
1194
+ /** Optional risk profile identifier for risk management */
1195
+ riskName?: RiskName;
1196
+ /** Optional several risk profile list for risk management (if multiple required) */
1197
+ riskList?: RiskName[];
1198
+ /** Optional list of action identifiers to attach to this strategy */
1199
+ actions?: ActionName[];
1200
+ }
1201
+ /**
1202
+ * Reason why signal was closed.
1203
+ * Used in discriminated union for type-safe handling.
1204
+ */
1205
+ type StrategyCloseReason = "time_expired" | "take_profit" | "stop_loss";
1206
+ /**
1207
+ * Reason why scheduled signal was cancelled.
1208
+ * Used in discriminated union for type-safe handling.
1209
+ */
1210
+ type StrategyCancelReason = "timeout" | "price_reject" | "user";
1211
+ /**
1212
+ * Profit and loss calculation result.
1213
+ * Includes adjusted prices with fees (0.1%) and slippage (0.1%).
1214
+ */
1215
+ interface IStrategyPnL {
1216
+ /** Profit/loss as percentage (e.g., 1.5 for +1.5%, -2.3 for -2.3%) */
1217
+ pnlPercentage: number;
1218
+ /** Entry price adjusted with slippage and fees */
1219
+ priceOpen: number;
1220
+ /** Exit price adjusted with slippage and fees */
1221
+ priceClose: number;
1222
+ }
1223
+ /**
1224
+ * Tick result: no active signal, idle state.
1225
+ */
1226
+ interface IStrategyTickResultIdle {
1227
+ /** Discriminator for type-safe union */
1228
+ action: "idle";
1229
+ /** No signal in idle state */
1230
+ signal: null;
1231
+ /** Strategy name for tracking idle events */
1232
+ strategyName: StrategyName;
1233
+ /** Exchange name for tracking idle events */
1430
1234
  exchangeName: ExchangeName;
1431
- /**
1432
- * Frame name where this signal is being executed.
1433
- * Identifies which frame this loss event belongs to (empty string for live mode).
1434
- */
1235
+ /** Time frame name for tracking (e.g., "1m", "5m") */
1435
1236
  frameName: FrameName;
1436
- /**
1437
- * Complete signal row data with original prices.
1438
- * Contains all signal information including originalPriceStopLoss, originalPriceTakeProfit, and totalExecuted.
1439
- */
1440
- data: IPublicSignalRow;
1441
- /**
1442
- * Current market price at which this loss level was reached.
1443
- * Used to calculate actual loss percentage.
1444
- */
1237
+ /** Trading pair symbol (e.g., "BTCUSDT") */
1238
+ symbol: string;
1239
+ /** Current VWAP price during idle state */
1445
1240
  currentPrice: number;
1446
- /**
1447
- * Loss level milestone reached (10, 20, 30, 40, 50, 60, 70, 80, 90, or 100).
1448
- * Represents percentage loss relative to entry price (absolute value).
1449
- *
1450
- * Note: Stored as positive number, but represents negative loss.
1451
- * level=20 means -20% loss from entry price.
1452
- *
1453
- * @example
1454
- * ```typescript
1455
- * // If entry was $50000 and level is 20:
1456
- * // currentPrice <= $40000 (-20% loss)
1457
- * // Level is stored as 20, not -20
1458
- * ```
1459
- */
1460
- level: PartialLevel;
1461
- /**
1462
- * Execution mode flag.
1463
- * - true: Event from backtest execution (historical candle data)
1464
- * - false: Event from live trading (real-time tick)
1465
- */
1241
+ /** Whether this event is from backtest mode (true) or live mode (false) */
1466
1242
  backtest: boolean;
1467
- /**
1468
- * Event timestamp in milliseconds since Unix epoch.
1469
- *
1470
- * Timing semantics:
1471
- * - Live mode: when.getTime() at the moment loss level was detected
1472
- * - Backtest mode: candle.timestamp of the candle that triggered the level
1473
- *
1474
- * @example
1475
- * ```typescript
1476
- * const eventDate = new Date(event.timestamp);
1477
- * console.log(`Loss reached at: ${eventDate.toISOString()}`);
1478
- *
1479
- * // Calculate time in loss
1480
- * const entryTime = event.data.pendingAt;
1481
- * const timeInLoss = event.timestamp - entryTime;
1482
- * console.log(`In loss for ${timeInLoss / 1000 / 60} minutes`);
1483
- * ```
1484
- */
1485
- timestamp: number;
1486
1243
  }
1487
-
1488
1244
  /**
1489
- * Contract for ping events during scheduled signal monitoring.
1490
- *
1491
- * Emitted by pingSubject every minute when a scheduled signal is being monitored.
1492
- * Used for tracking scheduled signal lifecycle and custom monitoring logic.
1493
- *
1494
- * Events are emitted only when scheduled signal is active (not cancelled, not activated).
1495
- * Allows users to implement custom cancellation logic via onPing callback.
1496
- *
1497
- * Consumers:
1498
- * - User callbacks via listenPing() / listenPingOnce()
1499
- *
1500
- * @example
1501
- * ```typescript
1502
- * import { listenPing } from "backtest-kit";
1503
- *
1504
- * // Listen to all ping events
1505
- * listenPing((event) => {
1506
- * console.log(`[${event.backtest ? "Backtest" : "Live"}] Ping for ${event.symbol}`);
1507
- * console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
1508
- * console.log(`Signal ID: ${event.data.id}, priceOpen: ${event.data.priceOpen}`);
1509
- * console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
1510
- * });
1511
- *
1512
- * // Wait for specific ping
1513
- * listenPingOnce(
1514
- * (event) => event.symbol === "BTCUSDT",
1515
- * (event) => console.log("BTCUSDT ping received:", event.timestamp)
1516
- * );
1517
- * ```
1245
+ * Tick result: scheduled signal created, waiting for price to reach entry point.
1246
+ * Triggered when getSignal returns signal with priceOpen specified.
1518
1247
  */
1519
- interface PingContract {
1520
- /**
1521
- * Trading pair symbol (e.g., "BTCUSDT").
1522
- * Identifies which market this ping event belongs to.
1523
- */
1524
- symbol: string;
1525
- /**
1526
- * Strategy name that is monitoring this scheduled signal.
1527
- * Identifies which strategy execution this ping event belongs to.
1528
- */
1248
+ interface IStrategyTickResultScheduled {
1249
+ /** Discriminator for type-safe union */
1250
+ action: "scheduled";
1251
+ /** Scheduled signal waiting for activation */
1252
+ signal: IPublicSignalRow;
1253
+ /** Strategy name for tracking */
1529
1254
  strategyName: StrategyName;
1530
- /**
1531
- * Exchange name where this scheduled signal is being monitored.
1532
- * Identifies which exchange this ping event belongs to.
1533
- */
1255
+ /** Exchange name for tracking */
1534
1256
  exchangeName: ExchangeName;
1535
- /**
1536
- * Complete scheduled signal row data.
1537
- * Contains all signal information: id, position, priceOpen, priceTakeProfit, priceStopLoss, etc.
1538
- */
1539
- data: IScheduledSignalRow;
1540
- /**
1541
- * Execution mode flag.
1542
- * - true: Event from backtest execution (historical candle data)
1543
- * - false: Event from live trading (real-time tick)
1544
- */
1257
+ /** Time frame name for tracking (e.g., "1m", "5m") */
1258
+ frameName: FrameName;
1259
+ /** Trading pair symbol (e.g., "BTCUSDT") */
1260
+ symbol: string;
1261
+ /** Current VWAP price when scheduled signal created */
1262
+ currentPrice: number;
1263
+ /** Whether this event is from backtest mode (true) or live mode (false) */
1545
1264
  backtest: boolean;
1546
- /**
1547
- * Event timestamp in milliseconds since Unix epoch.
1548
- *
1549
- * Timing semantics:
1550
- * - Live mode: when.getTime() at the moment of ping
1551
- * - Backtest mode: candle.timestamp of the candle being processed
1552
- *
1553
- * @example
1554
- * ```typescript
1555
- * const eventDate = new Date(event.timestamp);
1556
- * console.log(`Ping at: ${eventDate.toISOString()}`);
1557
- * ```
1558
- */
1559
- timestamp: number;
1560
1265
  }
1561
-
1562
1266
  /**
1563
- * Contract for risk rejection events.
1564
- *
1565
- * Emitted by riskSubject ONLY when a signal is REJECTED due to risk validation failure.
1566
- * Used for tracking actual risk violations and monitoring rejected signals.
1567
- *
1568
- * Events are emitted only when risk limits are violated (not for allowed signals).
1569
- * This prevents spam and allows focusing on actual risk management interventions.
1570
- *
1571
- * Consumers:
1572
- * - RiskMarkdownService: Accumulates rejection events for report generation
1573
- * - User callbacks via listenRisk() / listenRiskOnce()
1574
- *
1575
- * @example
1576
- * ```typescript
1577
- * import { listenRisk } from "backtest-kit";
1578
- *
1579
- * // Listen to all risk rejection events
1580
- * listenRisk((event) => {
1581
- * console.log(`[RISK REJECTED] Signal for ${event.symbol}`);
1582
- * console.log(`Strategy: ${event.strategyName}`);
1583
- * console.log(`Active positions: ${event.activePositionCount}`);
1584
- * console.log(`Price: ${event.currentPrice}`);
1585
- * console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
1586
- * });
1587
- *
1588
- * // Alert on risk rejections for specific symbol
1589
- * listenRisk((event) => {
1590
- * if (event.symbol === "BTCUSDT") {
1591
- * console.warn("BTC signal rejected due to risk limits!");
1592
- * }
1593
- * });
1594
- * ```
1267
+ * Tick result: new signal just created.
1268
+ * Triggered after getSignal validation and persistence.
1595
1269
  */
1596
- interface RiskContract {
1597
- /**
1598
- * Trading pair symbol (e.g., "BTCUSDT").
1599
- * Identifies which market this rejected signal belongs to.
1600
- */
1270
+ interface IStrategyTickResultOpened {
1271
+ /** Discriminator for type-safe union */
1272
+ action: "opened";
1273
+ /** Newly created and validated signal with generated ID */
1274
+ signal: IPublicSignalRow;
1275
+ /** Strategy name for tracking */
1276
+ strategyName: StrategyName;
1277
+ /** Exchange name for tracking */
1278
+ exchangeName: ExchangeName;
1279
+ /** Time frame name for tracking (e.g., "1m", "5m") */
1280
+ frameName: FrameName;
1281
+ /** Trading pair symbol (e.g., "BTCUSDT") */
1601
1282
  symbol: string;
1602
- /**
1603
- * Pending signal to apply.
1604
- * Contains signal details (position, priceOpen, priceTakeProfit, priceStopLoss, etc).
1605
- */
1606
- pendingSignal: ISignalDto;
1607
- /**
1608
- * Strategy name requesting to open a position.
1609
- * Identifies which strategy attempted to create the signal.
1610
- */
1283
+ /** Current VWAP price at signal open */
1284
+ currentPrice: number;
1285
+ /** Whether this event is from backtest mode (true) or live mode (false) */
1286
+ backtest: boolean;
1287
+ }
1288
+ /**
1289
+ * Tick result: signal is being monitored.
1290
+ * Waiting for TP/SL or time expiration.
1291
+ */
1292
+ interface IStrategyTickResultActive {
1293
+ /** Discriminator for type-safe union */
1294
+ action: "active";
1295
+ /** Currently monitored signal */
1296
+ signal: IPublicSignalRow;
1297
+ /** Current VWAP price for monitoring */
1298
+ currentPrice: number;
1299
+ /** Strategy name for tracking */
1611
1300
  strategyName: StrategyName;
1612
- /**
1613
- * Frame name used in backtest execution.
1614
- * Identifies which frame this signal was for in backtest execution.
1615
- */
1301
+ /** Exchange name for tracking */
1302
+ exchangeName: ExchangeName;
1303
+ /** Time frame name for tracking (e.g., "1m", "5m") */
1616
1304
  frameName: FrameName;
1617
- /**
1618
- * Exchange name.
1619
- * Identifies which exchange this signal was for.
1620
- */
1305
+ /** Trading pair symbol (e.g., "BTCUSDT") */
1306
+ symbol: string;
1307
+ /** Percentage progress towards take profit (0-100%, 0 if moving towards SL) */
1308
+ percentTp: number;
1309
+ /** Percentage progress towards stop loss (0-100%, 0 if moving towards TP) */
1310
+ percentSl: number;
1311
+ /** Unrealized PNL for active position with fees, slippage, and partial closes */
1312
+ pnl: IStrategyPnL;
1313
+ /** Whether this event is from backtest mode (true) or live mode (false) */
1314
+ backtest: boolean;
1315
+ }
1316
+ /**
1317
+ * Tick result: signal closed with PNL.
1318
+ * Final state with close reason and profit/loss calculation.
1319
+ */
1320
+ interface IStrategyTickResultClosed {
1321
+ /** Discriminator for type-safe union */
1322
+ action: "closed";
1323
+ /** Completed signal with original parameters */
1324
+ signal: IPublicSignalRow;
1325
+ /** Final VWAP price at close */
1326
+ currentPrice: number;
1327
+ /** Why signal closed (time_expired | take_profit | stop_loss) */
1328
+ closeReason: StrategyCloseReason;
1329
+ /** Unix timestamp in milliseconds when signal closed */
1330
+ closeTimestamp: number;
1331
+ /** Profit/loss calculation with fees and slippage */
1332
+ pnl: IStrategyPnL;
1333
+ /** Strategy name for tracking */
1334
+ strategyName: StrategyName;
1335
+ /** Exchange name for tracking */
1621
1336
  exchangeName: ExchangeName;
1622
- /**
1623
- * Current VWAP price at the time of rejection.
1624
- * Market price when risk check was performed.
1625
- */
1337
+ /** Time frame name for tracking (e.g., "1m", "5m") */
1338
+ frameName: FrameName;
1339
+ /** Trading pair symbol (e.g., "BTCUSDT") */
1340
+ symbol: string;
1341
+ /** Whether this event is from backtest mode (true) or live mode (false) */
1342
+ backtest: boolean;
1343
+ }
1344
+ /**
1345
+ * Tick result: scheduled signal cancelled without opening position.
1346
+ * Occurs when scheduled signal doesn't activate or hits stop loss before entry.
1347
+ */
1348
+ interface IStrategyTickResultCancelled {
1349
+ /** Discriminator for type-safe union */
1350
+ action: "cancelled";
1351
+ /** Cancelled scheduled signal */
1352
+ signal: IPublicSignalRow;
1353
+ /** Final VWAP price at cancellation */
1626
1354
  currentPrice: number;
1355
+ /** Unix timestamp in milliseconds when signal cancelled */
1356
+ closeTimestamp: number;
1357
+ /** Strategy name for tracking */
1358
+ strategyName: StrategyName;
1359
+ /** Exchange name for tracking */
1360
+ exchangeName: ExchangeName;
1361
+ /** Time frame name for tracking (e.g., "1m", "5m") */
1362
+ frameName: FrameName;
1363
+ /** Trading pair symbol (e.g., "BTCUSDT") */
1364
+ symbol: string;
1365
+ /** Whether this event is from backtest mode (true) or live mode (false) */
1366
+ backtest: boolean;
1367
+ /** Reason for cancellation */
1368
+ reason: StrategyCancelReason;
1369
+ /** Optional cancellation ID (provided when user calls Backtest.cancel() or Live.cancel()) */
1370
+ cancelId?: string;
1371
+ }
1372
+ /**
1373
+ * Discriminated union of all tick results.
1374
+ * Use type guards: `result.action === "closed"` for type safety.
1375
+ */
1376
+ type IStrategyTickResult = IStrategyTickResultIdle | IStrategyTickResultScheduled | IStrategyTickResultOpened | IStrategyTickResultActive | IStrategyTickResultClosed | IStrategyTickResultCancelled;
1377
+ /**
1378
+ * Backtest returns closed result (TP/SL or time_expired) or cancelled result (scheduled signal never activated).
1379
+ */
1380
+ type IStrategyBacktestResult = IStrategyTickResultClosed | IStrategyTickResultCancelled;
1381
+ /**
1382
+ * Strategy interface implemented by ClientStrategy.
1383
+ * Defines core strategy execution methods.
1384
+ */
1385
+ interface IStrategy {
1627
1386
  /**
1628
- * Number of currently active positions across all strategies at rejection time.
1629
- * Used to track portfolio-level exposure when signal was rejected.
1387
+ * Single tick of strategy execution with VWAP monitoring.
1388
+ * Checks for signal generation (throttled) and TP/SL conditions.
1389
+ *
1390
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
1391
+ * @param strategyName - Name of the strategy
1392
+ * @returns Promise resolving to tick result (idle | opened | active | closed)
1630
1393
  */
1631
- activePositionCount: number;
1394
+ tick: (symbol: string, strategyName: StrategyName) => Promise<IStrategyTickResult>;
1632
1395
  /**
1633
- * Unique identifier for this rejection instance.
1634
- * Generated by ClientRisk for tracking and debugging purposes.
1635
- * Null if validation threw exception without custom ID.
1396
+ * Retrieves the currently active pending signal for the symbol.
1397
+ * If no active signal exists, returns null.
1398
+ * Used internally for monitoring TP/SL and time expiration.
1399
+ *
1400
+ * @param symbol - Trading pair symbol
1401
+ * @returns Promise resolving to pending signal or null
1636
1402
  */
1637
- rejectionId: string | null;
1403
+ getPendingSignal: (symbol: string) => Promise<IPublicSignalRow | null>;
1638
1404
  /**
1639
- * Human-readable reason why the signal was rejected.
1640
- * Captured from IRiskValidation.note or error message.
1405
+ * Retrieves the currently active scheduled signal for the symbol.
1406
+ * If no scheduled signal exists, returns null.
1407
+ * Used internally for monitoring scheduled signal activation.
1641
1408
  *
1642
- * @example
1643
- * ```typescript
1644
- * console.log(`Rejection reason: ${event.rejectionNote}`);
1645
- * // Output: "Rejection reason: Max 3 positions allowed"
1646
- * ```
1409
+ * @param symbol - Trading pair symbol
1410
+ * @returns Promise resolving to scheduled signal or null
1647
1411
  */
1648
- rejectionNote: string;
1412
+ getScheduledSignal: (symbol: string) => Promise<IPublicSignalRow | null>;
1649
1413
  /**
1650
- * Event timestamp in milliseconds since Unix epoch.
1651
- * Represents when the signal was rejected.
1414
+ * Checks if breakeven threshold has been reached for the current pending signal.
1415
+ *
1416
+ * Uses the same formula as BREAKEVEN_FN to determine if price has moved far enough
1417
+ * to cover transaction costs (slippage + fees) and allow breakeven to be set.
1418
+ * Threshold: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2 transactions
1419
+ *
1420
+ * For LONG position:
1421
+ * - Returns true when: currentPrice >= priceOpen * (1 + threshold%)
1422
+ * - Example: entry=100, threshold=0.4% → true when price >= 100.4
1423
+ *
1424
+ * For SHORT position:
1425
+ * - Returns true when: currentPrice <= priceOpen * (1 - threshold%)
1426
+ * - Example: entry=100, threshold=0.4% → true when price <= 99.6
1427
+ *
1428
+ * Special cases:
1429
+ * - Returns false if no pending signal exists
1430
+ * - Returns true if trailing stop is already in profit zone (breakeven already achieved)
1431
+ * - Returns false if threshold not reached yet
1432
+ *
1433
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
1434
+ * @param currentPrice - Current market price to check against threshold
1435
+ * @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
1652
1436
  *
1653
1437
  * @example
1654
1438
  * ```typescript
1655
- * const eventDate = new Date(event.timestamp);
1656
- * console.log(`Signal rejected at: ${eventDate.toISOString()}`);
1439
+ * // Check if breakeven is available for LONG position (entry=100, threshold=0.4%)
1440
+ * const canBreakeven = await strategy.getBreakeven("BTCUSDT", 100.5);
1441
+ * // Returns true (price >= 100.4)
1442
+ *
1443
+ * if (canBreakeven) {
1444
+ * await strategy.breakeven("BTCUSDT", 100.5, false);
1445
+ * }
1657
1446
  * ```
1658
1447
  */
1659
- timestamp: number;
1448
+ getBreakeven: (symbol: string, currentPrice: number) => Promise<boolean>;
1660
1449
  /**
1661
- * Whether this event is from backtest mode (true) or live mode (false).
1662
- * Used to separate backtest and live risk rejection tracking.
1450
+ * Checks if the strategy has been stopped.
1451
+ *
1452
+ * Returns the stopped state indicating whether the strategy should
1453
+ * cease processing new ticks or signals.
1454
+ *
1455
+ * @param symbol - Trading pair symbol
1456
+ * @returns Promise resolving to true if strategy is stopped, false otherwise
1663
1457
  */
1664
- backtest: boolean;
1665
- }
1666
-
1667
- /**
1668
- * Constructor type for action handlers with strategy context.
1669
- *
1670
- * @param strategyName - Strategy identifier (e.g., "rsi_divergence", "macd_cross")
1671
- * @param frameName - Timeframe identifier (e.g., "1m", "5m", "1h")
1672
- * @param backtest - True for backtest mode, false for live trading
1673
- * @returns Partial implementation of IAction (only required handlers)
1674
- *
1675
- * @example
1676
- * ```typescript
1677
- * class TelegramNotifier implements Partial<IAction> {
1678
- * constructor(
1679
- * private strategyName: StrategyName,
1680
- * private frameName: FrameName,
1681
- * private backtest: boolean
1682
- * ) {}
1683
- *
1684
- * signal(event: IStrategyTickResult): void {
1685
- * if (!this.backtest && event.state === 'opened') {
1686
- * telegram.send(`[${this.strategyName}/${this.frameName}] New signal`);
1687
- * }
1688
- * }
1689
- * }
1690
- *
1691
- * const actionCtors: TActionCtor[] = [TelegramNotifier, ReduxLogger];
1692
- * ```
1693
- */
1694
- type TActionCtor = new (strategyName: StrategyName, frameName: FrameName, actionName: ActionName) => Partial<IPublicAction>;
1695
- /**
1696
- * Action parameters passed to ClientAction constructor.
1697
- * Combines schema with runtime dependencies and execution context.
1698
- *
1699
- * Extended from IActionSchema with:
1700
- * - Logger instance for debugging and monitoring
1701
- * - Strategy context (strategyName, frameName)
1702
- * - Runtime environment flags
1703
- *
1704
- * @example
1705
- * ```typescript
1706
- * const params: IActionParams = {
1707
- * actionName: "telegram-notifier",
1708
- * handler: TelegramNotifier,
1709
- * callbacks: { onInit, onDispose, onSignal },
1710
- * logger: loggerService,
1711
- * strategyName: "rsi_divergence",
1712
- * frameName: "1h"
1713
- * };
1714
- *
1715
- * const actionClient = new ClientAction(params);
1716
- * ```
1717
- */
1718
- interface IActionParams extends IActionSchema {
1719
- /** Logger service for debugging and monitoring action execution */
1720
- logger: ILogger;
1721
- /** Strategy identifier this action is attached to */
1722
- strategyName: StrategyName;
1723
- /** Exchange name (e.g., "binance") */
1724
- exchangeName: ExchangeName;
1725
- /** Timeframe identifier this action is attached to */
1726
- frameName: FrameName;
1727
- /** Whether running in backtest mode */
1728
- backtest: boolean;
1729
- }
1730
- /**
1731
- * Lifecycle and event callbacks for action handlers.
1732
- *
1733
- * Provides hooks for initialization, disposal, and event handling.
1734
- * All callbacks are optional and support both sync and async execution.
1735
- *
1736
- * Use cases:
1737
- * - Resource initialization (database connections, file handles)
1738
- * - Resource cleanup (close connections, flush buffers)
1739
- * - Event logging and monitoring
1740
- * - State persistence
1741
- *
1742
- * @example
1743
- * ```typescript
1744
- * const callbacks: IActionCallbacks = {
1745
- * onInit: async (strategyName, frameName, backtest) => {
1746
- * console.log(`[${strategyName}/${frameName}] Action initialized (backtest=${backtest})`);
1747
- * await db.connect();
1748
- * },
1749
- * onSignal: (event, strategyName, frameName, backtest) => {
1750
- * if (event.action === 'opened') {
1751
- * console.log(`New signal opened: ${event.signal.id}`);
1752
- * }
1753
- * },
1754
- * onDispose: async (strategyName, frameName, backtest) => {
1755
- * await db.disconnect();
1756
- * console.log(`[${strategyName}/${frameName}] Action disposed`);
1757
- * }
1758
- * };
1759
- * ```
1760
- */
1761
- interface IActionCallbacks {
1458
+ getStopped: (symbol: string) => Promise<boolean>;
1762
1459
  /**
1763
- * Called when action handler is initialized.
1460
+ * Fast backtest using historical candles.
1461
+ * Iterates through candles, calculates VWAP, checks TP/SL on each candle.
1764
1462
  *
1765
- * Use for:
1766
- * - Opening database connections
1767
- * - Initializing external services
1768
- * - Loading persisted state
1769
- * - Setting up subscriptions
1463
+ * For scheduled signals: first monitors activation/cancellation,
1464
+ * then if activated continues with TP/SL monitoring.
1770
1465
  *
1771
- * @param actionName - Action identifier
1772
- * @param strategyName - Strategy identifier
1773
- * @param frameName - Timeframe identifier
1774
- * @param backtest - True for backtest mode, false for live trading
1466
+ * @param symbol - Trading pair symbol
1467
+ * @param strategyName - Name of the strategy
1468
+ * @param candles - Array of historical candle data
1469
+ * @returns Promise resolving to closed result (always completes signal)
1775
1470
  */
1776
- onInit(actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
1471
+ backtest: (symbol: string, strategyName: StrategyName, candles: ICandleData[]) => Promise<IStrategyBacktestResult>;
1777
1472
  /**
1778
- * Called when action handler is disposed.
1779
- *
1780
- * Use for:
1781
- * - Closing database connections
1782
- * - Flushing buffers
1783
- * - Saving state to disk
1784
- * - Unsubscribing from observables
1473
+ * Stops the strategy from generating new signals.
1785
1474
  *
1786
- * @param actionName - Action identifier
1787
- * @param strategyName - Strategy identifier
1788
- * @param frameName - Timeframe identifier
1789
- * @param backtest - True for backtest mode, false for live trading
1790
- */
1791
- onDispose(actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
1792
- /**
1793
- * Called on signal events from all modes (live + backtest).
1475
+ * Sets internal flag to prevent getSignal from being called on subsequent ticks.
1476
+ * Does NOT force-close active pending signals - they continue monitoring until natural closure (TP/SL/time_expired).
1794
1477
  *
1795
- * Triggered by: StrategyConnectionService via signalEmitter
1796
- * Frequency: Every tick/candle when strategy is evaluated
1478
+ * Use case: Graceful shutdown in live trading mode without abandoning open positions.
1797
1479
  *
1798
- * @param event - Signal state result (idle, scheduled, opened, active, closed, cancelled)
1799
- * @param actionName - Action identifier
1800
- * @param strategyName - Strategy identifier
1801
- * @param frameName - Timeframe identifier
1802
- * @param backtest - True for backtest mode, false for live trading
1803
- */
1804
- onSignal(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
1805
- /**
1806
- * Called on signal events from live trading only.
1480
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
1481
+ * @returns Promise that resolves immediately when stop flag is set
1807
1482
  *
1808
- * Triggered by: StrategyConnectionService via signalLiveEmitter
1809
- * Frequency: Every tick in live mode
1483
+ * @example
1484
+ * ```typescript
1485
+ * // Graceful shutdown in Live.background() cancellation
1486
+ * const cancel = await Live.background("BTCUSDT", { ... });
1810
1487
  *
1811
- * @param event - Signal state result from live trading
1812
- * @param actionName - Action identifier
1813
- * @param strategyName - Strategy identifier
1814
- * @param frameName - Timeframe identifier
1815
- * @param backtest - Always false (live mode only)
1488
+ * // Later: stop new signals, let existing ones close naturally
1489
+ * await cancel();
1490
+ * ```
1816
1491
  */
1817
- onSignalLive(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
1492
+ stop: (symbol: string, backtest: boolean) => Promise<void>;
1818
1493
  /**
1819
- * Called on signal events from backtest only.
1494
+ * Cancels the scheduled signal without stopping the strategy.
1820
1495
  *
1821
- * Triggered by: StrategyConnectionService via signalBacktestEmitter
1822
- * Frequency: Every candle in backtest mode
1496
+ * Clears the scheduled signal (waiting for priceOpen activation).
1497
+ * Does NOT affect active pending signals or strategy operation.
1498
+ * Does NOT set stop flag - strategy can continue generating new signals.
1823
1499
  *
1824
- * @param event - Signal state result from backtest
1825
- * @param actionName - Action identifier
1826
- * @param strategyName - Strategy identifier
1827
- * @param frameName - Timeframe identifier
1828
- * @param backtest - Always true (backtest mode only)
1829
- */
1830
- onSignalBacktest(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
1831
- /**
1832
- * Called when breakeven is triggered (stop-loss moved to entry price).
1500
+ * Use case: Cancel a scheduled entry that is no longer desired without stopping the entire strategy.
1833
1501
  *
1834
- * Triggered by: BreakevenConnectionService via breakevenSubject
1835
- * Frequency: Once per signal when breakeven threshold is reached
1502
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
1503
+ * @param cancelId - Optional cancellation ID
1504
+ * @returns Promise that resolves when scheduled signal is cleared
1836
1505
  *
1837
- * @param event - Breakeven milestone data
1838
- * @param actionName - Action identifier
1839
- * @param strategyName - Strategy identifier
1840
- * @param frameName - Timeframe identifier
1841
- * @param backtest - True for backtest mode, false for live trading
1506
+ * @example
1507
+ * ```typescript
1508
+ * // Cancel scheduled signal without stopping strategy
1509
+ * await strategy.cancel("BTCUSDT");
1510
+ * // Strategy continues, can generate new signals
1511
+ * ```
1842
1512
  */
1843
- onBreakeven(event: BreakevenContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
1513
+ cancel: (symbol: string, backtest: boolean, cancelId?: string) => Promise<void>;
1844
1514
  /**
1845
- * Called when partial profit level is reached (10%, 20%, 30%, etc).
1515
+ * Executes partial close at profit level (moving toward TP).
1846
1516
  *
1847
- * Triggered by: PartialConnectionService via partialProfitSubject
1848
- * Frequency: Once per profit level per signal (deduplicated)
1517
+ * Closes specified percentage of position at current price.
1518
+ * Updates _tpClosed, _totalClosed, and _partialHistory state.
1519
+ * Persists updated signal state for crash recovery.
1849
1520
  *
1850
- * @param event - Profit milestone data with level and price
1851
- * @param actionName - Action identifier
1852
- * @param strategyName - Strategy identifier
1853
- * @param frameName - Timeframe identifier
1854
- * @param backtest - True for backtest mode, false for live trading
1855
- */
1856
- onPartialProfit(event: PartialProfitContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
1857
- /**
1858
- * Called when partial loss level is reached (-10%, -20%, -30%, etc).
1521
+ * Validations:
1522
+ * - Throws if no pending signal exists
1523
+ * - Throws if called on scheduled signal (not yet activated)
1524
+ * - Throws if percentToClose <= 0 or > 100
1525
+ * - Returns false if _totalClosed + percentToClose > 100 (prevents over-closing)
1859
1526
  *
1860
- * Triggered by: PartialConnectionService via partialLossSubject
1861
- * Frequency: Once per loss level per signal (deduplicated)
1527
+ * Use case: User-controlled partial close triggered from onPartialProfit callback.
1862
1528
  *
1863
- * @param event - Loss milestone data with level and price
1864
- * @param actionName - Action identifier
1865
- * @param strategyName - Strategy identifier
1866
- * @param frameName - Timeframe identifier
1867
- * @param backtest - True for backtest mode, false for live trading
1529
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
1530
+ * @param percentToClose - Absolute percentage of position to close (0-100)
1531
+ * @param currentPrice - Current market price for partial close
1532
+ * @param backtest - Whether running in backtest mode
1533
+ * @returns Promise<boolean> - true if partial close executed, false if skipped
1534
+ *
1535
+ * @example
1536
+ * ```typescript
1537
+ * callbacks: {
1538
+ * onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
1539
+ * if (percentTp >= 50) {
1540
+ * const success = await strategy.partialProfit(symbol, 25, currentPrice, backtest);
1541
+ * if (success) {
1542
+ * console.log('Partial profit executed');
1543
+ * }
1544
+ * }
1545
+ * }
1546
+ * }
1547
+ * ```
1868
1548
  */
1869
- onPartialLoss(event: PartialLossContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
1549
+ partialProfit: (symbol: string, percentToClose: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
1870
1550
  /**
1871
- * Called during scheduled signal monitoring (every minute while waiting for activation).
1551
+ * Executes partial close at loss level (moving toward SL).
1872
1552
  *
1873
- * Triggered by: StrategyConnectionService via pingSubject
1874
- * Frequency: Every minute while scheduled signal is waiting
1875
- *
1876
- * @param event - Scheduled signal monitoring data
1877
- * @param actionName - Action identifier
1878
- * @param strategyName - Strategy identifier
1879
- * @param frameName - Timeframe identifier
1880
- * @param backtest - True for backtest mode, false for live trading
1881
- */
1882
- onPing(event: PingContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
1883
- /**
1884
- * Called when signal is rejected by risk management.
1885
- *
1886
- * Triggered by: RiskConnectionService via riskSubject
1887
- * Frequency: Only when signal fails risk validation (not emitted for allowed signals)
1888
- *
1889
- * @param event - Risk rejection data with reason and context
1890
- * @param actionName - Action identifier
1891
- * @param strategyName - Strategy identifier
1892
- * @param frameName - Timeframe identifier
1893
- * @param backtest - True for backtest mode, false for live trading
1894
- */
1895
- onRiskRejection(event: RiskContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
1896
- }
1897
- /**
1898
- * Action schema registered via addAction().
1899
- * Defines event handler implementation and lifecycle callbacks for state management integration.
1900
- *
1901
- * Actions provide a way to attach custom event handlers to strategies for:
1902
- * - State management (Redux, Zustand, MobX)
1903
- * - Event logging and monitoring
1904
- * - Real-time notifications (Telegram, Discord, email)
1905
- * - Analytics and metrics collection
1906
- * - Custom business logic triggers
1907
- *
1908
- * Each action instance is created per strategy-frame pair and receives all events
1909
- * emitted during strategy execution. Multiple actions can be attached to a single strategy.
1910
- *
1911
- * @example
1912
- * ```typescript
1913
- * import { addAction } from "backtest-kit";
1914
- *
1915
- * // Define action handler class
1916
- * class TelegramNotifier implements Partial<IAction> {
1917
- * constructor(
1918
- * private strategyName: StrategyName,
1919
- * private frameName: FrameName,
1920
- * private backtest: boolean
1921
- * ) {}
1922
- *
1923
- * signal(event: IStrategyTickResult): void {
1924
- * if (!this.backtest && event.action === 'opened') {
1925
- * telegram.send(`[${this.strategyName}/${this.frameName}] New signal`);
1926
- * }
1927
- * }
1928
- *
1929
- * dispose(): void {
1930
- * telegram.close();
1931
- * }
1932
- * }
1933
- *
1934
- * // Register action schema
1935
- * addAction({
1936
- * actionName: "telegram-notifier",
1937
- * handler: TelegramNotifier,
1938
- * callbacks: {
1939
- * onInit: async (strategyName, frameName, backtest) => {
1940
- * console.log(`Telegram notifier initialized for ${strategyName}/${frameName}`);
1941
- * },
1942
- * onSignal: (event, strategyName, frameName, backtest) => {
1943
- * console.log(`Signal event: ${event.action}`);
1944
- * }
1945
- * }
1946
- * });
1947
- * ```
1948
- */
1949
- interface IActionSchema {
1950
- /** Unique action identifier for registration */
1951
- actionName: ActionName;
1952
- /** Action handler constructor (instantiated per strategy-frame pair) */
1953
- handler: TActionCtor | Partial<IPublicAction>;
1954
- /** Optional lifecycle and event callbacks */
1955
- callbacks?: Partial<IActionCallbacks>;
1956
- }
1957
- /**
1958
- * Public action interface for custom action handler implementations.
1959
- *
1960
- * Extends IAction with an initialization lifecycle method.
1961
- * Action handlers implement this interface to receive strategy events and perform custom logic.
1962
- *
1963
- * Lifecycle:
1964
- * 1. Constructor called with (strategyName, frameName, actionName)
1965
- * 2. init() called once for async initialization (setup connections, load resources)
1966
- * 3. Event methods called as strategy executes (signal, breakeven, partialProfit, etc.)
1967
- * 4. dispose() called once for cleanup (close connections, flush buffers)
1968
- *
1969
- * Key features:
1970
- * - init() for async initialization (database connections, API clients, file handles)
1971
- * - All IAction methods available for event handling
1972
- * - dispose() guaranteed to run exactly once via singleshot pattern
1973
- *
1974
- * Common use cases:
1975
- * - State management: Redux/Zustand store integration
1976
- * - Notifications: Telegram/Discord/Email alerts
1977
- * - Logging: Custom event tracking and monitoring
1978
- * - Analytics: Metrics collection and reporting
1979
- * - External systems: Database writes, API calls, file operations
1980
- *
1981
- * @example
1982
- * ```typescript
1983
- * class TelegramNotifier implements Partial<IPublicAction> {
1984
- * private bot: TelegramBot | null = null;
1985
- *
1986
- * constructor(
1987
- * private strategyName: string,
1988
- * private frameName: string,
1989
- * private actionName: string
1990
- * ) {}
1991
- *
1992
- * // Called once during initialization
1993
- * async init() {
1994
- * this.bot = new TelegramBot(process.env.TELEGRAM_TOKEN);
1995
- * await this.bot.connect();
1996
- * }
1997
- *
1998
- * // Called on every signal event
1999
- * async signal(event: IStrategyTickResult) {
2000
- * if (event.action === 'opened') {
2001
- * await this.bot.send(
2002
- * `[${this.strategyName}/${this.frameName}] Signal opened: ${event.signal.side}`
2003
- * );
2004
- * }
2005
- * }
2006
- *
2007
- * // Called once during cleanup
2008
- * async dispose() {
2009
- * await this.bot?.disconnect();
2010
- * this.bot = null;
2011
- * }
2012
- * }
2013
- * ```
2014
- *
2015
- * @see IAction for all available event methods
2016
- * @see TActionCtor for constructor signature requirements
2017
- * @see ClientAction for internal wrapper that manages lifecycle
2018
- */
2019
- interface IPublicAction extends IAction {
2020
- /**
2021
- * Async initialization method called once after construction.
1553
+ * Closes specified percentage of position at current price.
1554
+ * Updates _slClosed, _totalClosed, and _partialHistory state.
1555
+ * Persists updated signal state for crash recovery.
2022
1556
  *
2023
- * Use this method to:
2024
- * - Establish database connections
2025
- * - Initialize API clients
2026
- * - Load configuration files
2027
- * - Open file handles or network sockets
2028
- * - Perform any async setup required before handling events
1557
+ * Validations:
1558
+ * - Throws if no pending signal exists
1559
+ * - Throws if called on scheduled signal (not yet activated)
1560
+ * - Throws if percentToClose <= 0 or > 100
1561
+ * - Returns false if _totalClosed + percentToClose > 100 (prevents over-closing)
2029
1562
  *
2030
- * Guaranteed to:
2031
- * - Run exactly once per action handler instance
2032
- * - Complete before any event methods are called
2033
- * - Run after constructor but before first event
1563
+ * Use case: User-controlled partial close triggered from onPartialLoss callback.
2034
1564
  *
2035
- * @returns Promise that resolves when initialization is complete
2036
- * @throws Error if initialization fails (will prevent strategy execution)
1565
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
1566
+ * @param percentToClose - Absolute percentage of position to close (0-100)
1567
+ * @param currentPrice - Current market price for partial close
1568
+ * @param backtest - Whether running in backtest mode
1569
+ * @returns Promise<boolean> - true if partial close executed, false if skipped
2037
1570
  *
2038
1571
  * @example
2039
1572
  * ```typescript
2040
- * async init() {
2041
- * this.db = await connectToDatabase();
2042
- * this.cache = new Redis(process.env.REDIS_URL);
2043
- * await this.cache.connect();
2044
- * console.log('Action initialized');
1573
+ * callbacks: {
1574
+ * onPartialLoss: async (symbol, signal, currentPrice, percentSl, backtest) => {
1575
+ * if (percentSl >= 80) {
1576
+ * const success = await strategy.partialLoss(symbol, 50, currentPrice, backtest);
1577
+ * if (success) {
1578
+ * console.log('Partial loss executed');
1579
+ * }
1580
+ * }
1581
+ * }
2045
1582
  * }
2046
1583
  * ```
2047
1584
  */
2048
- init(): void | Promise<void>;
2049
- }
2050
- /**
2051
- * Action interface for state manager integration.
2052
- *
2053
- * Provides methods to handle all events emitted by connection services.
2054
- * Each method corresponds to a specific event type emitted via .next() calls.
2055
- *
2056
- * Use this interface to implement custom state management logic:
2057
- * - Redux/Zustand action dispatchers
2058
- * - Event logging systems
2059
- * - Real-time monitoring dashboards
2060
- * - Analytics and metrics collection
2061
- *
2062
- * @example
2063
- * ```typescript
2064
- * class ReduxStateManager implements IAction {
2065
- * constructor(private store: Store) {}
2066
- *
2067
- * signal(event: IStrategyTickResult): void {
2068
- * this.store.dispatch({ type: 'SIGNAL', payload: event });
2069
- * }
2070
- *
2071
- * breakeven(event: BreakevenContract): void {
2072
- * this.store.dispatch({ type: 'BREAKEVEN', payload: event });
2073
- * }
2074
- *
2075
- * // ... implement other methods
2076
- * }
2077
- * ```
2078
- */
2079
- interface IAction {
1585
+ partialLoss: (symbol: string, percentToClose: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
2080
1586
  /**
2081
- * Handles signal events from all modes (live + backtest).
2082
- *
2083
- * Emitted by: StrategyConnectionService via signalEmitter
2084
- * Source: StrategyConnectionService.tick() and StrategyConnectionService.backtest()
2085
- * Frequency: Every tick/candle when strategy is evaluated
1587
+ * Adjusts trailing stop-loss by shifting distance between entry and original SL.
2086
1588
  *
2087
- * @param event - Signal state result (idle, scheduled, opened, active, closed, cancelled)
2088
- */
2089
- signal(event: IStrategyTickResult): void | Promise<void>;
2090
- /**
2091
- * Handles signal events from live trading only.
1589
+ * CRITICAL: Always calculates from ORIGINAL SL, not from current trailing SL.
1590
+ * This prevents error accumulation on repeated calls.
1591
+ * Larger percentShift ABSORBS smaller one (updates only towards better protection).
2092
1592
  *
2093
- * Emitted by: StrategyConnectionService via signalLiveEmitter
2094
- * Source: StrategyConnectionService.tick() when backtest=false
2095
- * Frequency: Every tick in live mode
1593
+ * Calculates new SL based on percentage shift of the ORIGINAL distance (entry - originalSL):
1594
+ * - Negative %: tightens stop (moves SL closer to entry, reduces risk)
1595
+ * - Positive %: loosens stop (moves SL away from entry, allows more drawdown)
2096
1596
  *
2097
- * @param event - Signal state result from live trading
2098
- */
2099
- signalLive(event: IStrategyTickResult): void | Promise<void>;
2100
- /**
2101
- * Handles signal events from backtest only.
1597
+ * For LONG position (entry=100, originalSL=90, distance=10%):
1598
+ * - percentShift = -50: newSL = 100 - 10%*(1-0.5) = 95 (5% distance, tighter)
1599
+ * - percentShift = +20: newSL = 100 - 10%*(1+0.2) = 88 (12% distance, looser)
2102
1600
  *
2103
- * Emitted by: StrategyConnectionService via signalBacktestEmitter
2104
- * Source: StrategyConnectionService.backtest() when backtest=true
2105
- * Frequency: Every candle in backtest mode
1601
+ * For SHORT position (entry=100, originalSL=110, distance=10%):
1602
+ * - percentShift = -50: newSL = 100 + 10%*(1-0.5) = 105 (5% distance, tighter)
1603
+ * - percentShift = +20: newSL = 100 + 10%*(1+0.2) = 112 (12% distance, looser)
2106
1604
  *
2107
- * @param event - Signal state result from backtest
2108
- */
2109
- signalBacktest(event: IStrategyTickResult): void | Promise<void>;
2110
- /**
2111
- * Handles breakeven events when stop-loss is moved to entry price.
1605
+ * Absorption behavior:
1606
+ * - First call: sets trailing SL unconditionally
1607
+ * - Subsequent calls: updates only if new SL is BETTER (protects more profit)
1608
+ * - For LONG: only accepts HIGHER SL (never moves down, closer to entry wins)
1609
+ * - For SHORT: only accepts LOWER SL (never moves up, closer to entry wins)
1610
+ * - Stores in _trailingPriceStopLoss, original priceStopLoss always preserved
2112
1611
  *
2113
- * Emitted by: BreakevenConnectionService via breakevenSubject
2114
- * Source: COMMIT_BREAKEVEN_FN callback in BreakevenConnectionService
2115
- * Frequency: Once per signal when breakeven threshold is reached
1612
+ * Validations:
1613
+ * - Throws if no pending signal exists
1614
+ * - Throws if percentShift < -100 or > 100
1615
+ * - Throws if percentShift === 0
1616
+ * - Skips if new SL would cross entry price
1617
+ * - Skips if currentPrice already crossed new SL level (price intrusion protection)
2116
1618
  *
2117
- * @param event - Breakeven milestone data
2118
- */
2119
- breakeven(event: BreakevenContract): void | Promise<void>;
2120
- /**
2121
- * Handles partial profit level events (10%, 20%, 30%, etc).
1619
+ * Use case: User-controlled trailing stop triggered from onPartialProfit callback.
2122
1620
  *
2123
- * Emitted by: PartialConnectionService via partialProfitSubject
2124
- * Source: COMMIT_PROFIT_FN callback in PartialConnectionService
2125
- * Frequency: Once per profit level per signal (deduplicated)
1621
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
1622
+ * @param percentShift - Percentage shift of ORIGINAL SL distance [-100, 100], excluding 0
1623
+ * @param currentPrice - Current market price to check for intrusion
1624
+ * @param backtest - Whether running in backtest mode
1625
+ * @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected
2126
1626
  *
2127
- * @param event - Profit milestone data with level and price
2128
- */
2129
- partialProfit(event: PartialProfitContract): void | Promise<void>;
2130
- /**
2131
- * Handles partial loss level events (-10%, -20%, -30%, etc).
1627
+ * @example
1628
+ * ```typescript
1629
+ * callbacks: {
1630
+ * onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
1631
+ * if (percentTp >= 50) {
1632
+ * // LONG: entry=100, originalSL=90, distance=10%
2132
1633
  *
2133
- * Emitted by: PartialConnectionService via partialLossSubject
2134
- * Source: COMMIT_LOSS_FN callback in PartialConnectionService
2135
- * Frequency: Once per loss level per signal (deduplicated)
1634
+ * // First call: tighten by 5%
1635
+ * const success1 = await strategy.trailingStop(symbol, -5, currentPrice, backtest);
1636
+ * // success1 = true, newDistance = 10% - 5% = 5%, newSL = 95
2136
1637
  *
2137
- * @param event - Loss milestone data with level and price
1638
+ * // Second call: try weaker protection
1639
+ * const success2 = await strategy.trailingStop(symbol, -3, currentPrice, backtest);
1640
+ * // success2 = false (SKIPPED: newSL=97 < 95, worse protection, larger % absorbs smaller)
1641
+ *
1642
+ * // Third call: stronger protection
1643
+ * const success3 = await strategy.trailingStop(symbol, -7, currentPrice, backtest);
1644
+ * // success3 = true (ACCEPTED: newDistance = 3%, newSL = 97 > 95, better protection)
1645
+ * }
1646
+ * }
1647
+ * }
1648
+ * ```
2138
1649
  */
2139
- partialLoss(event: PartialLossContract): void | Promise<void>;
1650
+ trailingStop: (symbol: string, percentShift: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
2140
1651
  /**
2141
- * Handles ping events during scheduled signal monitoring.
1652
+ * Adjusts the trailing take-profit distance for an active pending signal.
2142
1653
  *
2143
- * Emitted by: StrategyConnectionService via pingSubject
2144
- * Source: COMMIT_PING_FN callback in StrategyConnectionService
2145
- * Frequency: Every minute while scheduled signal is waiting for activation
1654
+ * CRITICAL: Always calculates from ORIGINAL TP, not from current trailing TP.
1655
+ * This prevents error accumulation on repeated calls.
1656
+ * Larger percentShift ABSORBS smaller one (updates only towards more conservative TP).
2146
1657
  *
2147
- * @param event - Scheduled signal monitoring data
1658
+ * Updates the take-profit distance by a percentage adjustment relative to the ORIGINAL TP distance.
1659
+ * Negative percentShift brings TP closer to entry (more conservative).
1660
+ * Positive percentShift moves TP further from entry (more aggressive).
1661
+ *
1662
+ * Absorption behavior:
1663
+ * - First call: sets trailing TP unconditionally
1664
+ * - Subsequent calls: updates only if new TP is MORE CONSERVATIVE (closer to entry)
1665
+ * - For LONG: only accepts LOWER TP (never moves up, closer to entry wins)
1666
+ * - For SHORT: only accepts HIGHER TP (never moves down, closer to entry wins)
1667
+ * - Stores in _trailingPriceTakeProfit, original priceTakeProfit always preserved
1668
+ *
1669
+ * Price intrusion protection: If current price has already crossed the new TP level,
1670
+ * the update is skipped to prevent immediate TP triggering.
1671
+ *
1672
+ * @param symbol - Trading pair symbol
1673
+ * @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
1674
+ * @param currentPrice - Current market price to check for intrusion
1675
+ * @param backtest - Whether running in backtest mode
1676
+ * @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected
1677
+ *
1678
+ * @example
1679
+ * ```typescript
1680
+ * callbacks: {
1681
+ * onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
1682
+ * // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
1683
+ *
1684
+ * // First call: bring TP closer by 3%
1685
+ * const success1 = await strategy.trailingTake(symbol, -3, currentPrice, backtest);
1686
+ * // success1 = true, newDistance = 10% - 3% = 7%, newTP = 107
1687
+ *
1688
+ * // Second call: try to move TP further (less conservative)
1689
+ * const success2 = await strategy.trailingTake(symbol, 2, currentPrice, backtest);
1690
+ * // success2 = false (SKIPPED: newTP=112 > 107, less conservative, larger % absorbs smaller)
1691
+ *
1692
+ * // Third call: even more conservative
1693
+ * const success3 = await strategy.trailingTake(symbol, -5, currentPrice, backtest);
1694
+ * // success3 = true (ACCEPTED: newDistance = 5%, newTP = 105 < 107, more conservative)
1695
+ * }
1696
+ * }
1697
+ * ```
2148
1698
  */
2149
- ping(event: PingContract): void | Promise<void>;
1699
+ trailingTake: (symbol: string, percentShift: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
2150
1700
  /**
2151
- * Handles risk rejection events when signals fail risk validation.
1701
+ * Moves stop-loss to breakeven (entry price) when price reaches threshold.
2152
1702
  *
2153
- * Emitted by: RiskConnectionService via riskSubject
2154
- * Source: COMMIT_REJECTION_FN callback in RiskConnectionService
2155
- * Frequency: Only when signal is rejected (not emitted for allowed signals)
1703
+ * Moves SL to entry price (zero-risk position) when current price has moved
1704
+ * far enough in profit direction to cover transaction costs (slippage + fees).
1705
+ * Threshold is calculated as: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2
2156
1706
  *
2157
- * @param event - Risk rejection data with reason and context
1707
+ * Behavior:
1708
+ * - Returns true if SL was moved to breakeven
1709
+ * - Returns false if conditions not met (threshold not reached or already at breakeven)
1710
+ * - Uses _trailingPriceStopLoss to store breakeven SL (preserves original priceStopLoss)
1711
+ * - Only moves SL once per position (idempotent - safe to call multiple times)
1712
+ *
1713
+ * For LONG position (entry=100, slippage=0.1%, fee=0.1%):
1714
+ * - Threshold: (0.1 + 0.1) * 2 = 0.4%
1715
+ * - Breakeven available when price >= 100.4 (entry + 0.4%)
1716
+ * - Moves SL from original (e.g. 95) to 100 (breakeven)
1717
+ * - Returns true on first successful move, false on subsequent calls
1718
+ *
1719
+ * For SHORT position (entry=100, slippage=0.1%, fee=0.1%):
1720
+ * - Threshold: (0.1 + 0.1) * 2 = 0.4%
1721
+ * - Breakeven available when price <= 99.6 (entry - 0.4%)
1722
+ * - Moves SL from original (e.g. 105) to 100 (breakeven)
1723
+ * - Returns true on first successful move, false on subsequent calls
1724
+ *
1725
+ * Validations:
1726
+ * - Throws if no pending signal exists
1727
+ * - Throws if currentPrice is not a positive finite number
1728
+ *
1729
+ * Use case: User-controlled breakeven protection triggered from onPartialProfit callback.
1730
+ *
1731
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
1732
+ * @param currentPrice - Current market price to check threshold
1733
+ * @param backtest - Whether running in backtest mode
1734
+ * @returns Promise<boolean> - true if breakeven was set, false if conditions not met
1735
+ *
1736
+ * @example
1737
+ * ```typescript
1738
+ * callbacks: {
1739
+ * onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
1740
+ * // Try to move SL to breakeven when threshold reached
1741
+ * const movedToBreakeven = await strategy.breakeven(symbol, currentPrice, backtest);
1742
+ * if (movedToBreakeven) {
1743
+ * console.log(`Position moved to breakeven at ${currentPrice}`);
1744
+ * }
1745
+ * }
1746
+ * }
1747
+ * ```
2158
1748
  */
2159
- riskRejection(event: RiskContract): void | Promise<void>;
1749
+ breakeven: (symbol: string, currentPrice: number, backtest: boolean) => Promise<boolean>;
2160
1750
  /**
2161
- * Cleans up resources and subscriptions when action handler is no longer needed.
1751
+ * Disposes the strategy instance and cleans up resources.
2162
1752
  *
2163
- * Called by: Connection services during shutdown
2164
- * Use for: Unsubscribing from observables, closing connections, flushing buffers
1753
+ * Called when the strategy is being removed from cache or shut down.
1754
+ * Invokes the onDispose callback to notify external systems.
1755
+ *
1756
+ * @returns Promise that resolves when disposal is complete
2165
1757
  */
2166
- dispose(): void | Promise<void>;
1758
+ dispose: () => Promise<void>;
2167
1759
  }
2168
1760
  /**
2169
- * Unique action identifier.
1761
+ * Unique strategy identifier.
2170
1762
  */
2171
- type ActionName = string;
1763
+ type StrategyName = string;
2172
1764
 
2173
1765
  /**
2174
- * Signal generation interval for throttling.
2175
- * Enforces minimum time between getSignal calls.
2176
- */
2177
- type SignalInterval = "1m" | "3m" | "5m" | "15m" | "30m" | "1h";
2178
- /**
2179
- * Signal data transfer object returned by getSignal.
2180
- * Will be validated and augmented with auto-generated id.
2181
- */
2182
- interface ISignalDto {
2183
- /** Optional signal ID (auto-generated if not provided) */
2184
- id?: string;
2185
- /** Trade direction: "long" (buy) or "short" (sell) */
2186
- position: "long" | "short";
2187
- /** Human-readable description of signal reason */
2188
- note?: string;
2189
- /** Entry price for the position */
2190
- priceOpen?: number;
2191
- /** Take profit target price (must be > priceOpen for long, < priceOpen for short) */
2192
- priceTakeProfit: number;
2193
- /** Stop loss exit price (must be < priceOpen for long, > priceOpen for short) */
2194
- priceStopLoss: number;
2195
- /** Expected duration in minutes before time_expired */
2196
- minuteEstimatedTime: number;
2197
- }
2198
- /**
2199
- * Complete signal with auto-generated id.
2200
- * Used throughout the system after validation.
1766
+ * Contract for breakeven events.
1767
+ *
1768
+ * Emitted by breakevenSubject when a signal's stop-loss is moved to breakeven (entry price).
1769
+ * Used for tracking risk reduction milestones and monitoring strategy safety.
1770
+ *
1771
+ * Events are emitted only once per signal (idempotent - protected by ClientBreakeven state).
1772
+ * Breakeven is triggered when price moves far enough in profit direction to cover transaction costs.
1773
+ *
1774
+ * Consumers:
1775
+ * - BreakevenMarkdownService: Accumulates events for report generation
1776
+ * - User callbacks via listenBreakeven() / listenBreakevenOnce()
1777
+ *
1778
+ * @example
1779
+ * ```typescript
1780
+ * import { listenBreakeven } from "backtest-kit";
1781
+ *
1782
+ * // Listen to all breakeven events
1783
+ * listenBreakeven((event) => {
1784
+ * console.log(`[${event.backtest ? "Backtest" : "Live"}] Signal ${event.data.id} moved to breakeven`);
1785
+ * console.log(`Symbol: ${event.symbol}, Price: ${event.currentPrice}`);
1786
+ * console.log(`Position: ${event.data.position}, Entry: ${event.data.priceOpen}`);
1787
+ * console.log(`Original SL: ${event.data.priceStopLoss}, New SL: ${event.data.priceOpen}`);
1788
+ * });
1789
+ *
1790
+ * // Wait for specific signal to reach breakeven
1791
+ * listenBreakevenOnce(
1792
+ * (event) => event.data.id === "target-signal-id",
1793
+ * (event) => console.log("Signal reached breakeven:", event.data.id)
1794
+ * );
1795
+ * ```
2201
1796
  */
2202
- interface ISignalRow extends ISignalDto {
2203
- /** Unique signal identifier (UUID v4 auto-generated) */
2204
- id: string;
2205
- /** Entry price for the position */
2206
- priceOpen: number;
2207
- /** Unique exchange identifier for execution */
2208
- exchangeName: ExchangeName;
2209
- /** Unique strategy identifier for execution */
1797
+ interface BreakevenContract {
1798
+ /**
1799
+ * Trading pair symbol (e.g., "BTCUSDT").
1800
+ * Identifies which market this breakeven event belongs to.
1801
+ */
1802
+ symbol: string;
1803
+ /**
1804
+ * Strategy name that generated this signal.
1805
+ * Identifies which strategy execution this breakeven event belongs to.
1806
+ */
2210
1807
  strategyName: StrategyName;
2211
- /** Unique frame identifier for execution (empty string for live mode) */
1808
+ /**
1809
+ * Exchange name where this signal is being executed.
1810
+ * Identifies which exchange this breakeven event belongs to.
1811
+ */
1812
+ exchangeName: ExchangeName;
1813
+ /**
1814
+ * Frame name where this signal is being executed.
1815
+ * Identifies which frame this breakeven event belongs to (empty string for live mode).
1816
+ */
2212
1817
  frameName: FrameName;
2213
- /** Signal creation timestamp in milliseconds (when signal was first created/scheduled) */
2214
- scheduledAt: number;
2215
- /** Pending timestamp in milliseconds (when position became pending/active at priceOpen) */
2216
- pendingAt: number;
2217
- /** Trading pair symbol (e.g., "BTCUSDT") */
2218
- symbol: string;
2219
- /** Internal runtime marker for scheduled signals */
2220
- _isScheduled: boolean;
2221
1818
  /**
2222
- * History of partial closes for PNL calculation.
2223
- * Each entry contains type (profit/loss), percent closed, and price.
2224
- * Used to calculate weighted PNL: Σ(percent_i × pnl_i) for each partial + (remaining% × final_pnl)
2225
- *
2226
- * Computed values (derived from this array):
2227
- * - _tpClosed: Sum of all "profit" type partial close percentages
2228
- * - _slClosed: Sum of all "loss" type partial close percentages
2229
- * - _totalClosed: Sum of all partial close percentages (profit + loss)
1819
+ * Complete signal row data with original prices.
1820
+ * Contains all signal information including originalPriceStopLoss, originalPriceTakeProfit, and totalExecuted.
2230
1821
  */
2231
- _partial?: Array<{
2232
- /** Type of partial close: profit (moving toward TP) or loss (moving toward SL) */
2233
- type: "profit" | "loss";
2234
- /** Percentage of position closed (0-100) */
2235
- percent: number;
2236
- /** Price at which this partial was executed */
2237
- price: number;
2238
- }>;
1822
+ data: IPublicSignalRow;
2239
1823
  /**
2240
- * Trailing stop-loss price that overrides priceStopLoss when set.
2241
- * Updated by trailing() method based on position type and percentage distance.
2242
- * - For LONG: moves upward as price moves toward TP (never moves down)
2243
- * - For SHORT: moves downward as price moves toward TP (never moves up)
2244
- * When _trailingPriceStopLoss is set, it replaces priceStopLoss for TP/SL checks.
2245
- * Original priceStopLoss is preserved in persistence but ignored during execution.
1824
+ * Current market price at which breakeven was triggered.
1825
+ * Used to verify threshold calculation.
2246
1826
  */
2247
- _trailingPriceStopLoss?: number;
1827
+ currentPrice: number;
2248
1828
  /**
2249
- * Trailing take-profit price that overrides priceTakeProfit when set.
2250
- * Created and managed by trailingTake() method for dynamic TP adjustment.
2251
- * Allows moving TP further from or closer to current price based on strategy.
2252
- * Updated by trailingTake() method based on position type and percentage distance.
2253
- * - For LONG: can move upward (further) or downward (closer) from entry
2254
- * - For SHORT: can move downward (further) or upward (closer) from entry
2255
- * When _trailingPriceTakeProfit is set, it replaces priceTakeProfit for TP/SL checks.
2256
- * Original priceTakeProfit is preserved in persistence but ignored during execution.
1829
+ * Execution mode flag.
1830
+ * - true: Event from backtest execution (historical candle data)
1831
+ * - false: Event from live trading (real-time tick)
2257
1832
  */
2258
- _trailingPriceTakeProfit?: number;
2259
- }
2260
- /**
2261
- * Scheduled signal row for delayed entry at specific price.
2262
- * Inherits from ISignalRow - represents a signal waiting for price to reach priceOpen.
2263
- * Once price reaches priceOpen, will be converted to regular _pendingSignal.
2264
- * Note: pendingAt will be set to scheduledAt until activation, then updated to actual pending time.
2265
- */
2266
- interface IScheduledSignalRow extends ISignalRow {
2267
- /** Entry price for the position */
2268
- priceOpen: number;
1833
+ backtest: boolean;
1834
+ /**
1835
+ * Event timestamp in milliseconds since Unix epoch.
1836
+ *
1837
+ * Timing semantics:
1838
+ * - Live mode: when.getTime() at the moment breakeven was set
1839
+ * - Backtest mode: candle.timestamp of the candle that triggered breakeven
1840
+ *
1841
+ * @example
1842
+ * ```typescript
1843
+ * const eventDate = new Date(event.timestamp);
1844
+ * console.log(`Breakeven set at: ${eventDate.toISOString()}`);
1845
+ * ```
1846
+ */
1847
+ timestamp: number;
2269
1848
  }
1849
+
2270
1850
  /**
2271
- * Public signal row with original stop-loss and take-profit prices.
2272
- * Extends ISignalRow to include originalPriceStopLoss and originalPriceTakeProfit for external visibility.
2273
- * Used in public APIs to show user the original SL/TP even if trailing SL/TP are active.
2274
- * This allows users to see both the current effective SL/TP and the original values set at signal creation.
2275
- * The original prices remain unchanged even if _trailingPriceStopLoss or _trailingPriceTakeProfit modify the effective values.
2276
- * Useful for transparency in reporting and user interfaces.
2277
- * Note: originalPriceStopLoss/originalPriceTakeProfit are identical to priceStopLoss/priceTakeProfit at signal creation time.
1851
+ * Contract for partial profit level events.
1852
+ *
1853
+ * Emitted by partialProfitSubject when a signal reaches a profit level milestone (10%, 20%, 30%, etc).
1854
+ * Used for tracking partial take-profit execution and monitoring strategy performance.
1855
+ *
1856
+ * Events are emitted only once per level per signal (Set-based deduplication in ClientPartial).
1857
+ * Multiple levels can be emitted in a single tick if price jumps significantly.
1858
+ *
1859
+ * Consumers:
1860
+ * - PartialMarkdownService: Accumulates events for report generation
1861
+ * - User callbacks via listenPartialProfit() / listenPartialProfitOnce()
1862
+ *
1863
+ * @example
1864
+ * ```typescript
1865
+ * import { listenPartialProfit } from "backtest-kit";
1866
+ *
1867
+ * // Listen to all partial profit events
1868
+ * listenPartialProfit((event) => {
1869
+ * console.log(`[${event.backtest ? "Backtest" : "Live"}] Signal ${event.data.id} reached ${event.level}% profit`);
1870
+ * console.log(`Symbol: ${event.symbol}, Price: ${event.currentPrice}`);
1871
+ * console.log(`Position: ${event.data.position}, Entry: ${event.data.priceOpen}`);
1872
+ * });
1873
+ *
1874
+ * // Wait for first 50% profit level
1875
+ * listenPartialProfitOnce(
1876
+ * (event) => event.level === 50,
1877
+ * (event) => console.log("50% profit reached:", event.data.id)
1878
+ * );
1879
+ * ```
2278
1880
  */
2279
- interface IPublicSignalRow extends ISignalRow {
1881
+ interface PartialProfitContract {
2280
1882
  /**
2281
- * Original stop-loss price set at signal creation.
2282
- * Remains unchanged even if trailing stop-loss modifies effective SL.
2283
- * Used for user visibility of initial SL parameters.
1883
+ * Trading pair symbol (e.g., "BTCUSDT").
1884
+ * Identifies which market this profit event belongs to.
2284
1885
  */
2285
- originalPriceStopLoss: number;
1886
+ symbol: string;
2286
1887
  /**
2287
- * Original take-profit price set at signal creation.
2288
- * Remains unchanged even if trailing take-profit modifies effective TP.
2289
- * Used for user visibility of initial TP parameters.
1888
+ * Strategy name that generated this signal.
1889
+ * Identifies which strategy execution this profit event belongs to.
2290
1890
  */
2291
- originalPriceTakeProfit: number;
1891
+ strategyName: StrategyName;
2292
1892
  /**
2293
- * Total executed percentage from partial closes.
2294
- * Sum of all percent values from _partial array (both profit and loss types).
2295
- * Represents the total portion of the position that has been closed through partial executions.
2296
- * Range: 0-100. Value of 0 means no partial closes, 100 means position fully closed through partials.
1893
+ * Exchange name where this signal is being executed.
1894
+ * Identifies which exchange this profit event belongs to.
2297
1895
  */
2298
- totalExecuted: number;
2299
- }
2300
- /**
2301
- * Risk signal row for internal risk management.
2302
- * Extends ISignalDto to include priceOpen, originalPriceStopLoss and originalPriceTakeProfit.
2303
- * Used in risk validation to access entry price and original SL/TP.
2304
- */
2305
- interface IRiskSignalRow extends IPublicSignalRow {
1896
+ exchangeName: ExchangeName;
2306
1897
  /**
2307
- * Entry price for the position.
1898
+ * Frame name where this signal is being executed.
1899
+ * Identifies which frame this profit event belongs to (empty string for live mode).
2308
1900
  */
2309
- priceOpen: number;
1901
+ frameName: FrameName;
2310
1902
  /**
2311
- * Original stop-loss price set at signal creation.
1903
+ * Complete signal row data with original prices.
1904
+ * Contains all signal information including originalPriceStopLoss, originalPriceTakeProfit, and totalExecuted.
2312
1905
  */
2313
- originalPriceStopLoss: number;
1906
+ data: IPublicSignalRow;
2314
1907
  /**
2315
- * Original take-profit price set at signal creation.
1908
+ * Current market price at which this profit level was reached.
1909
+ * Used to calculate actual profit percentage.
2316
1910
  */
2317
- originalPriceTakeProfit: number;
2318
- }
2319
- /**
2320
- * Scheduled signal row with cancellation ID.
2321
- * Extends IScheduledSignalRow to include optional cancelId for user-initiated cancellations.
2322
- */
2323
- interface IScheduledSignalCancelRow extends IScheduledSignalRow {
2324
- /** Cancellation ID (only for user-initiated cancellations) */
2325
- cancelId?: string;
1911
+ currentPrice: number;
1912
+ /**
1913
+ * Profit level milestone reached (10, 20, 30, 40, 50, 60, 70, 80, 90, or 100).
1914
+ * Represents percentage profit relative to entry price.
1915
+ *
1916
+ * @example
1917
+ * ```typescript
1918
+ * // If entry was $50000 and level is 20:
1919
+ * // currentPrice >= $60000 (20% profit)
1920
+ * ```
1921
+ */
1922
+ level: PartialLevel;
1923
+ /**
1924
+ * Execution mode flag.
1925
+ * - true: Event from backtest execution (historical candle data)
1926
+ * - false: Event from live trading (real-time tick)
1927
+ */
1928
+ backtest: boolean;
1929
+ /**
1930
+ * Event timestamp in milliseconds since Unix epoch.
1931
+ *
1932
+ * Timing semantics:
1933
+ * - Live mode: when.getTime() at the moment profit level was detected
1934
+ * - Backtest mode: candle.timestamp of the candle that triggered the level
1935
+ *
1936
+ * @example
1937
+ * ```typescript
1938
+ * const eventDate = new Date(event.timestamp);
1939
+ * console.log(`Profit reached at: ${eventDate.toISOString()}`);
1940
+ * ```
1941
+ */
1942
+ timestamp: number;
2326
1943
  }
1944
+
2327
1945
  /**
2328
- * Optional lifecycle callbacks for signal events.
2329
- * Called when signals are opened, active, idle, closed, scheduled, or cancelled.
1946
+ * Contract for partial loss level events.
1947
+ *
1948
+ * Emitted by partialLossSubject when a signal reaches a loss level milestone (-10%, -20%, -30%, etc).
1949
+ * Used for tracking partial stop-loss execution and monitoring strategy drawdown.
1950
+ *
1951
+ * Events are emitted only once per level per signal (Set-based deduplication in ClientPartial).
1952
+ * Multiple levels can be emitted in a single tick if price drops significantly.
1953
+ *
1954
+ * Consumers:
1955
+ * - PartialMarkdownService: Accumulates events for report generation
1956
+ * - User callbacks via listenPartialLoss() / listenPartialLossOnce()
1957
+ *
1958
+ * @example
1959
+ * ```typescript
1960
+ * import { listenPartialLoss } from "backtest-kit";
1961
+ *
1962
+ * // Listen to all partial loss events
1963
+ * listenPartialLoss((event) => {
1964
+ * console.log(`[${event.backtest ? "Backtest" : "Live"}] Signal ${event.data.id} reached -${event.level}% loss`);
1965
+ * console.log(`Symbol: ${event.symbol}, Price: ${event.currentPrice}`);
1966
+ * console.log(`Position: ${event.data.position}, Entry: ${event.data.priceOpen}`);
1967
+ *
1968
+ * // Alert on significant loss
1969
+ * if (event.level >= 30 && !event.backtest) {
1970
+ * console.warn("HIGH LOSS ALERT:", event.data.id);
1971
+ * }
1972
+ * });
1973
+ *
1974
+ * // Wait for first 20% loss level
1975
+ * listenPartialLossOnce(
1976
+ * (event) => event.level === 20,
1977
+ * (event) => console.log("20% loss reached:", event.data.id)
1978
+ * );
1979
+ * ```
2330
1980
  */
2331
- interface IStrategyCallbacks {
2332
- /** Called on every tick with the result */
2333
- onTick: (symbol: string, result: IStrategyTickResult, backtest: boolean) => void | Promise<void>;
2334
- /** Called when new signal is opened (after validation) */
2335
- onOpen: (symbol: string, data: IPublicSignalRow, currentPrice: number, backtest: boolean) => void | Promise<void>;
2336
- /** Called when signal is being monitored (active state) */
2337
- onActive: (symbol: string, data: IPublicSignalRow, currentPrice: number, backtest: boolean) => void | Promise<void>;
2338
- /** Called when no active signal exists (idle state) */
2339
- onIdle: (symbol: string, currentPrice: number, backtest: boolean) => void | Promise<void>;
2340
- /** Called when signal is closed with final price */
2341
- onClose: (symbol: string, data: IPublicSignalRow, priceClose: number, backtest: boolean) => void | Promise<void>;
2342
- /** Called when scheduled signal is created (delayed entry) */
2343
- onSchedule: (symbol: string, data: IPublicSignalRow, currentPrice: number, backtest: boolean) => void | Promise<void>;
2344
- /** Called when scheduled signal is cancelled without opening position */
2345
- onCancel: (symbol: string, data: IPublicSignalRow, currentPrice: number, backtest: boolean) => void | Promise<void>;
2346
- /** Called when signal is written to persist storage (for testing) */
2347
- onWrite: (symbol: string, data: IPublicSignalRow | null, backtest: boolean) => void;
2348
- /** Called when signal is in partial profit state (price moved favorably but not reached TP yet) */
2349
- onPartialProfit: (symbol: string, data: IPublicSignalRow, currentPrice: number, revenuePercent: number, backtest: boolean) => void | Promise<void>;
2350
- /** Called when signal is in partial loss state (price moved against position but not hit SL yet) */
2351
- onPartialLoss: (symbol: string, data: IPublicSignalRow, currentPrice: number, lossPercent: number, backtest: boolean) => void | Promise<void>;
2352
- /** Called when signal reaches breakeven (stop-loss moved to entry price to protect capital) */
2353
- onBreakeven: (symbol: string, data: IPublicSignalRow, currentPrice: number, backtest: boolean) => void | Promise<void>;
2354
- /** Called every minute regardless of strategy interval (for custom monitoring like checking if signal should be cancelled) */
2355
- onPing: (symbol: string, data: IPublicSignalRow, when: Date, backtest: boolean) => void | Promise<void>;
2356
- }
2357
- /**
2358
- * Strategy schema registered via addStrategy().
2359
- * Defines signal generation logic and configuration.
2360
- */
2361
- interface IStrategySchema {
2362
- /** Unique strategy identifier for registration */
2363
- strategyName: StrategyName;
2364
- /** Optional developer note for documentation */
2365
- note?: string;
2366
- /** Minimum interval between getSignal calls (throttling) */
2367
- interval: SignalInterval;
1981
+ interface PartialLossContract {
2368
1982
  /**
2369
- * Signal generation function (returns null if no signal, validated DTO if signal).
2370
- * If priceOpen is provided - becomes scheduled signal waiting for price to reach entry point.
2371
- * If priceOpen is omitted - opens immediately at current price.
1983
+ * Trading pair symbol (e.g., "BTCUSDT").
1984
+ * Identifies which market this loss event belongs to.
2372
1985
  */
2373
- getSignal: (symbol: string, when: Date) => Promise<ISignalDto | null>;
2374
- /** Optional lifecycle event callbacks (onOpen, onClose) */
2375
- callbacks?: Partial<IStrategyCallbacks>;
2376
- /** Optional risk profile identifier for risk management */
2377
- riskName?: RiskName;
2378
- /** Optional several risk profile list for risk management (if multiple required) */
2379
- riskList?: RiskName[];
2380
- /** Optional list of action identifiers to attach to this strategy */
2381
- actions?: ActionName[];
2382
- }
2383
- /**
2384
- * Reason why signal was closed.
2385
- * Used in discriminated union for type-safe handling.
2386
- */
2387
- type StrategyCloseReason = "time_expired" | "take_profit" | "stop_loss";
2388
- /**
2389
- * Reason why scheduled signal was cancelled.
2390
- * Used in discriminated union for type-safe handling.
2391
- */
2392
- type StrategyCancelReason = "timeout" | "price_reject" | "user";
2393
- /**
2394
- * Profit and loss calculation result.
2395
- * Includes adjusted prices with fees (0.1%) and slippage (0.1%).
2396
- */
2397
- interface IStrategyPnL {
2398
- /** Profit/loss as percentage (e.g., 1.5 for +1.5%, -2.3 for -2.3%) */
2399
- pnlPercentage: number;
2400
- /** Entry price adjusted with slippage and fees */
2401
- priceOpen: number;
2402
- /** Exit price adjusted with slippage and fees */
2403
- priceClose: number;
2404
- }
2405
- /**
2406
- * Tick result: no active signal, idle state.
2407
- */
2408
- interface IStrategyTickResultIdle {
2409
- /** Discriminator for type-safe union */
2410
- action: "idle";
2411
- /** No signal in idle state */
2412
- signal: null;
2413
- /** Strategy name for tracking idle events */
2414
- strategyName: StrategyName;
2415
- /** Exchange name for tracking idle events */
2416
- exchangeName: ExchangeName;
2417
- /** Time frame name for tracking (e.g., "1m", "5m") */
2418
- frameName: FrameName;
2419
- /** Trading pair symbol (e.g., "BTCUSDT") */
2420
1986
  symbol: string;
2421
- /** Current VWAP price during idle state */
2422
- currentPrice: number;
2423
- /** Whether this event is from backtest mode (true) or live mode (false) */
2424
- backtest: boolean;
2425
- }
2426
- /**
2427
- * Tick result: scheduled signal created, waiting for price to reach entry point.
2428
- * Triggered when getSignal returns signal with priceOpen specified.
2429
- */
2430
- interface IStrategyTickResultScheduled {
2431
- /** Discriminator for type-safe union */
2432
- action: "scheduled";
2433
- /** Scheduled signal waiting for activation */
2434
- signal: IPublicSignalRow;
2435
- /** Strategy name for tracking */
1987
+ /**
1988
+ * Strategy name that generated this signal.
1989
+ * Identifies which strategy execution this loss event belongs to.
1990
+ */
2436
1991
  strategyName: StrategyName;
2437
- /** Exchange name for tracking */
1992
+ /**
1993
+ * Exchange name where this signal is being executed.
1994
+ * Identifies which exchange this loss event belongs to.
1995
+ */
2438
1996
  exchangeName: ExchangeName;
2439
- /** Time frame name for tracking (e.g., "1m", "5m") */
1997
+ /**
1998
+ * Frame name where this signal is being executed.
1999
+ * Identifies which frame this loss event belongs to (empty string for live mode).
2000
+ */
2440
2001
  frameName: FrameName;
2441
- /** Trading pair symbol (e.g., "BTCUSDT") */
2442
- symbol: string;
2443
- /** Current VWAP price when scheduled signal created */
2002
+ /**
2003
+ * Complete signal row data with original prices.
2004
+ * Contains all signal information including originalPriceStopLoss, originalPriceTakeProfit, and totalExecuted.
2005
+ */
2006
+ data: IPublicSignalRow;
2007
+ /**
2008
+ * Current market price at which this loss level was reached.
2009
+ * Used to calculate actual loss percentage.
2010
+ */
2444
2011
  currentPrice: number;
2445
- /** Whether this event is from backtest mode (true) or live mode (false) */
2012
+ /**
2013
+ * Loss level milestone reached (10, 20, 30, 40, 50, 60, 70, 80, 90, or 100).
2014
+ * Represents percentage loss relative to entry price (absolute value).
2015
+ *
2016
+ * Note: Stored as positive number, but represents negative loss.
2017
+ * level=20 means -20% loss from entry price.
2018
+ *
2019
+ * @example
2020
+ * ```typescript
2021
+ * // If entry was $50000 and level is 20:
2022
+ * // currentPrice <= $40000 (-20% loss)
2023
+ * // Level is stored as 20, not -20
2024
+ * ```
2025
+ */
2026
+ level: PartialLevel;
2027
+ /**
2028
+ * Execution mode flag.
2029
+ * - true: Event from backtest execution (historical candle data)
2030
+ * - false: Event from live trading (real-time tick)
2031
+ */
2446
2032
  backtest: boolean;
2033
+ /**
2034
+ * Event timestamp in milliseconds since Unix epoch.
2035
+ *
2036
+ * Timing semantics:
2037
+ * - Live mode: when.getTime() at the moment loss level was detected
2038
+ * - Backtest mode: candle.timestamp of the candle that triggered the level
2039
+ *
2040
+ * @example
2041
+ * ```typescript
2042
+ * const eventDate = new Date(event.timestamp);
2043
+ * console.log(`Loss reached at: ${eventDate.toISOString()}`);
2044
+ *
2045
+ * // Calculate time in loss
2046
+ * const entryTime = event.data.pendingAt;
2047
+ * const timeInLoss = event.timestamp - entryTime;
2048
+ * console.log(`In loss for ${timeInLoss / 1000 / 60} minutes`);
2049
+ * ```
2050
+ */
2051
+ timestamp: number;
2447
2052
  }
2053
+
2448
2054
  /**
2449
- * Tick result: new signal just created.
2450
- * Triggered after getSignal validation and persistence.
2055
+ * Contract for ping events during scheduled signal monitoring.
2056
+ *
2057
+ * Emitted by pingSubject every minute when a scheduled signal is being monitored.
2058
+ * Used for tracking scheduled signal lifecycle and custom monitoring logic.
2059
+ *
2060
+ * Events are emitted only when scheduled signal is active (not cancelled, not activated).
2061
+ * Allows users to implement custom cancellation logic via onPing callback.
2062
+ *
2063
+ * Consumers:
2064
+ * - User callbacks via listenPing() / listenPingOnce()
2065
+ *
2066
+ * @example
2067
+ * ```typescript
2068
+ * import { listenPing } from "backtest-kit";
2069
+ *
2070
+ * // Listen to all ping events
2071
+ * listenPing((event) => {
2072
+ * console.log(`[${event.backtest ? "Backtest" : "Live"}] Ping for ${event.symbol}`);
2073
+ * console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
2074
+ * console.log(`Signal ID: ${event.data.id}, priceOpen: ${event.data.priceOpen}`);
2075
+ * console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
2076
+ * });
2077
+ *
2078
+ * // Wait for specific ping
2079
+ * listenPingOnce(
2080
+ * (event) => event.symbol === "BTCUSDT",
2081
+ * (event) => console.log("BTCUSDT ping received:", event.timestamp)
2082
+ * );
2083
+ * ```
2451
2084
  */
2452
- interface IStrategyTickResultOpened {
2453
- /** Discriminator for type-safe union */
2454
- action: "opened";
2455
- /** Newly created and validated signal with generated ID */
2456
- signal: IPublicSignalRow;
2457
- /** Strategy name for tracking */
2085
+ interface PingContract {
2086
+ /**
2087
+ * Trading pair symbol (e.g., "BTCUSDT").
2088
+ * Identifies which market this ping event belongs to.
2089
+ */
2090
+ symbol: string;
2091
+ /**
2092
+ * Strategy name that is monitoring this scheduled signal.
2093
+ * Identifies which strategy execution this ping event belongs to.
2094
+ */
2458
2095
  strategyName: StrategyName;
2459
- /** Exchange name for tracking */
2096
+ /**
2097
+ * Exchange name where this scheduled signal is being monitored.
2098
+ * Identifies which exchange this ping event belongs to.
2099
+ */
2460
2100
  exchangeName: ExchangeName;
2461
- /** Time frame name for tracking (e.g., "1m", "5m") */
2462
- frameName: FrameName;
2463
- /** Trading pair symbol (e.g., "BTCUSDT") */
2464
- symbol: string;
2465
- /** Current VWAP price at signal open */
2466
- currentPrice: number;
2467
- /** Whether this event is from backtest mode (true) or live mode (false) */
2101
+ /**
2102
+ * Complete scheduled signal row data.
2103
+ * Contains all signal information: id, position, priceOpen, priceTakeProfit, priceStopLoss, etc.
2104
+ */
2105
+ data: IScheduledSignalRow;
2106
+ /**
2107
+ * Execution mode flag.
2108
+ * - true: Event from backtest execution (historical candle data)
2109
+ * - false: Event from live trading (real-time tick)
2110
+ */
2468
2111
  backtest: boolean;
2112
+ /**
2113
+ * Event timestamp in milliseconds since Unix epoch.
2114
+ *
2115
+ * Timing semantics:
2116
+ * - Live mode: when.getTime() at the moment of ping
2117
+ * - Backtest mode: candle.timestamp of the candle being processed
2118
+ *
2119
+ * @example
2120
+ * ```typescript
2121
+ * const eventDate = new Date(event.timestamp);
2122
+ * console.log(`Ping at: ${eventDate.toISOString()}`);
2123
+ * ```
2124
+ */
2125
+ timestamp: number;
2469
2126
  }
2127
+
2470
2128
  /**
2471
- * Tick result: signal is being monitored.
2472
- * Waiting for TP/SL or time expiration.
2473
- */
2474
- interface IStrategyTickResultActive {
2475
- /** Discriminator for type-safe union */
2476
- action: "active";
2477
- /** Currently monitored signal */
2478
- signal: IPublicSignalRow;
2479
- /** Current VWAP price for monitoring */
2480
- currentPrice: number;
2481
- /** Strategy name for tracking */
2482
- strategyName: StrategyName;
2483
- /** Exchange name for tracking */
2484
- exchangeName: ExchangeName;
2485
- /** Time frame name for tracking (e.g., "1m", "5m") */
2486
- frameName: FrameName;
2487
- /** Trading pair symbol (e.g., "BTCUSDT") */
2488
- symbol: string;
2489
- /** Percentage progress towards take profit (0-100%, 0 if moving towards SL) */
2490
- percentTp: number;
2491
- /** Percentage progress towards stop loss (0-100%, 0 if moving towards TP) */
2492
- percentSl: number;
2493
- /** Unrealized PNL for active position with fees, slippage, and partial closes */
2494
- pnl: IStrategyPnL;
2495
- /** Whether this event is from backtest mode (true) or live mode (false) */
2496
- backtest: boolean;
2497
- }
2498
- /**
2499
- * Tick result: signal closed with PNL.
2500
- * Final state with close reason and profit/loss calculation.
2501
- */
2502
- interface IStrategyTickResultClosed {
2503
- /** Discriminator for type-safe union */
2504
- action: "closed";
2505
- /** Completed signal with original parameters */
2506
- signal: IPublicSignalRow;
2507
- /** Final VWAP price at close */
2508
- currentPrice: number;
2509
- /** Why signal closed (time_expired | take_profit | stop_loss) */
2510
- closeReason: StrategyCloseReason;
2511
- /** Unix timestamp in milliseconds when signal closed */
2512
- closeTimestamp: number;
2513
- /** Profit/loss calculation with fees and slippage */
2514
- pnl: IStrategyPnL;
2515
- /** Strategy name for tracking */
2516
- strategyName: StrategyName;
2517
- /** Exchange name for tracking */
2518
- exchangeName: ExchangeName;
2519
- /** Time frame name for tracking (e.g., "1m", "5m") */
2520
- frameName: FrameName;
2521
- /** Trading pair symbol (e.g., "BTCUSDT") */
2522
- symbol: string;
2523
- /** Whether this event is from backtest mode (true) or live mode (false) */
2524
- backtest: boolean;
2525
- }
2526
- /**
2527
- * Tick result: scheduled signal cancelled without opening position.
2528
- * Occurs when scheduled signal doesn't activate or hits stop loss before entry.
2129
+ * Contract for risk rejection events.
2130
+ *
2131
+ * Emitted by riskSubject ONLY when a signal is REJECTED due to risk validation failure.
2132
+ * Used for tracking actual risk violations and monitoring rejected signals.
2133
+ *
2134
+ * Events are emitted only when risk limits are violated (not for allowed signals).
2135
+ * This prevents spam and allows focusing on actual risk management interventions.
2136
+ *
2137
+ * Consumers:
2138
+ * - RiskMarkdownService: Accumulates rejection events for report generation
2139
+ * - User callbacks via listenRisk() / listenRiskOnce()
2140
+ *
2141
+ * @example
2142
+ * ```typescript
2143
+ * import { listenRisk } from "backtest-kit";
2144
+ *
2145
+ * // Listen to all risk rejection events
2146
+ * listenRisk((event) => {
2147
+ * console.log(`[RISK REJECTED] Signal for ${event.symbol}`);
2148
+ * console.log(`Strategy: ${event.strategyName}`);
2149
+ * console.log(`Active positions: ${event.activePositionCount}`);
2150
+ * console.log(`Price: ${event.currentPrice}`);
2151
+ * console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
2152
+ * });
2153
+ *
2154
+ * // Alert on risk rejections for specific symbol
2155
+ * listenRisk((event) => {
2156
+ * if (event.symbol === "BTCUSDT") {
2157
+ * console.warn("BTC signal rejected due to risk limits!");
2158
+ * }
2159
+ * });
2160
+ * ```
2529
2161
  */
2530
- interface IStrategyTickResultCancelled {
2531
- /** Discriminator for type-safe union */
2532
- action: "cancelled";
2533
- /** Cancelled scheduled signal */
2534
- signal: IPublicSignalRow;
2535
- /** Final VWAP price at cancellation */
2536
- currentPrice: number;
2537
- /** Unix timestamp in milliseconds when signal cancelled */
2538
- closeTimestamp: number;
2539
- /** Strategy name for tracking */
2540
- strategyName: StrategyName;
2541
- /** Exchange name for tracking */
2542
- exchangeName: ExchangeName;
2543
- /** Time frame name for tracking (e.g., "1m", "5m") */
2544
- frameName: FrameName;
2545
- /** Trading pair symbol (e.g., "BTCUSDT") */
2162
+ interface RiskContract {
2163
+ /**
2164
+ * Trading pair symbol (e.g., "BTCUSDT").
2165
+ * Identifies which market this rejected signal belongs to.
2166
+ */
2546
2167
  symbol: string;
2547
- /** Whether this event is from backtest mode (true) or live mode (false) */
2548
- backtest: boolean;
2549
- /** Reason for cancellation */
2550
- reason: StrategyCancelReason;
2551
- /** Optional cancellation ID (provided when user calls Backtest.cancel() or Live.cancel()) */
2552
- cancelId?: string;
2553
- }
2554
- /**
2555
- * Discriminated union of all tick results.
2556
- * Use type guards: `result.action === "closed"` for type safety.
2557
- */
2558
- type IStrategyTickResult = IStrategyTickResultIdle | IStrategyTickResultScheduled | IStrategyTickResultOpened | IStrategyTickResultActive | IStrategyTickResultClosed | IStrategyTickResultCancelled;
2559
- /**
2560
- * Backtest returns closed result (TP/SL or time_expired) or cancelled result (scheduled signal never activated).
2561
- */
2562
- type IStrategyBacktestResult = IStrategyTickResultClosed | IStrategyTickResultCancelled;
2563
- /**
2564
- * Strategy interface implemented by ClientStrategy.
2565
- * Defines core strategy execution methods.
2566
- */
2567
- interface IStrategy {
2568
2168
  /**
2569
- * Single tick of strategy execution with VWAP monitoring.
2570
- * Checks for signal generation (throttled) and TP/SL conditions.
2571
- *
2572
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
2573
- * @param strategyName - Name of the strategy
2574
- * @returns Promise resolving to tick result (idle | opened | active | closed)
2169
+ * Pending signal to apply.
2170
+ * Contains signal details (position, priceOpen, priceTakeProfit, priceStopLoss, etc).
2575
2171
  */
2576
- tick: (symbol: string, strategyName: StrategyName) => Promise<IStrategyTickResult>;
2172
+ pendingSignal: ISignalDto;
2577
2173
  /**
2578
- * Retrieves the currently active pending signal for the symbol.
2579
- * If no active signal exists, returns null.
2580
- * Used internally for monitoring TP/SL and time expiration.
2581
- *
2582
- * @param symbol - Trading pair symbol
2583
- * @returns Promise resolving to pending signal or null
2174
+ * Strategy name requesting to open a position.
2175
+ * Identifies which strategy attempted to create the signal.
2584
2176
  */
2585
- getPendingSignal: (symbol: string) => Promise<IPublicSignalRow | null>;
2177
+ strategyName: StrategyName;
2586
2178
  /**
2587
- * Retrieves the currently active scheduled signal for the symbol.
2588
- * If no scheduled signal exists, returns null.
2589
- * Used internally for monitoring scheduled signal activation.
2590
- *
2591
- * @param symbol - Trading pair symbol
2592
- * @returns Promise resolving to scheduled signal or null
2179
+ * Frame name used in backtest execution.
2180
+ * Identifies which frame this signal was for in backtest execution.
2593
2181
  */
2594
- getScheduledSignal: (symbol: string) => Promise<IPublicSignalRow | null>;
2182
+ frameName: FrameName;
2595
2183
  /**
2596
- * Checks if breakeven threshold has been reached for the current pending signal.
2597
- *
2598
- * Uses the same formula as BREAKEVEN_FN to determine if price has moved far enough
2599
- * to cover transaction costs (slippage + fees) and allow breakeven to be set.
2600
- * Threshold: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2 transactions
2601
- *
2602
- * For LONG position:
2603
- * - Returns true when: currentPrice >= priceOpen * (1 + threshold%)
2604
- * - Example: entry=100, threshold=0.4% → true when price >= 100.4
2605
- *
2606
- * For SHORT position:
2607
- * - Returns true when: currentPrice <= priceOpen * (1 - threshold%)
2608
- * - Example: entry=100, threshold=0.4% → true when price <= 99.6
2609
- *
2610
- * Special cases:
2611
- * - Returns false if no pending signal exists
2612
- * - Returns true if trailing stop is already in profit zone (breakeven already achieved)
2613
- * - Returns false if threshold not reached yet
2614
- *
2615
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
2616
- * @param currentPrice - Current market price to check against threshold
2617
- * @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
2618
- *
2619
- * @example
2620
- * ```typescript
2621
- * // Check if breakeven is available for LONG position (entry=100, threshold=0.4%)
2622
- * const canBreakeven = await strategy.getBreakeven("BTCUSDT", 100.5);
2623
- * // Returns true (price >= 100.4)
2624
- *
2625
- * if (canBreakeven) {
2626
- * await strategy.breakeven("BTCUSDT", 100.5, false);
2627
- * }
2628
- * ```
2184
+ * Exchange name.
2185
+ * Identifies which exchange this signal was for.
2629
2186
  */
2630
- getBreakeven: (symbol: string, currentPrice: number) => Promise<boolean>;
2187
+ exchangeName: ExchangeName;
2631
2188
  /**
2632
- * Checks if the strategy has been stopped.
2633
- *
2634
- * Returns the stopped state indicating whether the strategy should
2635
- * cease processing new ticks or signals.
2636
- *
2637
- * @param symbol - Trading pair symbol
2638
- * @returns Promise resolving to true if strategy is stopped, false otherwise
2189
+ * Current VWAP price at the time of rejection.
2190
+ * Market price when risk check was performed.
2639
2191
  */
2640
- getStopped: (symbol: string) => Promise<boolean>;
2192
+ currentPrice: number;
2641
2193
  /**
2642
- * Fast backtest using historical candles.
2643
- * Iterates through candles, calculates VWAP, checks TP/SL on each candle.
2644
- *
2645
- * For scheduled signals: first monitors activation/cancellation,
2646
- * then if activated continues with TP/SL monitoring.
2647
- *
2648
- * @param symbol - Trading pair symbol
2649
- * @param strategyName - Name of the strategy
2650
- * @param candles - Array of historical candle data
2651
- * @returns Promise resolving to closed result (always completes signal)
2194
+ * Number of currently active positions across all strategies at rejection time.
2195
+ * Used to track portfolio-level exposure when signal was rejected.
2652
2196
  */
2653
- backtest: (symbol: string, strategyName: StrategyName, candles: ICandleData[]) => Promise<IStrategyBacktestResult>;
2197
+ activePositionCount: number;
2654
2198
  /**
2655
- * Stops the strategy from generating new signals.
2656
- *
2657
- * Sets internal flag to prevent getSignal from being called on subsequent ticks.
2658
- * Does NOT force-close active pending signals - they continue monitoring until natural closure (TP/SL/time_expired).
2659
- *
2660
- * Use case: Graceful shutdown in live trading mode without abandoning open positions.
2661
- *
2662
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
2663
- * @returns Promise that resolves immediately when stop flag is set
2199
+ * Unique identifier for this rejection instance.
2200
+ * Generated by ClientRisk for tracking and debugging purposes.
2201
+ * Null if validation threw exception without custom ID.
2202
+ */
2203
+ rejectionId: string | null;
2204
+ /**
2205
+ * Human-readable reason why the signal was rejected.
2206
+ * Captured from IRiskValidation.note or error message.
2664
2207
  *
2665
2208
  * @example
2666
2209
  * ```typescript
2667
- * // Graceful shutdown in Live.background() cancellation
2668
- * const cancel = await Live.background("BTCUSDT", { ... });
2669
- *
2670
- * // Later: stop new signals, let existing ones close naturally
2671
- * await cancel();
2210
+ * console.log(`Rejection reason: ${event.rejectionNote}`);
2211
+ * // Output: "Rejection reason: Max 3 positions allowed"
2672
2212
  * ```
2673
2213
  */
2674
- stop: (symbol: string, backtest: boolean) => Promise<void>;
2214
+ rejectionNote: string;
2675
2215
  /**
2676
- * Cancels the scheduled signal without stopping the strategy.
2677
- *
2678
- * Clears the scheduled signal (waiting for priceOpen activation).
2679
- * Does NOT affect active pending signals or strategy operation.
2680
- * Does NOT set stop flag - strategy can continue generating new signals.
2681
- *
2682
- * Use case: Cancel a scheduled entry that is no longer desired without stopping the entire strategy.
2683
- *
2684
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
2685
- * @param cancelId - Optional cancellation ID
2686
- * @returns Promise that resolves when scheduled signal is cleared
2216
+ * Event timestamp in milliseconds since Unix epoch.
2217
+ * Represents when the signal was rejected.
2687
2218
  *
2688
2219
  * @example
2689
2220
  * ```typescript
2690
- * // Cancel scheduled signal without stopping strategy
2691
- * await strategy.cancel("BTCUSDT");
2692
- * // Strategy continues, can generate new signals
2221
+ * const eventDate = new Date(event.timestamp);
2222
+ * console.log(`Signal rejected at: ${eventDate.toISOString()}`);
2693
2223
  * ```
2694
2224
  */
2695
- cancel: (symbol: string, backtest: boolean, cancelId?: string) => Promise<void>;
2225
+ timestamp: number;
2696
2226
  /**
2697
- * Executes partial close at profit level (moving toward TP).
2698
- *
2699
- * Closes specified percentage of position at current price.
2700
- * Updates _tpClosed, _totalClosed, and _partialHistory state.
2701
- * Persists updated signal state for crash recovery.
2227
+ * Whether this event is from backtest mode (true) or live mode (false).
2228
+ * Used to separate backtest and live risk rejection tracking.
2229
+ */
2230
+ backtest: boolean;
2231
+ }
2232
+
2233
+ /**
2234
+ * Constructor type for action handlers with strategy context.
2235
+ *
2236
+ * @param strategyName - Strategy identifier (e.g., "rsi_divergence", "macd_cross")
2237
+ * @param frameName - Timeframe identifier (e.g., "1m", "5m", "1h")
2238
+ * @param backtest - True for backtest mode, false for live trading
2239
+ * @returns Partial implementation of IAction (only required handlers)
2240
+ *
2241
+ * @example
2242
+ * ```typescript
2243
+ * class TelegramNotifier implements Partial<IAction> {
2244
+ * constructor(
2245
+ * private strategyName: StrategyName,
2246
+ * private frameName: FrameName,
2247
+ * private backtest: boolean
2248
+ * ) {}
2249
+ *
2250
+ * signal(event: IStrategyTickResult): void {
2251
+ * if (!this.backtest && event.state === 'opened') {
2252
+ * telegram.send(`[${this.strategyName}/${this.frameName}] New signal`);
2253
+ * }
2254
+ * }
2255
+ * }
2256
+ *
2257
+ * const actionCtors: TActionCtor[] = [TelegramNotifier, ReduxLogger];
2258
+ * ```
2259
+ */
2260
+ type TActionCtor = new (strategyName: StrategyName, frameName: FrameName, actionName: ActionName) => Partial<IPublicAction>;
2261
+ /**
2262
+ * Action parameters passed to ClientAction constructor.
2263
+ * Combines schema with runtime dependencies and execution context.
2264
+ *
2265
+ * Extended from IActionSchema with:
2266
+ * - Logger instance for debugging and monitoring
2267
+ * - Strategy context (strategyName, frameName)
2268
+ * - Runtime environment flags
2269
+ *
2270
+ * @example
2271
+ * ```typescript
2272
+ * const params: IActionParams = {
2273
+ * actionName: "telegram-notifier",
2274
+ * handler: TelegramNotifier,
2275
+ * callbacks: { onInit, onDispose, onSignal },
2276
+ * logger: loggerService,
2277
+ * strategyName: "rsi_divergence",
2278
+ * frameName: "1h"
2279
+ * };
2280
+ *
2281
+ * const actionClient = new ClientAction(params);
2282
+ * ```
2283
+ */
2284
+ interface IActionParams extends IActionSchema {
2285
+ /** Logger service for debugging and monitoring action execution */
2286
+ logger: ILogger;
2287
+ /** Strategy identifier this action is attached to */
2288
+ strategyName: StrategyName;
2289
+ /** Exchange name (e.g., "binance") */
2290
+ exchangeName: ExchangeName;
2291
+ /** Timeframe identifier this action is attached to */
2292
+ frameName: FrameName;
2293
+ /** Whether running in backtest mode */
2294
+ backtest: boolean;
2295
+ }
2296
+ /**
2297
+ * Lifecycle and event callbacks for action handlers.
2298
+ *
2299
+ * Provides hooks for initialization, disposal, and event handling.
2300
+ * All callbacks are optional and support both sync and async execution.
2301
+ *
2302
+ * Use cases:
2303
+ * - Resource initialization (database connections, file handles)
2304
+ * - Resource cleanup (close connections, flush buffers)
2305
+ * - Event logging and monitoring
2306
+ * - State persistence
2307
+ *
2308
+ * @example
2309
+ * ```typescript
2310
+ * const callbacks: IActionCallbacks = {
2311
+ * onInit: async (strategyName, frameName, backtest) => {
2312
+ * console.log(`[${strategyName}/${frameName}] Action initialized (backtest=${backtest})`);
2313
+ * await db.connect();
2314
+ * },
2315
+ * onSignal: (event, strategyName, frameName, backtest) => {
2316
+ * if (event.action === 'opened') {
2317
+ * console.log(`New signal opened: ${event.signal.id}`);
2318
+ * }
2319
+ * },
2320
+ * onDispose: async (strategyName, frameName, backtest) => {
2321
+ * await db.disconnect();
2322
+ * console.log(`[${strategyName}/${frameName}] Action disposed`);
2323
+ * }
2324
+ * };
2325
+ * ```
2326
+ */
2327
+ interface IActionCallbacks {
2328
+ /**
2329
+ * Called when action handler is initialized.
2702
2330
  *
2703
- * Validations:
2704
- * - Throws if no pending signal exists
2705
- * - Throws if called on scheduled signal (not yet activated)
2706
- * - Throws if percentToClose <= 0 or > 100
2707
- * - Returns false if _totalClosed + percentToClose > 100 (prevents over-closing)
2331
+ * Use for:
2332
+ * - Opening database connections
2333
+ * - Initializing external services
2334
+ * - Loading persisted state
2335
+ * - Setting up subscriptions
2708
2336
  *
2709
- * Use case: User-controlled partial close triggered from onPartialProfit callback.
2337
+ * @param actionName - Action identifier
2338
+ * @param strategyName - Strategy identifier
2339
+ * @param frameName - Timeframe identifier
2340
+ * @param backtest - True for backtest mode, false for live trading
2341
+ */
2342
+ onInit(actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
2343
+ /**
2344
+ * Called when action handler is disposed.
2710
2345
  *
2711
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
2712
- * @param percentToClose - Absolute percentage of position to close (0-100)
2713
- * @param currentPrice - Current market price for partial close
2714
- * @param backtest - Whether running in backtest mode
2715
- * @returns Promise<boolean> - true if partial close executed, false if skipped
2346
+ * Use for:
2347
+ * - Closing database connections
2348
+ * - Flushing buffers
2349
+ * - Saving state to disk
2350
+ * - Unsubscribing from observables
2716
2351
  *
2717
- * @example
2718
- * ```typescript
2719
- * callbacks: {
2720
- * onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
2721
- * if (percentTp >= 50) {
2722
- * const success = await strategy.partialProfit(symbol, 25, currentPrice, backtest);
2723
- * if (success) {
2724
- * console.log('Partial profit executed');
2725
- * }
2726
- * }
2727
- * }
2728
- * }
2729
- * ```
2352
+ * @param actionName - Action identifier
2353
+ * @param strategyName - Strategy identifier
2354
+ * @param frameName - Timeframe identifier
2355
+ * @param backtest - True for backtest mode, false for live trading
2730
2356
  */
2731
- partialProfit: (symbol: string, percentToClose: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
2357
+ onDispose(actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
2732
2358
  /**
2733
- * Executes partial close at loss level (moving toward SL).
2734
- *
2735
- * Closes specified percentage of position at current price.
2736
- * Updates _slClosed, _totalClosed, and _partialHistory state.
2737
- * Persists updated signal state for crash recovery.
2359
+ * Called on signal events from all modes (live + backtest).
2738
2360
  *
2739
- * Validations:
2740
- * - Throws if no pending signal exists
2741
- * - Throws if called on scheduled signal (not yet activated)
2742
- * - Throws if percentToClose <= 0 or > 100
2743
- * - Returns false if _totalClosed + percentToClose > 100 (prevents over-closing)
2361
+ * Triggered by: StrategyConnectionService via signalEmitter
2362
+ * Frequency: Every tick/candle when strategy is evaluated
2744
2363
  *
2745
- * Use case: User-controlled partial close triggered from onPartialLoss callback.
2364
+ * @param event - Signal state result (idle, scheduled, opened, active, closed, cancelled)
2365
+ * @param actionName - Action identifier
2366
+ * @param strategyName - Strategy identifier
2367
+ * @param frameName - Timeframe identifier
2368
+ * @param backtest - True for backtest mode, false for live trading
2369
+ */
2370
+ onSignal(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
2371
+ /**
2372
+ * Called on signal events from live trading only.
2746
2373
  *
2747
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
2748
- * @param percentToClose - Absolute percentage of position to close (0-100)
2749
- * @param currentPrice - Current market price for partial close
2750
- * @param backtest - Whether running in backtest mode
2751
- * @returns Promise<boolean> - true if partial close executed, false if skipped
2374
+ * Triggered by: StrategyConnectionService via signalLiveEmitter
2375
+ * Frequency: Every tick in live mode
2752
2376
  *
2753
- * @example
2754
- * ```typescript
2755
- * callbacks: {
2756
- * onPartialLoss: async (symbol, signal, currentPrice, percentSl, backtest) => {
2757
- * if (percentSl >= 80) {
2758
- * const success = await strategy.partialLoss(symbol, 50, currentPrice, backtest);
2759
- * if (success) {
2760
- * console.log('Partial loss executed');
2761
- * }
2762
- * }
2763
- * }
2764
- * }
2765
- * ```
2377
+ * @param event - Signal state result from live trading
2378
+ * @param actionName - Action identifier
2379
+ * @param strategyName - Strategy identifier
2380
+ * @param frameName - Timeframe identifier
2381
+ * @param backtest - Always false (live mode only)
2766
2382
  */
2767
- partialLoss: (symbol: string, percentToClose: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
2383
+ onSignalLive(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
2768
2384
  /**
2769
- * Adjusts trailing stop-loss by shifting distance between entry and original SL.
2385
+ * Called on signal events from backtest only.
2770
2386
  *
2771
- * CRITICAL: Always calculates from ORIGINAL SL, not from current trailing SL.
2772
- * This prevents error accumulation on repeated calls.
2773
- * Larger percentShift ABSORBS smaller one (updates only towards better protection).
2387
+ * Triggered by: StrategyConnectionService via signalBacktestEmitter
2388
+ * Frequency: Every candle in backtest mode
2774
2389
  *
2775
- * Calculates new SL based on percentage shift of the ORIGINAL distance (entry - originalSL):
2776
- * - Negative %: tightens stop (moves SL closer to entry, reduces risk)
2777
- * - Positive %: loosens stop (moves SL away from entry, allows more drawdown)
2390
+ * @param event - Signal state result from backtest
2391
+ * @param actionName - Action identifier
2392
+ * @param strategyName - Strategy identifier
2393
+ * @param frameName - Timeframe identifier
2394
+ * @param backtest - Always true (backtest mode only)
2395
+ */
2396
+ onSignalBacktest(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
2397
+ /**
2398
+ * Called when breakeven is triggered (stop-loss moved to entry price).
2778
2399
  *
2779
- * For LONG position (entry=100, originalSL=90, distance=10%):
2780
- * - percentShift = -50: newSL = 100 - 10%*(1-0.5) = 95 (5% distance, tighter)
2781
- * - percentShift = +20: newSL = 100 - 10%*(1+0.2) = 88 (12% distance, looser)
2400
+ * Triggered by: BreakevenConnectionService via breakevenSubject
2401
+ * Frequency: Once per signal when breakeven threshold is reached
2782
2402
  *
2783
- * For SHORT position (entry=100, originalSL=110, distance=10%):
2784
- * - percentShift = -50: newSL = 100 + 10%*(1-0.5) = 105 (5% distance, tighter)
2785
- * - percentShift = +20: newSL = 100 + 10%*(1+0.2) = 112 (12% distance, looser)
2403
+ * @param event - Breakeven milestone data
2404
+ * @param actionName - Action identifier
2405
+ * @param strategyName - Strategy identifier
2406
+ * @param frameName - Timeframe identifier
2407
+ * @param backtest - True for backtest mode, false for live trading
2408
+ */
2409
+ onBreakeven(event: BreakevenContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
2410
+ /**
2411
+ * Called when partial profit level is reached (10%, 20%, 30%, etc).
2786
2412
  *
2787
- * Absorption behavior:
2788
- * - First call: sets trailing SL unconditionally
2789
- * - Subsequent calls: updates only if new SL is BETTER (protects more profit)
2790
- * - For LONG: only accepts HIGHER SL (never moves down, closer to entry wins)
2791
- * - For SHORT: only accepts LOWER SL (never moves up, closer to entry wins)
2792
- * - Stores in _trailingPriceStopLoss, original priceStopLoss always preserved
2413
+ * Triggered by: PartialConnectionService via partialProfitSubject
2414
+ * Frequency: Once per profit level per signal (deduplicated)
2793
2415
  *
2794
- * Validations:
2795
- * - Throws if no pending signal exists
2796
- * - Throws if percentShift < -100 or > 100
2797
- * - Throws if percentShift === 0
2798
- * - Skips if new SL would cross entry price
2799
- * - Skips if currentPrice already crossed new SL level (price intrusion protection)
2800
- *
2801
- * Use case: User-controlled trailing stop triggered from onPartialProfit callback.
2802
- *
2803
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
2804
- * @param percentShift - Percentage shift of ORIGINAL SL distance [-100, 100], excluding 0
2805
- * @param currentPrice - Current market price to check for intrusion
2806
- * @param backtest - Whether running in backtest mode
2807
- * @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected
2808
- *
2809
- * @example
2810
- * ```typescript
2811
- * callbacks: {
2812
- * onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
2813
- * if (percentTp >= 50) {
2814
- * // LONG: entry=100, originalSL=90, distance=10%
2815
- *
2816
- * // First call: tighten by 5%
2817
- * const success1 = await strategy.trailingStop(symbol, -5, currentPrice, backtest);
2818
- * // success1 = true, newDistance = 10% - 5% = 5%, newSL = 95
2819
- *
2820
- * // Second call: try weaker protection
2821
- * const success2 = await strategy.trailingStop(symbol, -3, currentPrice, backtest);
2822
- * // success2 = false (SKIPPED: newSL=97 < 95, worse protection, larger % absorbs smaller)
2823
- *
2824
- * // Third call: stronger protection
2825
- * const success3 = await strategy.trailingStop(symbol, -7, currentPrice, backtest);
2826
- * // success3 = true (ACCEPTED: newDistance = 3%, newSL = 97 > 95, better protection)
2827
- * }
2828
- * }
2829
- * }
2830
- * ```
2416
+ * @param event - Profit milestone data with level and price
2417
+ * @param actionName - Action identifier
2418
+ * @param strategyName - Strategy identifier
2419
+ * @param frameName - Timeframe identifier
2420
+ * @param backtest - True for backtest mode, false for live trading
2831
2421
  */
2832
- trailingStop: (symbol: string, percentShift: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
2422
+ onPartialProfit(event: PartialProfitContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
2833
2423
  /**
2834
- * Adjusts the trailing take-profit distance for an active pending signal.
2835
- *
2836
- * CRITICAL: Always calculates from ORIGINAL TP, not from current trailing TP.
2837
- * This prevents error accumulation on repeated calls.
2838
- * Larger percentShift ABSORBS smaller one (updates only towards more conservative TP).
2839
- *
2840
- * Updates the take-profit distance by a percentage adjustment relative to the ORIGINAL TP distance.
2841
- * Negative percentShift brings TP closer to entry (more conservative).
2842
- * Positive percentShift moves TP further from entry (more aggressive).
2843
- *
2844
- * Absorption behavior:
2845
- * - First call: sets trailing TP unconditionally
2846
- * - Subsequent calls: updates only if new TP is MORE CONSERVATIVE (closer to entry)
2847
- * - For LONG: only accepts LOWER TP (never moves up, closer to entry wins)
2848
- * - For SHORT: only accepts HIGHER TP (never moves down, closer to entry wins)
2849
- * - Stores in _trailingPriceTakeProfit, original priceTakeProfit always preserved
2850
- *
2851
- * Price intrusion protection: If current price has already crossed the new TP level,
2852
- * the update is skipped to prevent immediate TP triggering.
2853
- *
2854
- * @param symbol - Trading pair symbol
2855
- * @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
2856
- * @param currentPrice - Current market price to check for intrusion
2857
- * @param backtest - Whether running in backtest mode
2858
- * @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected
2859
- *
2860
- * @example
2861
- * ```typescript
2862
- * callbacks: {
2863
- * onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
2864
- * // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
2865
- *
2866
- * // First call: bring TP closer by 3%
2867
- * const success1 = await strategy.trailingTake(symbol, -3, currentPrice, backtest);
2868
- * // success1 = true, newDistance = 10% - 3% = 7%, newTP = 107
2424
+ * Called when partial loss level is reached (-10%, -20%, -30%, etc).
2869
2425
  *
2870
- * // Second call: try to move TP further (less conservative)
2871
- * const success2 = await strategy.trailingTake(symbol, 2, currentPrice, backtest);
2872
- * // success2 = false (SKIPPED: newTP=112 > 107, less conservative, larger % absorbs smaller)
2426
+ * Triggered by: PartialConnectionService via partialLossSubject
2427
+ * Frequency: Once per loss level per signal (deduplicated)
2873
2428
  *
2874
- * // Third call: even more conservative
2875
- * const success3 = await strategy.trailingTake(symbol, -5, currentPrice, backtest);
2876
- * // success3 = true (ACCEPTED: newDistance = 5%, newTP = 105 < 107, more conservative)
2877
- * }
2878
- * }
2879
- * ```
2429
+ * @param event - Loss milestone data with level and price
2430
+ * @param actionName - Action identifier
2431
+ * @param strategyName - Strategy identifier
2432
+ * @param frameName - Timeframe identifier
2433
+ * @param backtest - True for backtest mode, false for live trading
2880
2434
  */
2881
- trailingTake: (symbol: string, percentShift: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
2435
+ onPartialLoss(event: PartialLossContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
2882
2436
  /**
2883
- * Moves stop-loss to breakeven (entry price) when price reaches threshold.
2884
- *
2885
- * Moves SL to entry price (zero-risk position) when current price has moved
2886
- * far enough in profit direction to cover transaction costs (slippage + fees).
2887
- * Threshold is calculated as: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2
2888
- *
2889
- * Behavior:
2890
- * - Returns true if SL was moved to breakeven
2891
- * - Returns false if conditions not met (threshold not reached or already at breakeven)
2892
- * - Uses _trailingPriceStopLoss to store breakeven SL (preserves original priceStopLoss)
2893
- * - Only moves SL once per position (idempotent - safe to call multiple times)
2894
- *
2895
- * For LONG position (entry=100, slippage=0.1%, fee=0.1%):
2896
- * - Threshold: (0.1 + 0.1) * 2 = 0.4%
2897
- * - Breakeven available when price >= 100.4 (entry + 0.4%)
2898
- * - Moves SL from original (e.g. 95) to 100 (breakeven)
2899
- * - Returns true on first successful move, false on subsequent calls
2900
- *
2901
- * For SHORT position (entry=100, slippage=0.1%, fee=0.1%):
2902
- * - Threshold: (0.1 + 0.1) * 2 = 0.4%
2903
- * - Breakeven available when price <= 99.6 (entry - 0.4%)
2904
- * - Moves SL from original (e.g. 105) to 100 (breakeven)
2905
- * - Returns true on first successful move, false on subsequent calls
2906
- *
2907
- * Validations:
2908
- * - Throws if no pending signal exists
2909
- * - Throws if currentPrice is not a positive finite number
2910
- *
2911
- * Use case: User-controlled breakeven protection triggered from onPartialProfit callback.
2437
+ * Called during scheduled signal monitoring (every minute while waiting for activation).
2912
2438
  *
2913
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
2914
- * @param currentPrice - Current market price to check threshold
2915
- * @param backtest - Whether running in backtest mode
2916
- * @returns Promise<boolean> - true if breakeven was set, false if conditions not met
2439
+ * Triggered by: StrategyConnectionService via pingSubject
2440
+ * Frequency: Every minute while scheduled signal is waiting
2917
2441
  *
2918
- * @example
2919
- * ```typescript
2920
- * callbacks: {
2921
- * onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
2922
- * // Try to move SL to breakeven when threshold reached
2923
- * const movedToBreakeven = await strategy.breakeven(symbol, currentPrice, backtest);
2924
- * if (movedToBreakeven) {
2925
- * console.log(`Position moved to breakeven at ${currentPrice}`);
2926
- * }
2927
- * }
2928
- * }
2929
- * ```
2442
+ * @param event - Scheduled signal monitoring data
2443
+ * @param actionName - Action identifier
2444
+ * @param strategyName - Strategy identifier
2445
+ * @param frameName - Timeframe identifier
2446
+ * @param backtest - True for backtest mode, false for live trading
2930
2447
  */
2931
- breakeven: (symbol: string, currentPrice: number, backtest: boolean) => Promise<boolean>;
2448
+ onPing(event: PingContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
2932
2449
  /**
2933
- * Disposes the strategy instance and cleans up resources.
2450
+ * Called when signal is rejected by risk management.
2934
2451
  *
2935
- * Called when the strategy is being removed from cache or shut down.
2936
- * Invokes the onDispose callback to notify external systems.
2452
+ * Triggered by: RiskConnectionService via riskSubject
2453
+ * Frequency: Only when signal fails risk validation (not emitted for allowed signals)
2937
2454
  *
2938
- * @returns Promise that resolves when disposal is complete
2455
+ * @param event - Risk rejection data with reason and context
2456
+ * @param actionName - Action identifier
2457
+ * @param strategyName - Strategy identifier
2458
+ * @param frameName - Timeframe identifier
2459
+ * @param backtest - True for backtest mode, false for live trading
2939
2460
  */
2940
- dispose: () => Promise<void>;
2941
- }
2942
- /**
2943
- * Unique strategy identifier.
2944
- */
2945
- type StrategyName = string;
2946
-
2947
- /**
2948
- * Unified breakeven event data for report generation.
2949
- * Contains all information about when signals reached breakeven.
2950
- */
2951
- interface BreakevenEvent {
2952
- /** Event timestamp in milliseconds */
2953
- timestamp: number;
2954
- /** Trading pair symbol */
2955
- symbol: string;
2956
- /** Strategy name */
2957
- strategyName: StrategyName;
2958
- /** Signal ID */
2959
- signalId: string;
2960
- /** Position type */
2961
- position: string;
2962
- /** Current market price when breakeven was reached */
2963
- currentPrice: number;
2964
- /** Entry price (breakeven level) */
2965
- priceOpen: number;
2966
- /** Take profit target price */
2967
- priceTakeProfit?: number;
2968
- /** Stop loss exit price */
2969
- priceStopLoss?: number;
2970
- /** Original take profit price set at signal creation */
2971
- originalPriceTakeProfit?: number;
2972
- /** Original stop loss price set at signal creation */
2973
- originalPriceStopLoss?: number;
2974
- /** Total executed percentage from partial closes */
2975
- totalExecuted?: number;
2976
- /** Human-readable description of signal reason */
2977
- note?: string;
2978
- /** True if backtest mode, false if live mode */
2979
- backtest: boolean;
2461
+ onRiskRejection(event: RiskContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
2980
2462
  }
2981
2463
  /**
2982
- * Statistical data calculated from breakeven events.
2464
+ * Action schema registered via addAction().
2465
+ * Defines event handler implementation and lifecycle callbacks for state management integration.
2983
2466
  *
2984
- * Provides metrics for breakeven milestone tracking.
2467
+ * Actions provide a way to attach custom event handlers to strategies for:
2468
+ * - State management (Redux, Zustand, MobX)
2469
+ * - Event logging and monitoring
2470
+ * - Real-time notifications (Telegram, Discord, email)
2471
+ * - Analytics and metrics collection
2472
+ * - Custom business logic triggers
2473
+ *
2474
+ * Each action instance is created per strategy-frame pair and receives all events
2475
+ * emitted during strategy execution. Multiple actions can be attached to a single strategy.
2985
2476
  *
2986
2477
  * @example
2987
2478
  * ```typescript
2988
- * const stats = await Breakeven.getData("BTCUSDT", "my-strategy");
2479
+ * import { addAction } from "backtest-kit";
2989
2480
  *
2990
- * console.log(`Total breakeven events: ${stats.totalEvents}`);
2991
- * console.log(`Average threshold: ${stats.averageThreshold}%`);
2481
+ * // Define action handler class
2482
+ * class TelegramNotifier implements Partial<IAction> {
2483
+ * constructor(
2484
+ * private strategyName: StrategyName,
2485
+ * private frameName: FrameName,
2486
+ * private backtest: boolean
2487
+ * ) {}
2488
+ *
2489
+ * signal(event: IStrategyTickResult): void {
2490
+ * if (!this.backtest && event.action === 'opened') {
2491
+ * telegram.send(`[${this.strategyName}/${this.frameName}] New signal`);
2492
+ * }
2493
+ * }
2494
+ *
2495
+ * dispose(): void {
2496
+ * telegram.close();
2497
+ * }
2498
+ * }
2499
+ *
2500
+ * // Register action schema
2501
+ * addAction({
2502
+ * actionName: "telegram-notifier",
2503
+ * handler: TelegramNotifier,
2504
+ * callbacks: {
2505
+ * onInit: async (strategyName, frameName, backtest) => {
2506
+ * console.log(`Telegram notifier initialized for ${strategyName}/${frameName}`);
2507
+ * },
2508
+ * onSignal: (event, strategyName, frameName, backtest) => {
2509
+ * console.log(`Signal event: ${event.action}`);
2510
+ * }
2511
+ * }
2512
+ * });
2992
2513
  * ```
2993
2514
  */
2994
- interface BreakevenStatisticsModel {
2995
- /** Array of all breakeven events with full details */
2996
- eventList: BreakevenEvent[];
2997
- /** Total number of breakeven events */
2998
- totalEvents: number;
2999
- }
3000
-
3001
- declare const GLOBAL_CONFIG: {
3002
- /**
3003
- * Time to wait for scheduled signal to activate (in minutes)
3004
- * If signal does not activate within this time, it will be cancelled.
3005
- */
3006
- CC_SCHEDULE_AWAIT_MINUTES: number;
3007
- /**
3008
- * Number of candles to use for average price calculation (VWAP)
3009
- * Default: 5 candles (last 5 minutes when using 1m interval)
3010
- */
3011
- CC_AVG_PRICE_CANDLES_COUNT: number;
3012
- /**
3013
- * Slippage percentage applied to entry and exit prices.
3014
- * Simulates market impact and order book depth.
3015
- * Applied twice (entry and exit) for realistic execution simulation.
3016
- * Default: 0.1% per transaction
3017
- */
3018
- CC_PERCENT_SLIPPAGE: number;
3019
- /**
3020
- * Fee percentage charged per transaction.
3021
- * Applied twice (entry and exit) for total fee calculation.
3022
- * Default: 0.1% per transaction (total 0.2%)
3023
- */
3024
- CC_PERCENT_FEE: number;
2515
+ interface IActionSchema {
2516
+ /** Unique action identifier for registration */
2517
+ actionName: ActionName;
2518
+ /** Action handler constructor (instantiated per strategy-frame pair) */
2519
+ handler: TActionCtor | Partial<IPublicAction>;
2520
+ /** Optional lifecycle and event callbacks */
2521
+ callbacks?: Partial<IActionCallbacks>;
2522
+ }
2523
+ /**
2524
+ * Public action interface for custom action handler implementations.
2525
+ *
2526
+ * Extends IAction with an initialization lifecycle method.
2527
+ * Action handlers implement this interface to receive strategy events and perform custom logic.
2528
+ *
2529
+ * Lifecycle:
2530
+ * 1. Constructor called with (strategyName, frameName, actionName)
2531
+ * 2. init() called once for async initialization (setup connections, load resources)
2532
+ * 3. Event methods called as strategy executes (signal, breakeven, partialProfit, etc.)
2533
+ * 4. dispose() called once for cleanup (close connections, flush buffers)
2534
+ *
2535
+ * Key features:
2536
+ * - init() for async initialization (database connections, API clients, file handles)
2537
+ * - All IAction methods available for event handling
2538
+ * - dispose() guaranteed to run exactly once via singleshot pattern
2539
+ *
2540
+ * Common use cases:
2541
+ * - State management: Redux/Zustand store integration
2542
+ * - Notifications: Telegram/Discord/Email alerts
2543
+ * - Logging: Custom event tracking and monitoring
2544
+ * - Analytics: Metrics collection and reporting
2545
+ * - External systems: Database writes, API calls, file operations
2546
+ *
2547
+ * @example
2548
+ * ```typescript
2549
+ * class TelegramNotifier implements Partial<IPublicAction> {
2550
+ * private bot: TelegramBot | null = null;
2551
+ *
2552
+ * constructor(
2553
+ * private strategyName: string,
2554
+ * private frameName: string,
2555
+ * private actionName: string
2556
+ * ) {}
2557
+ *
2558
+ * // Called once during initialization
2559
+ * async init() {
2560
+ * this.bot = new TelegramBot(process.env.TELEGRAM_TOKEN);
2561
+ * await this.bot.connect();
2562
+ * }
2563
+ *
2564
+ * // Called on every signal event
2565
+ * async signal(event: IStrategyTickResult) {
2566
+ * if (event.action === 'opened') {
2567
+ * await this.bot.send(
2568
+ * `[${this.strategyName}/${this.frameName}] Signal opened: ${event.signal.side}`
2569
+ * );
2570
+ * }
2571
+ * }
2572
+ *
2573
+ * // Called once during cleanup
2574
+ * async dispose() {
2575
+ * await this.bot?.disconnect();
2576
+ * this.bot = null;
2577
+ * }
2578
+ * }
2579
+ * ```
2580
+ *
2581
+ * @see IAction for all available event methods
2582
+ * @see TActionCtor for constructor signature requirements
2583
+ * @see ClientAction for internal wrapper that manages lifecycle
2584
+ */
2585
+ interface IPublicAction extends IAction {
3025
2586
  /**
3026
- * Minimum TakeProfit distance from priceOpen (percentage)
3027
- * Must be greater than (slippage + fees) to ensure profitable trades
2587
+ * Async initialization method called once after construction.
3028
2588
  *
3029
- * Calculation:
3030
- * - Slippage effect: ~0.2% (0.1% × 2 transactions)
3031
- * - Fees: 0.2% (0.1% × 2 transactions)
3032
- * - Minimum profit buffer: 0.1%
3033
- * - Total: 0.5%
2589
+ * Use this method to:
2590
+ * - Establish database connections
2591
+ * - Initialize API clients
2592
+ * - Load configuration files
2593
+ * - Open file handles or network sockets
2594
+ * - Perform any async setup required before handling events
3034
2595
  *
3035
- * Default: 0.5% (covers all costs + minimum profit margin)
3036
- */
3037
- CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: number;
3038
- /**
3039
- * Minimum StopLoss distance from priceOpen (percentage)
3040
- * Prevents signals from being immediately stopped out due to price volatility
3041
- * Default: 0.5% (buffer to avoid instant stop loss on normal market fluctuations)
2596
+ * Guaranteed to:
2597
+ * - Run exactly once per action handler instance
2598
+ * - Complete before any event methods are called
2599
+ * - Run after constructor but before first event
2600
+ *
2601
+ * @returns Promise that resolves when initialization is complete
2602
+ * @throws Error if initialization fails (will prevent strategy execution)
2603
+ *
2604
+ * @example
2605
+ * ```typescript
2606
+ * async init() {
2607
+ * this.db = await connectToDatabase();
2608
+ * this.cache = new Redis(process.env.REDIS_URL);
2609
+ * await this.cache.connect();
2610
+ * console.log('Action initialized');
2611
+ * }
2612
+ * ```
3042
2613
  */
3043
- CC_MIN_STOPLOSS_DISTANCE_PERCENT: number;
2614
+ init(): void | Promise<void>;
2615
+ }
2616
+ /**
2617
+ * Action interface for state manager integration.
2618
+ *
2619
+ * Provides methods to handle all events emitted by connection services.
2620
+ * Each method corresponds to a specific event type emitted via .next() calls.
2621
+ *
2622
+ * Use this interface to implement custom state management logic:
2623
+ * - Redux/Zustand action dispatchers
2624
+ * - Event logging systems
2625
+ * - Real-time monitoring dashboards
2626
+ * - Analytics and metrics collection
2627
+ *
2628
+ * @example
2629
+ * ```typescript
2630
+ * class ReduxStateManager implements IAction {
2631
+ * constructor(private store: Store) {}
2632
+ *
2633
+ * signal(event: IStrategyTickResult): void {
2634
+ * this.store.dispatch({ type: 'SIGNAL', payload: event });
2635
+ * }
2636
+ *
2637
+ * breakeven(event: BreakevenContract): void {
2638
+ * this.store.dispatch({ type: 'BREAKEVEN', payload: event });
2639
+ * }
2640
+ *
2641
+ * // ... implement other methods
2642
+ * }
2643
+ * ```
2644
+ */
2645
+ interface IAction {
3044
2646
  /**
3045
- * Maximum StopLoss distance from priceOpen (percentage)
3046
- * Prevents catastrophic losses from extreme StopLoss values
3047
- * Default: 20% (one signal cannot lose more than 20% of position)
2647
+ * Handles signal events from all modes (live + backtest).
2648
+ *
2649
+ * Emitted by: StrategyConnectionService via signalEmitter
2650
+ * Source: StrategyConnectionService.tick() and StrategyConnectionService.backtest()
2651
+ * Frequency: Every tick/candle when strategy is evaluated
2652
+ *
2653
+ * @param event - Signal state result (idle, scheduled, opened, active, closed, cancelled)
3048
2654
  */
3049
- CC_MAX_STOPLOSS_DISTANCE_PERCENT: number;
2655
+ signal(event: IStrategyTickResult): void | Promise<void>;
3050
2656
  /**
3051
- * Maximum signal lifetime in minutes
3052
- * Prevents eternal signals that block risk limits for weeks/months
3053
- * Default: 1440 minutes (1 day)
2657
+ * Handles signal events from live trading only.
2658
+ *
2659
+ * Emitted by: StrategyConnectionService via signalLiveEmitter
2660
+ * Source: StrategyConnectionService.tick() when backtest=false
2661
+ * Frequency: Every tick in live mode
2662
+ *
2663
+ * @param event - Signal state result from live trading
3054
2664
  */
3055
- CC_MAX_SIGNAL_LIFETIME_MINUTES: number;
2665
+ signalLive(event: IStrategyTickResult): void | Promise<void>;
3056
2666
  /**
3057
- * Maximum time allowed for signal generation (in seconds).
3058
- * Prevents long-running or stuck signal generation routines from blocking
3059
- * execution or consuming resources indefinitely. If generation exceeds this
3060
- * threshold the attempt should be aborted, logged and optionally retried.
2667
+ * Handles signal events from backtest only.
3061
2668
  *
3062
- * Default: 180 seconds (3 minutes)
2669
+ * Emitted by: StrategyConnectionService via signalBacktestEmitter
2670
+ * Source: StrategyConnectionService.backtest() when backtest=true
2671
+ * Frequency: Every candle in backtest mode
2672
+ *
2673
+ * @param event - Signal state result from backtest
3063
2674
  */
3064
- CC_MAX_SIGNAL_GENERATION_SECONDS: number;
2675
+ signalBacktest(event: IStrategyTickResult): void | Promise<void>;
3065
2676
  /**
3066
- * Number of retries for getCandles function
3067
- * Default: 3 retries
2677
+ * Handles breakeven events when stop-loss is moved to entry price.
2678
+ *
2679
+ * Emitted by: BreakevenConnectionService via breakevenSubject
2680
+ * Source: COMMIT_BREAKEVEN_FN callback in BreakevenConnectionService
2681
+ * Frequency: Once per signal when breakeven threshold is reached
2682
+ *
2683
+ * @param event - Breakeven milestone data
3068
2684
  */
3069
- CC_GET_CANDLES_RETRY_COUNT: number;
2685
+ breakeven(event: BreakevenContract): void | Promise<void>;
3070
2686
  /**
3071
- * Delay between retries for getCandles function (in milliseconds)
3072
- * Default: 5000 ms (5 seconds)
2687
+ * Handles partial profit level events (10%, 20%, 30%, etc).
2688
+ *
2689
+ * Emitted by: PartialConnectionService via partialProfitSubject
2690
+ * Source: COMMIT_PROFIT_FN callback in PartialConnectionService
2691
+ * Frequency: Once per profit level per signal (deduplicated)
2692
+ *
2693
+ * @param event - Profit milestone data with level and price
3073
2694
  */
3074
- CC_GET_CANDLES_RETRY_DELAY_MS: number;
2695
+ partialProfit(event: PartialProfitContract): void | Promise<void>;
3075
2696
  /**
3076
- * Maximum number of candles to request per single API call.
3077
- * If a request exceeds this limit, data will be fetched using pagination.
3078
- * Default: 1000 candles per request
2697
+ * Handles partial loss level events (-10%, -20%, -30%, etc).
2698
+ *
2699
+ * Emitted by: PartialConnectionService via partialLossSubject
2700
+ * Source: COMMIT_LOSS_FN callback in PartialConnectionService
2701
+ * Frequency: Once per loss level per signal (deduplicated)
2702
+ *
2703
+ * @param event - Loss milestone data with level and price
3079
2704
  */
3080
- CC_MAX_CANDLES_PER_REQUEST: number;
2705
+ partialLoss(event: PartialLossContract): void | Promise<void>;
3081
2706
  /**
3082
- * Maximum allowed deviation factor for price anomaly detection.
3083
- * Price should not be more than this factor lower than reference price.
2707
+ * Handles ping events during scheduled signal monitoring.
3084
2708
  *
3085
- * Reasoning:
3086
- * - Incomplete candles from Binance API typically have prices near 0 (e.g., $0.01-1)
3087
- * - Normal BTC price ranges: $20,000-100,000
3088
- * - Factor 1000 catches prices below $20-100 when median is $20,000-100,000
3089
- * - Factor 100 would be too permissive (allows $200 when median is $20,000)
3090
- * - Factor 10000 might be too strict for low-cap altcoins
2709
+ * Emitted by: StrategyConnectionService via pingSubject
2710
+ * Source: COMMIT_PING_FN callback in StrategyConnectionService
2711
+ * Frequency: Every minute while scheduled signal is waiting for activation
3091
2712
  *
3092
- * Example: BTC at $50,000 median threshold $50 (catches $0.01-1 anomalies)
2713
+ * @param event - Scheduled signal monitoring data
3093
2714
  */
3094
- CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR: number;
2715
+ ping(event: PingContract): void | Promise<void>;
3095
2716
  /**
3096
- * Minimum number of candles required for reliable median calculation.
3097
- * Below this threshold, use simple average instead of median.
2717
+ * Handles risk rejection events when signals fail risk validation.
3098
2718
  *
3099
- * Reasoning:
3100
- * - Each candle provides 4 price points (OHLC)
3101
- * - 5 candles = 20 price points, sufficient for robust median calculation
3102
- * - Below 5 candles, single anomaly can heavily skew median
3103
- * - Statistical rule of thumb: minimum 7-10 data points for median stability
3104
- * - Average is more stable than median for small datasets (n < 20)
3105
- *
3106
- * Example: 3 candles = 12 points (use average), 5 candles = 20 points (use median)
3107
- */
3108
- CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN: number;
3109
- /**
3110
- * Controls visibility of signal notes in markdown report tables.
3111
- * When enabled, the "Note" column will be displayed in all markdown reports
3112
- * (backtest, live, schedule, risk, etc.)
3113
- *
3114
- * Default: false (notes are hidden to reduce table width and improve readability)
3115
- */
3116
- CC_REPORT_SHOW_SIGNAL_NOTE: boolean;
3117
- /**
3118
- * Breakeven threshold percentage - minimum profit distance from entry to enable breakeven.
3119
- * When price moves this percentage in profit direction, stop-loss can be moved to entry (breakeven).
3120
- *
3121
- * Calculation:
3122
- * - Slippage effect: ~0.2% (0.1% × 2 transactions)
3123
- * - Fees: 0.2% (0.1% × 2 transactions)
3124
- * - Total: 0.4%
3125
- * - Added buffer: 0.2%
3126
- * - Overall: 0.6%
3127
- *
3128
- * Default: 0.2% (additional buffer above costs to ensure no loss when moving to breakeven)
3129
- */
3130
- CC_BREAKEVEN_THRESHOLD: number;
3131
- /**
3132
- * Time offset in minutes for order book fetching.
3133
- * Subtracts this amount from the current time when fetching order book data.
3134
- * This helps get a more stable snapshot of the order book by avoiding real-time volatility.
2719
+ * Emitted by: RiskConnectionService via riskSubject
2720
+ * Source: COMMIT_REJECTION_FN callback in RiskConnectionService
2721
+ * Frequency: Only when signal is rejected (not emitted for allowed signals)
3135
2722
  *
3136
- * Default: 10 minutes
2723
+ * @param event - Risk rejection data with reason and context
3137
2724
  */
3138
- CC_ORDER_BOOK_TIME_OFFSET_MINUTES: number;
2725
+ riskRejection(event: RiskContract): void | Promise<void>;
3139
2726
  /**
3140
- * Maximum depth levels for order book fetching.
3141
- * Specifies how many price levels to fetch from both bids and asks.
2727
+ * Cleans up resources and subscriptions when action handler is no longer needed.
3142
2728
  *
3143
- * Default: 20 levels
2729
+ * Called by: Connection services during shutdown
2730
+ * Use for: Unsubscribing from observables, closing connections, flushing buffers
3144
2731
  */
3145
- CC_ORDER_BOOK_MAX_DEPTH_LEVELS: number;
3146
- };
3147
- /**
3148
- * Type for global configuration object.
3149
- */
3150
- type GlobalConfig = typeof GLOBAL_CONFIG;
3151
-
3152
- /**
3153
- * Mapping of available table/markdown reports to their column definitions.
3154
- *
3155
- * Each property references a column definition object imported from
3156
- * `src/assets/*.columns`. These are used by markdown/report generators
3157
- * (backtest, live, schedule, risk, heat, performance, partial, walker).
3158
- */
3159
- declare const COLUMN_CONFIG: {
3160
- /** Columns used in backtest markdown tables and reports */
3161
- backtest_columns: ColumnModel<IStrategyTickResultClosed>[];
3162
- /** Columns used by heatmap / heat reports */
3163
- heat_columns: ColumnModel<IHeatmapRow>[];
3164
- /** Columns for live trading reports and logs */
3165
- live_columns: ColumnModel<TickEvent>[];
3166
- /** Columns for partial-results / incremental reports */
3167
- partial_columns: ColumnModel<PartialEvent>[];
3168
- /** Columns for breakeven protection events */
3169
- breakeven_columns: ColumnModel<BreakevenEvent>[];
3170
- /** Columns for performance summary reports */
3171
- performance_columns: ColumnModel<MetricStats>[];
3172
- /** Columns for risk-related reports */
3173
- risk_columns: ColumnModel<RiskEvent>[];
3174
- /** Columns for scheduled report output */
3175
- schedule_columns: ColumnModel<ScheduledEvent>[];
3176
- /** Walker: PnL summary columns */
3177
- walker_pnl_columns: ColumnModel<SignalData$1>[];
3178
- /** Walker: strategy-level summary columns */
3179
- walker_strategy_columns: ColumnModel<IStrategyResult>[];
3180
- };
2732
+ dispose(): void | Promise<void>;
2733
+ }
3181
2734
  /**
3182
- * Type for the column configuration object.
2735
+ * Unique action identifier.
3183
2736
  */
3184
- type ColumnConfig = typeof COLUMN_CONFIG;
2737
+ type ActionName = string;
3185
2738
 
3186
2739
  /**
3187
- * Sets custom logger implementation for the framework.
3188
- *
3189
- * All log messages from internal services will be forwarded to the provided logger
3190
- * with automatic context injection (strategyName, exchangeName, symbol, etc.).
2740
+ * Statistical data calculated from backtest results.
3191
2741
  *
3192
- * @param logger - Custom logger implementing ILogger interface
2742
+ * All numeric values are null if calculation is unsafe (NaN, Infinity, etc).
2743
+ * Provides comprehensive metrics for strategy performance analysis.
3193
2744
  *
3194
2745
  * @example
3195
2746
  * ```typescript
3196
- * setLogger({
3197
- * log: (topic, ...args) => console.log(topic, args),
3198
- * debug: (topic, ...args) => console.debug(topic, args),
3199
- * info: (topic, ...args) => console.info(topic, args),
3200
- * });
3201
- * ```
3202
- */
3203
- declare function setLogger(logger: ILogger): void;
3204
- /**
3205
- * Sets global configuration parameters for the framework.
3206
- * @param config - Partial configuration object to override default settings
3207
- * @param _unsafe - Skip config validations - required for testbed
2747
+ * const stats = await Backtest.getData("my-strategy");
3208
2748
  *
3209
- * @example
3210
- * ```typescript
3211
- * setConfig({
3212
- * CC_SCHEDULE_AWAIT_MINUTES: 90,
2749
+ * console.log(`Total signals: ${stats.totalSignals}`);
2750
+ * console.log(`Win rate: ${stats.winRate}%`);
2751
+ * console.log(`Sharpe Ratio: ${stats.sharpeRatio}`);
2752
+ *
2753
+ * // Access raw signal data
2754
+ * stats.signalList.forEach(signal => {
2755
+ * console.log(`Signal ${signal.signal.id}: ${signal.pnl.pnlPercentage}%`);
3213
2756
  * });
3214
2757
  * ```
3215
2758
  */
3216
- declare function setConfig(config: Partial<GlobalConfig>, _unsafe?: boolean): void;
2759
+ interface BacktestStatisticsModel {
2760
+ /** Array of all closed signals with full details (price, PNL, timestamps, etc.) */
2761
+ signalList: IStrategyTickResultClosed[];
2762
+ /** Total number of closed signals */
2763
+ totalSignals: number;
2764
+ /** Number of winning signals (PNL > 0) */
2765
+ winCount: number;
2766
+ /** Number of losing signals (PNL < 0) */
2767
+ lossCount: number;
2768
+ /** Win rate as percentage (0-100), null if unsafe. Higher is better. */
2769
+ winRate: number | null;
2770
+ /** Average PNL per signal as percentage, null if unsafe. Higher is better. */
2771
+ avgPnl: number | null;
2772
+ /** Cumulative PNL across all signals as percentage, null if unsafe. Higher is better. */
2773
+ totalPnl: number | null;
2774
+ /** Standard deviation of returns (volatility metric), null if unsafe. Lower is better. */
2775
+ stdDev: number | null;
2776
+ /** Sharpe Ratio (risk-adjusted return = avgPnl / stdDev), null if unsafe. Higher is better. */
2777
+ sharpeRatio: number | null;
2778
+ /** Annualized Sharpe Ratio (sharpeRatio × √365), null if unsafe. Higher is better. */
2779
+ annualizedSharpeRatio: number | null;
2780
+ /** Certainty Ratio (avgWin / |avgLoss|), null if unsafe. Higher is better. */
2781
+ certaintyRatio: number | null;
2782
+ /** Expected yearly returns based on average trade duration and PNL, null if unsafe. Higher is better. */
2783
+ expectedYearlyReturns: number | null;
2784
+ }
2785
+
3217
2786
  /**
3218
- * Retrieves a copy of the current global configuration.
3219
- *
3220
- * Returns a shallow copy of the current GLOBAL_CONFIG to prevent accidental mutations.
3221
- * Use this to inspect the current configuration state without modifying it.
2787
+ * Contract for walker completion events.
3222
2788
  *
3223
- * @returns {GlobalConfig} A copy of the current global configuration object
2789
+ * Emitted when all strategies have been tested and final results are available.
2790
+ * Contains complete results of the walker comparison including the best strategy.
3224
2791
  *
3225
2792
  * @example
3226
2793
  * ```typescript
3227
- * const currentConfig = getConfig();
3228
- * console.log(currentConfig.CC_SCHEDULE_AWAIT_MINUTES);
2794
+ * import { walkerCompleteSubject } from "backtest-kit";
2795
+ *
2796
+ * walkerCompleteSubject
2797
+ * .filter((event) => event.symbol === "BTCUSDT")
2798
+ * .connect((event) => {
2799
+ * console.log("Walker completed:", event.walkerName);
2800
+ * console.log("Best strategy:", event.bestStrategy);
2801
+ * console.log("Best metric:", event.bestMetric);
2802
+ * });
3229
2803
  * ```
3230
2804
  */
3231
- declare function getConfig(): {
3232
- CC_SCHEDULE_AWAIT_MINUTES: number;
3233
- CC_AVG_PRICE_CANDLES_COUNT: number;
3234
- CC_PERCENT_SLIPPAGE: number;
3235
- CC_PERCENT_FEE: number;
3236
- CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: number;
3237
- CC_MIN_STOPLOSS_DISTANCE_PERCENT: number;
3238
- CC_MAX_STOPLOSS_DISTANCE_PERCENT: number;
3239
- CC_MAX_SIGNAL_LIFETIME_MINUTES: number;
3240
- CC_MAX_SIGNAL_GENERATION_SECONDS: number;
3241
- CC_GET_CANDLES_RETRY_COUNT: number;
3242
- CC_GET_CANDLES_RETRY_DELAY_MS: number;
3243
- CC_MAX_CANDLES_PER_REQUEST: number;
3244
- CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR: number;
3245
- CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN: number;
3246
- CC_REPORT_SHOW_SIGNAL_NOTE: boolean;
3247
- CC_BREAKEVEN_THRESHOLD: number;
3248
- CC_ORDER_BOOK_TIME_OFFSET_MINUTES: number;
3249
- CC_ORDER_BOOK_MAX_DEPTH_LEVELS: number;
3250
- };
2805
+ interface WalkerCompleteContract {
2806
+ /** walkerName - Walker name */
2807
+ walkerName: WalkerName;
2808
+ /** symbol - Symbol tested */
2809
+ symbol: string;
2810
+ /** exchangeName - Exchange used */
2811
+ exchangeName: ExchangeName;
2812
+ /** frameName - Frame used */
2813
+ frameName: FrameName;
2814
+ /** metric - Metric used for optimization */
2815
+ metric: WalkerMetric;
2816
+ /** totalStrategies - Total number of strategies tested */
2817
+ totalStrategies: number;
2818
+ /** bestStrategy - Best performing strategy name */
2819
+ bestStrategy: StrategyName | null;
2820
+ /** bestMetric - Best metric value achieved */
2821
+ bestMetric: number | null;
2822
+ /** bestStats - Best strategy statistics */
2823
+ bestStats: BacktestStatisticsModel | null;
2824
+ }
2825
+
3251
2826
  /**
3252
- * Retrieves the default configuration object for the framework.
3253
- *
3254
- * Returns a reference to the default configuration with all preset values.
3255
- * Use this to see what configuration options are available and their default values.
3256
- *
3257
- * @returns {GlobalConfig} The default configuration object
3258
- *
3259
- * @example
3260
- * ```typescript
3261
- * const defaultConfig = getDefaultConfig();
3262
- * console.log(defaultConfig.CC_SCHEDULE_AWAIT_MINUTES);
3263
- * ```
2827
+ * Optimization metric for comparing strategies.
2828
+ * Higher values are always better (metric is maximized).
3264
2829
  */
3265
- declare function getDefaultConfig(): Readonly<{
3266
- CC_SCHEDULE_AWAIT_MINUTES: number;
3267
- CC_AVG_PRICE_CANDLES_COUNT: number;
3268
- CC_PERCENT_SLIPPAGE: number;
3269
- CC_PERCENT_FEE: number;
3270
- CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: number;
3271
- CC_MIN_STOPLOSS_DISTANCE_PERCENT: number;
3272
- CC_MAX_STOPLOSS_DISTANCE_PERCENT: number;
3273
- CC_MAX_SIGNAL_LIFETIME_MINUTES: number;
3274
- CC_MAX_SIGNAL_GENERATION_SECONDS: number;
3275
- CC_GET_CANDLES_RETRY_COUNT: number;
3276
- CC_GET_CANDLES_RETRY_DELAY_MS: number;
3277
- CC_MAX_CANDLES_PER_REQUEST: number;
3278
- CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR: number;
3279
- CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN: number;
3280
- CC_REPORT_SHOW_SIGNAL_NOTE: boolean;
3281
- CC_BREAKEVEN_THRESHOLD: number;
3282
- CC_ORDER_BOOK_TIME_OFFSET_MINUTES: number;
3283
- CC_ORDER_BOOK_MAX_DEPTH_LEVELS: number;
3284
- }>;
3285
- /**
3286
- * Sets custom column configurations for markdown report generation.
3287
- *
3288
- * Allows overriding default column definitions for any report type.
3289
- * All columns are validated before assignment to ensure structural correctness.
3290
- *
3291
- * @param columns - Partial column configuration object to override default column settings
3292
- * @param _unsafe - Skip column validations - required for testbed
3293
- *
3294
- * @example
3295
- * ```typescript
3296
- * setColumns({
3297
- * backtest_columns: [
3298
- * {
3299
- * key: "customId",
3300
- * label: "Custom ID",
3301
- * format: (data) => data.signal.id,
3302
- * isVisible: () => true
3303
- * }
3304
- * ],
3305
- * });
3306
- * ```
3307
- *
3308
- * @throws {Error} If column configuration is invalid
3309
- */
3310
- declare function setColumns(columns: Partial<ColumnConfig>, _unsafe?: boolean): void;
3311
- /**
3312
- * Retrieves a copy of the current column configuration for markdown report generation.
3313
- *
3314
- * Returns a shallow copy of the current COLUMN_CONFIG to prevent accidental mutations.
3315
- * Use this to inspect the current column definitions without modifying them.
3316
- *
3317
- * @returns {ColumnConfig} A copy of the current column configuration object
3318
- *
3319
- * @example
3320
- * ```typescript
3321
- * const currentColumns = getColumns();
3322
- * console.log(currentColumns.backtest_columns.length);
3323
- * ```
3324
- */
3325
- declare function getColumns(): {
3326
- backtest_columns: ColumnModel<IStrategyTickResultClosed>[];
3327
- heat_columns: ColumnModel<IHeatmapRow>[];
3328
- live_columns: ColumnModel<TickEvent>[];
3329
- partial_columns: ColumnModel<PartialEvent>[];
3330
- breakeven_columns: ColumnModel<BreakevenEvent>[];
3331
- performance_columns: ColumnModel<MetricStats>[];
3332
- risk_columns: ColumnModel<RiskEvent>[];
3333
- schedule_columns: ColumnModel<ScheduledEvent>[];
3334
- walker_pnl_columns: ColumnModel<SignalData$1>[];
3335
- walker_strategy_columns: ColumnModel<IStrategyResult>[];
3336
- };
3337
- /**
3338
- * Retrieves the default column configuration object for markdown report generation.
3339
- *
3340
- * Returns a reference to the default column definitions with all preset values.
3341
- * Use this to see what column options are available and their default definitions.
3342
- *
3343
- * @returns {ColumnConfig} The default column configuration object
3344
- *
3345
- * @example
3346
- * ```typescript
3347
- * const defaultColumns = getDefaultColumns();
3348
- * console.log(defaultColumns.backtest_columns);
3349
- * ```
3350
- */
3351
- declare function getDefaultColumns(): Readonly<{
3352
- backtest_columns: ColumnModel<IStrategyTickResultClosed>[];
3353
- heat_columns: ColumnModel<IHeatmapRow>[];
3354
- live_columns: ColumnModel<TickEvent>[];
3355
- partial_columns: ColumnModel<PartialEvent>[];
3356
- breakeven_columns: ColumnModel<BreakevenEvent>[];
3357
- performance_columns: ColumnModel<MetricStats>[];
3358
- risk_columns: ColumnModel<RiskEvent>[];
3359
- schedule_columns: ColumnModel<ScheduledEvent>[];
3360
- walker_pnl_columns: ColumnModel<SignalData$1>[];
3361
- walker_strategy_columns: ColumnModel<IStrategyResult>[];
3362
- }>;
3363
-
3364
- /**
3365
- * Statistical data calculated from backtest results.
3366
- *
3367
- * All numeric values are null if calculation is unsafe (NaN, Infinity, etc).
3368
- * Provides comprehensive metrics for strategy performance analysis.
3369
- *
3370
- * @example
3371
- * ```typescript
3372
- * const stats = await Backtest.getData("my-strategy");
3373
- *
3374
- * console.log(`Total signals: ${stats.totalSignals}`);
3375
- * console.log(`Win rate: ${stats.winRate}%`);
3376
- * console.log(`Sharpe Ratio: ${stats.sharpeRatio}`);
3377
- *
3378
- * // Access raw signal data
3379
- * stats.signalList.forEach(signal => {
3380
- * console.log(`Signal ${signal.signal.id}: ${signal.pnl.pnlPercentage}%`);
3381
- * });
3382
- * ```
3383
- */
3384
- interface BacktestStatisticsModel {
3385
- /** Array of all closed signals with full details (price, PNL, timestamps, etc.) */
3386
- signalList: IStrategyTickResultClosed[];
3387
- /** Total number of closed signals */
3388
- totalSignals: number;
3389
- /** Number of winning signals (PNL > 0) */
3390
- winCount: number;
3391
- /** Number of losing signals (PNL < 0) */
3392
- lossCount: number;
3393
- /** Win rate as percentage (0-100), null if unsafe. Higher is better. */
3394
- winRate: number | null;
3395
- /** Average PNL per signal as percentage, null if unsafe. Higher is better. */
3396
- avgPnl: number | null;
3397
- /** Cumulative PNL across all signals as percentage, null if unsafe. Higher is better. */
3398
- totalPnl: number | null;
3399
- /** Standard deviation of returns (volatility metric), null if unsafe. Lower is better. */
3400
- stdDev: number | null;
3401
- /** Sharpe Ratio (risk-adjusted return = avgPnl / stdDev), null if unsafe. Higher is better. */
3402
- sharpeRatio: number | null;
3403
- /** Annualized Sharpe Ratio (sharpeRatio × √365), null if unsafe. Higher is better. */
3404
- annualizedSharpeRatio: number | null;
3405
- /** Certainty Ratio (avgWin / |avgLoss|), null if unsafe. Higher is better. */
3406
- certaintyRatio: number | null;
3407
- /** Expected yearly returns based on average trade duration and PNL, null if unsafe. Higher is better. */
3408
- expectedYearlyReturns: number | null;
3409
- }
3410
-
3411
- /**
3412
- * Contract for walker completion events.
3413
- *
3414
- * Emitted when all strategies have been tested and final results are available.
3415
- * Contains complete results of the walker comparison including the best strategy.
3416
- *
3417
- * @example
3418
- * ```typescript
3419
- * import { walkerCompleteSubject } from "backtest-kit";
3420
- *
3421
- * walkerCompleteSubject
3422
- * .filter((event) => event.symbol === "BTCUSDT")
3423
- * .connect((event) => {
3424
- * console.log("Walker completed:", event.walkerName);
3425
- * console.log("Best strategy:", event.bestStrategy);
3426
- * console.log("Best metric:", event.bestMetric);
3427
- * });
3428
- * ```
3429
- */
3430
- interface WalkerCompleteContract {
3431
- /** walkerName - Walker name */
3432
- walkerName: WalkerName;
3433
- /** symbol - Symbol tested */
3434
- symbol: string;
3435
- /** exchangeName - Exchange used */
3436
- exchangeName: ExchangeName;
3437
- /** frameName - Frame used */
3438
- frameName: FrameName;
3439
- /** metric - Metric used for optimization */
3440
- metric: WalkerMetric;
3441
- /** totalStrategies - Total number of strategies tested */
3442
- totalStrategies: number;
3443
- /** bestStrategy - Best performing strategy name */
3444
- bestStrategy: StrategyName | null;
3445
- /** bestMetric - Best metric value achieved */
3446
- bestMetric: number | null;
3447
- /** bestStats - Best strategy statistics */
3448
- bestStats: BacktestStatisticsModel | null;
3449
- }
3450
-
3451
- /**
3452
- * Optimization metric for comparing strategies.
3453
- * Higher values are always better (metric is maximized).
3454
- */
3455
- type WalkerMetric = "sharpeRatio" | "annualizedSharpeRatio" | "winRate" | "totalPnl" | "certaintyRatio" | "avgPnl" | "expectedYearlyReturns";
2830
+ type WalkerMetric = "sharpeRatio" | "annualizedSharpeRatio" | "winRate" | "totalPnl" | "certaintyRatio" | "avgPnl" | "expectedYearlyReturns";
3456
2831
  /**
3457
2832
  * Walker schema registered via addWalker().
3458
2833
  * Defines A/B testing configuration for multiple strategies.
@@ -4129,6 +3504,766 @@ interface IOptimizer {
4129
3504
  */
4130
3505
  type OptimizerName = string;
4131
3506
 
3507
+ /**
3508
+ * Retrieves a registered strategy schema by name.
3509
+ *
3510
+ * @param strategyName - Unique strategy identifier
3511
+ * @returns The strategy schema configuration object
3512
+ * @throws Error if strategy is not registered
3513
+ *
3514
+ * @example
3515
+ * ```typescript
3516
+ * const strategy = getStrategy("my-strategy");
3517
+ * console.log(strategy.interval); // "5m"
3518
+ * console.log(strategy.getSignal); // async function
3519
+ * ```
3520
+ */
3521
+ declare function getStrategy(strategyName: StrategyName): IStrategySchema;
3522
+ /**
3523
+ * Retrieves a registered exchange schema by name.
3524
+ *
3525
+ * @param exchangeName - Unique exchange identifier
3526
+ * @returns The exchange schema configuration object
3527
+ * @throws Error if exchange is not registered
3528
+ *
3529
+ * @example
3530
+ * ```typescript
3531
+ * const exchange = getExchange("binance");
3532
+ * console.log(exchange.getCandles); // async function
3533
+ * console.log(exchange.formatPrice); // async function
3534
+ * ```
3535
+ */
3536
+ declare function getExchange(exchangeName: ExchangeName): IExchangeSchema;
3537
+ /**
3538
+ * Retrieves a registered frame schema by name.
3539
+ *
3540
+ * @param frameName - Unique frame identifier
3541
+ * @returns The frame schema configuration object
3542
+ * @throws Error if frame is not registered
3543
+ *
3544
+ * @example
3545
+ * ```typescript
3546
+ * const frame = getFrame("1d-backtest");
3547
+ * console.log(frame.interval); // "1m"
3548
+ * console.log(frame.startDate); // Date object
3549
+ * console.log(frame.endDate); // Date object
3550
+ * ```
3551
+ */
3552
+ declare function getFrame(frameName: FrameName): IFrameSchema;
3553
+ /**
3554
+ * Retrieves a registered walker schema by name.
3555
+ *
3556
+ * @param walkerName - Unique walker identifier
3557
+ * @returns The walker schema configuration object
3558
+ * @throws Error if walker is not registered
3559
+ *
3560
+ * @example
3561
+ * ```typescript
3562
+ * const walker = getWalker("llm-prompt-optimizer");
3563
+ * console.log(walker.exchangeName); // "binance"
3564
+ * console.log(walker.frameName); // "1d-backtest"
3565
+ * console.log(walker.strategies); // ["my-strategy-v1", "my-strategy-v2"]
3566
+ * console.log(walker.metric); // "sharpeRatio"
3567
+ * ```
3568
+ */
3569
+ declare function getWalker(walkerName: WalkerName): IWalkerSchema;
3570
+ /**
3571
+ * Retrieves a registered sizing schema by name.
3572
+ *
3573
+ * @param sizingName - Unique sizing identifier
3574
+ * @returns The sizing schema configuration object
3575
+ * @throws Error if sizing is not registered
3576
+ *
3577
+ * @example
3578
+ * ```typescript
3579
+ * const sizing = getSizing("conservative");
3580
+ * console.log(sizing.method); // "fixed-percentage"
3581
+ * console.log(sizing.riskPercentage); // 1
3582
+ * console.log(sizing.maxPositionPercentage); // 10
3583
+ * ```
3584
+ */
3585
+ declare function getSizing(sizingName: SizingName): ISizingSchema;
3586
+ /**
3587
+ * Retrieves a registered risk schema by name.
3588
+ *
3589
+ * @param riskName - Unique risk identifier
3590
+ * @returns The risk schema configuration object
3591
+ * @throws Error if risk is not registered
3592
+ *
3593
+ * @example
3594
+ * ```typescript
3595
+ * const risk = getRisk("conservative");
3596
+ * console.log(risk.maxConcurrentPositions); // 5
3597
+ * console.log(risk.validations); // Array of validation functions
3598
+ * ```
3599
+ */
3600
+ declare function getRisk(riskName: RiskName): IRiskSchema;
3601
+ /**
3602
+ * Retrieves a registered optimizer schema by name.
3603
+ *
3604
+ * @param optimizerName - Unique optimizer identifier
3605
+ * @returns The optimizer schema configuration object
3606
+ * @throws Error if optimizer is not registered
3607
+ *
3608
+ * @example
3609
+ * ```typescript
3610
+ * const optimizer = getOptimizer("llm-strategy-generator");
3611
+ * console.log(optimizer.rangeTrain); // Array of training ranges
3612
+ * console.log(optimizer.rangeTest); // Testing range
3613
+ * console.log(optimizer.source); // Array of data sources
3614
+ * console.log(optimizer.getPrompt); // async function
3615
+ * ```
3616
+ */
3617
+ declare function getOptimizer(optimizerName: OptimizerName): IOptimizerSchema;
3618
+ /**
3619
+ * Retrieves a registered action schema by name.
3620
+ *
3621
+ * @param actionName - Unique action identifier
3622
+ * @returns The action schema configuration object
3623
+ * @throws Error if action is not registered
3624
+ *
3625
+ * @example
3626
+ * ```typescript
3627
+ * const action = getAction("telegram-notifier");
3628
+ * console.log(action.handler); // Class constructor or object
3629
+ * console.log(action.callbacks); // Optional lifecycle callbacks
3630
+ * ```
3631
+ */
3632
+ declare function getAction(actionName: ActionName): IActionSchema;
3633
+
3634
+ /**
3635
+ * Stops the strategy from generating new signals.
3636
+ *
3637
+ * Sets internal flag to prevent strategy from opening new signals.
3638
+ * Current active signal (if any) will complete normally.
3639
+ * Backtest/Live mode will stop at the next safe point (idle state or after signal closes).
3640
+ *
3641
+ * Automatically detects backtest/live mode from execution context.
3642
+ *
3643
+ * @param symbol - Trading pair symbol
3644
+ * @param strategyName - Strategy name to stop
3645
+ * @returns Promise that resolves when stop flag is set
3646
+ *
3647
+ * @example
3648
+ * ```typescript
3649
+ * import { stop } from "backtest-kit";
3650
+ *
3651
+ * // Stop strategy after some condition
3652
+ * await stop("BTCUSDT", "my-strategy");
3653
+ * ```
3654
+ */
3655
+ declare function stop(symbol: string): Promise<void>;
3656
+ /**
3657
+ * Cancels the scheduled signal without stopping the strategy.
3658
+ *
3659
+ * Clears the scheduled signal (waiting for priceOpen activation).
3660
+ * Does NOT affect active pending signals or strategy operation.
3661
+ * Does NOT set stop flag - strategy can continue generating new signals.
3662
+ *
3663
+ * Automatically detects backtest/live mode from execution context.
3664
+ *
3665
+ * @param symbol - Trading pair symbol
3666
+ * @param strategyName - Strategy name
3667
+ * @param cancelId - Optional cancellation ID for tracking user-initiated cancellations
3668
+ * @returns Promise that resolves when scheduled signal is cancelled
3669
+ *
3670
+ * @example
3671
+ * ```typescript
3672
+ * import { cancel } from "backtest-kit";
3673
+ *
3674
+ * // Cancel scheduled signal with custom ID
3675
+ * await cancel("BTCUSDT", "my-strategy", "manual-cancel-001");
3676
+ * ```
3677
+ */
3678
+ declare function cancel(symbol: string, cancelId?: string): Promise<void>;
3679
+ /**
3680
+ * Executes partial close at profit level (moving toward TP).
3681
+ *
3682
+ * Closes a percentage of the active pending position at profit.
3683
+ * Price must be moving toward take profit (in profit direction).
3684
+ *
3685
+ * Automatically detects backtest/live mode from execution context.
3686
+ *
3687
+ * @param symbol - Trading pair symbol
3688
+ * @param percentToClose - Percentage of position to close (0-100, absolute value)
3689
+ * @returns Promise<boolean> - true if partial close executed, false if skipped
3690
+ *
3691
+ * @throws Error if currentPrice is not in profit direction:
3692
+ * - LONG: currentPrice must be > priceOpen
3693
+ * - SHORT: currentPrice must be < priceOpen
3694
+ *
3695
+ * @example
3696
+ * ```typescript
3697
+ * import { partialProfit } from "backtest-kit";
3698
+ *
3699
+ * // Close 30% of LONG position at profit
3700
+ * const success = await partialProfit("BTCUSDT", 30);
3701
+ * if (success) {
3702
+ * console.log('Partial profit executed');
3703
+ * }
3704
+ * ```
3705
+ */
3706
+ declare function partialProfit(symbol: string, percentToClose: number): Promise<boolean>;
3707
+ /**
3708
+ * Executes partial close at loss level (moving toward SL).
3709
+ *
3710
+ * Closes a percentage of the active pending position at loss.
3711
+ * Price must be moving toward stop loss (in loss direction).
3712
+ *
3713
+ * Automatically detects backtest/live mode from execution context.
3714
+ *
3715
+ * @param symbol - Trading pair symbol
3716
+ * @param percentToClose - Percentage of position to close (0-100, absolute value)
3717
+ * @returns Promise<boolean> - true if partial close executed, false if skipped
3718
+ *
3719
+ * @throws Error if currentPrice is not in loss direction:
3720
+ * - LONG: currentPrice must be < priceOpen
3721
+ * - SHORT: currentPrice must be > priceOpen
3722
+ *
3723
+ * @example
3724
+ * ```typescript
3725
+ * import { partialLoss } from "backtest-kit";
3726
+ *
3727
+ * // Close 40% of LONG position at loss
3728
+ * const success = await partialLoss("BTCUSDT", 40);
3729
+ * if (success) {
3730
+ * console.log('Partial loss executed');
3731
+ * }
3732
+ * ```
3733
+ */
3734
+ declare function partialLoss(symbol: string, percentToClose: number): Promise<boolean>;
3735
+ /**
3736
+ * Adjusts the trailing stop-loss distance for an active pending signal.
3737
+ *
3738
+ * CRITICAL: Always calculates from ORIGINAL SL, not from current trailing SL.
3739
+ * This prevents error accumulation on repeated calls.
3740
+ * Larger percentShift ABSORBS smaller one (updates only towards better protection).
3741
+ *
3742
+ * Updates the stop-loss distance by a percentage adjustment relative to the ORIGINAL SL distance.
3743
+ * Negative percentShift tightens the SL (reduces distance, moves closer to entry).
3744
+ * Positive percentShift loosens the SL (increases distance, moves away from entry).
3745
+ *
3746
+ * Absorption behavior:
3747
+ * - First call: sets trailing SL unconditionally
3748
+ * - Subsequent calls: updates only if new SL is BETTER (protects more profit)
3749
+ * - For LONG: only accepts HIGHER SL (never moves down, closer to entry wins)
3750
+ * - For SHORT: only accepts LOWER SL (never moves up, closer to entry wins)
3751
+ *
3752
+ * Automatically detects backtest/live mode from execution context.
3753
+ *
3754
+ * @param symbol - Trading pair symbol
3755
+ * @param percentShift - Percentage adjustment to ORIGINAL SL distance (-100 to 100)
3756
+ * @param currentPrice - Current market price to check for intrusion
3757
+ * @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected (absorption/intrusion/conflict)
3758
+ *
3759
+ * @example
3760
+ * ```typescript
3761
+ * import { trailingStop } from "backtest-kit";
3762
+ *
3763
+ * // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
3764
+ *
3765
+ * // First call: tighten by 5%
3766
+ * const success1 = await trailingStop("BTCUSDT", -5, 102);
3767
+ * // success1 = true, newDistance = 10% - 5% = 5%, newSL = 95
3768
+ *
3769
+ * // Second call: try weaker protection (smaller percentShift)
3770
+ * const success2 = await trailingStop("BTCUSDT", -3, 102);
3771
+ * // success2 = false (SKIPPED: newSL=97 < 95, worse protection, larger % absorbs smaller)
3772
+ *
3773
+ * // Third call: stronger protection (larger percentShift)
3774
+ * const success3 = await trailingStop("BTCUSDT", -7, 102);
3775
+ * // success3 = true (ACCEPTED: newDistance = 10% - 7% = 3%, newSL = 97 > 95, better protection)
3776
+ * ```
3777
+ */
3778
+ declare function trailingStop(symbol: string, percentShift: number, currentPrice: number): Promise<boolean>;
3779
+ /**
3780
+ * Adjusts the trailing take-profit distance for an active pending signal.
3781
+ *
3782
+ * CRITICAL: Always calculates from ORIGINAL TP, not from current trailing TP.
3783
+ * This prevents error accumulation on repeated calls.
3784
+ * Larger percentShift ABSORBS smaller one (updates only towards more conservative TP).
3785
+ *
3786
+ * Updates the take-profit distance by a percentage adjustment relative to the ORIGINAL TP distance.
3787
+ * Negative percentShift brings TP closer to entry (more conservative).
3788
+ * Positive percentShift moves TP further from entry (more aggressive).
3789
+ *
3790
+ * Absorption behavior:
3791
+ * - First call: sets trailing TP unconditionally
3792
+ * - Subsequent calls: updates only if new TP is MORE CONSERVATIVE (closer to entry)
3793
+ * - For LONG: only accepts LOWER TP (never moves up, closer to entry wins)
3794
+ * - For SHORT: only accepts HIGHER TP (never moves down, closer to entry wins)
3795
+ *
3796
+ * Automatically detects backtest/live mode from execution context.
3797
+ *
3798
+ * @param symbol - Trading pair symbol
3799
+ * @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
3800
+ * @param currentPrice - Current market price to check for intrusion
3801
+ * @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected (absorption/intrusion/conflict)
3802
+ *
3803
+ * @example
3804
+ * ```typescript
3805
+ * import { trailingTake } from "backtest-kit";
3806
+ *
3807
+ * // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
3808
+ *
3809
+ * // First call: bring TP closer by 3%
3810
+ * const success1 = await trailingTake("BTCUSDT", -3, 102);
3811
+ * // success1 = true, newDistance = 10% - 3% = 7%, newTP = 107
3812
+ *
3813
+ * // Second call: try to move TP further (less conservative)
3814
+ * const success2 = await trailingTake("BTCUSDT", 2, 102);
3815
+ * // success2 = false (SKIPPED: newTP=112 > 107, less conservative, larger % absorbs smaller)
3816
+ *
3817
+ * // Third call: even more conservative
3818
+ * const success3 = await trailingTake("BTCUSDT", -5, 102);
3819
+ * // success3 = true (ACCEPTED: newDistance = 10% - 5% = 5%, newTP = 105 < 107, more conservative)
3820
+ * ```
3821
+ */
3822
+ declare function trailingTake(symbol: string, percentShift: number, currentPrice: number): Promise<boolean>;
3823
+ /**
3824
+ * Moves stop-loss to breakeven when price reaches threshold.
3825
+ *
3826
+ * Moves SL to entry price (zero-risk position) when current price has moved
3827
+ * far enough in profit direction to cover transaction costs.
3828
+ * Threshold is calculated as: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2
3829
+ *
3830
+ * Automatically detects backtest/live mode from execution context.
3831
+ * Automatically fetches current price via getAveragePrice.
3832
+ *
3833
+ * @param symbol - Trading pair symbol
3834
+ * @returns Promise<boolean> - true if breakeven was set, false if conditions not met
3835
+ *
3836
+ * @example
3837
+ * ```typescript
3838
+ * import { breakeven } from "backtest-kit";
3839
+ *
3840
+ * // LONG: entry=100, slippage=0.1%, fee=0.1%, threshold=0.4%
3841
+ * // Try to move SL to breakeven (activates when price >= 100.4)
3842
+ * const moved = await breakeven("BTCUSDT");
3843
+ * if (moved) {
3844
+ * console.log("Position moved to breakeven!");
3845
+ * }
3846
+ * ```
3847
+ */
3848
+ declare function breakeven(symbol: string): Promise<boolean>;
3849
+
3850
+ /**
3851
+ * Unified breakeven event data for report generation.
3852
+ * Contains all information about when signals reached breakeven.
3853
+ */
3854
+ interface BreakevenEvent {
3855
+ /** Event timestamp in milliseconds */
3856
+ timestamp: number;
3857
+ /** Trading pair symbol */
3858
+ symbol: string;
3859
+ /** Strategy name */
3860
+ strategyName: StrategyName;
3861
+ /** Signal ID */
3862
+ signalId: string;
3863
+ /** Position type */
3864
+ position: string;
3865
+ /** Current market price when breakeven was reached */
3866
+ currentPrice: number;
3867
+ /** Entry price (breakeven level) */
3868
+ priceOpen: number;
3869
+ /** Take profit target price */
3870
+ priceTakeProfit?: number;
3871
+ /** Stop loss exit price */
3872
+ priceStopLoss?: number;
3873
+ /** Original take profit price set at signal creation */
3874
+ originalPriceTakeProfit?: number;
3875
+ /** Original stop loss price set at signal creation */
3876
+ originalPriceStopLoss?: number;
3877
+ /** Total executed percentage from partial closes */
3878
+ totalExecuted?: number;
3879
+ /** Human-readable description of signal reason */
3880
+ note?: string;
3881
+ /** True if backtest mode, false if live mode */
3882
+ backtest: boolean;
3883
+ }
3884
+ /**
3885
+ * Statistical data calculated from breakeven events.
3886
+ *
3887
+ * Provides metrics for breakeven milestone tracking.
3888
+ *
3889
+ * @example
3890
+ * ```typescript
3891
+ * const stats = await Breakeven.getData("BTCUSDT", "my-strategy");
3892
+ *
3893
+ * console.log(`Total breakeven events: ${stats.totalEvents}`);
3894
+ * console.log(`Average threshold: ${stats.averageThreshold}%`);
3895
+ * ```
3896
+ */
3897
+ interface BreakevenStatisticsModel {
3898
+ /** Array of all breakeven events with full details */
3899
+ eventList: BreakevenEvent[];
3900
+ /** Total number of breakeven events */
3901
+ totalEvents: number;
3902
+ }
3903
+
3904
+ declare const GLOBAL_CONFIG: {
3905
+ /**
3906
+ * Time to wait for scheduled signal to activate (in minutes)
3907
+ * If signal does not activate within this time, it will be cancelled.
3908
+ */
3909
+ CC_SCHEDULE_AWAIT_MINUTES: number;
3910
+ /**
3911
+ * Number of candles to use for average price calculation (VWAP)
3912
+ * Default: 5 candles (last 5 minutes when using 1m interval)
3913
+ */
3914
+ CC_AVG_PRICE_CANDLES_COUNT: number;
3915
+ /**
3916
+ * Slippage percentage applied to entry and exit prices.
3917
+ * Simulates market impact and order book depth.
3918
+ * Applied twice (entry and exit) for realistic execution simulation.
3919
+ * Default: 0.1% per transaction
3920
+ */
3921
+ CC_PERCENT_SLIPPAGE: number;
3922
+ /**
3923
+ * Fee percentage charged per transaction.
3924
+ * Applied twice (entry and exit) for total fee calculation.
3925
+ * Default: 0.1% per transaction (total 0.2%)
3926
+ */
3927
+ CC_PERCENT_FEE: number;
3928
+ /**
3929
+ * Minimum TakeProfit distance from priceOpen (percentage)
3930
+ * Must be greater than (slippage + fees) to ensure profitable trades
3931
+ *
3932
+ * Calculation:
3933
+ * - Slippage effect: ~0.2% (0.1% × 2 transactions)
3934
+ * - Fees: 0.2% (0.1% × 2 transactions)
3935
+ * - Minimum profit buffer: 0.1%
3936
+ * - Total: 0.5%
3937
+ *
3938
+ * Default: 0.5% (covers all costs + minimum profit margin)
3939
+ */
3940
+ CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: number;
3941
+ /**
3942
+ * Minimum StopLoss distance from priceOpen (percentage)
3943
+ * Prevents signals from being immediately stopped out due to price volatility
3944
+ * Default: 0.5% (buffer to avoid instant stop loss on normal market fluctuations)
3945
+ */
3946
+ CC_MIN_STOPLOSS_DISTANCE_PERCENT: number;
3947
+ /**
3948
+ * Maximum StopLoss distance from priceOpen (percentage)
3949
+ * Prevents catastrophic losses from extreme StopLoss values
3950
+ * Default: 20% (one signal cannot lose more than 20% of position)
3951
+ */
3952
+ CC_MAX_STOPLOSS_DISTANCE_PERCENT: number;
3953
+ /**
3954
+ * Maximum signal lifetime in minutes
3955
+ * Prevents eternal signals that block risk limits for weeks/months
3956
+ * Default: 1440 minutes (1 day)
3957
+ */
3958
+ CC_MAX_SIGNAL_LIFETIME_MINUTES: number;
3959
+ /**
3960
+ * Maximum time allowed for signal generation (in seconds).
3961
+ * Prevents long-running or stuck signal generation routines from blocking
3962
+ * execution or consuming resources indefinitely. If generation exceeds this
3963
+ * threshold the attempt should be aborted, logged and optionally retried.
3964
+ *
3965
+ * Default: 180 seconds (3 minutes)
3966
+ */
3967
+ CC_MAX_SIGNAL_GENERATION_SECONDS: number;
3968
+ /**
3969
+ * Number of retries for getCandles function
3970
+ * Default: 3 retries
3971
+ */
3972
+ CC_GET_CANDLES_RETRY_COUNT: number;
3973
+ /**
3974
+ * Delay between retries for getCandles function (in milliseconds)
3975
+ * Default: 5000 ms (5 seconds)
3976
+ */
3977
+ CC_GET_CANDLES_RETRY_DELAY_MS: number;
3978
+ /**
3979
+ * Maximum number of candles to request per single API call.
3980
+ * If a request exceeds this limit, data will be fetched using pagination.
3981
+ * Default: 1000 candles per request
3982
+ */
3983
+ CC_MAX_CANDLES_PER_REQUEST: number;
3984
+ /**
3985
+ * Maximum allowed deviation factor for price anomaly detection.
3986
+ * Price should not be more than this factor lower than reference price.
3987
+ *
3988
+ * Reasoning:
3989
+ * - Incomplete candles from Binance API typically have prices near 0 (e.g., $0.01-1)
3990
+ * - Normal BTC price ranges: $20,000-100,000
3991
+ * - Factor 1000 catches prices below $20-100 when median is $20,000-100,000
3992
+ * - Factor 100 would be too permissive (allows $200 when median is $20,000)
3993
+ * - Factor 10000 might be too strict for low-cap altcoins
3994
+ *
3995
+ * Example: BTC at $50,000 median → threshold $50 (catches $0.01-1 anomalies)
3996
+ */
3997
+ CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR: number;
3998
+ /**
3999
+ * Minimum number of candles required for reliable median calculation.
4000
+ * Below this threshold, use simple average instead of median.
4001
+ *
4002
+ * Reasoning:
4003
+ * - Each candle provides 4 price points (OHLC)
4004
+ * - 5 candles = 20 price points, sufficient for robust median calculation
4005
+ * - Below 5 candles, single anomaly can heavily skew median
4006
+ * - Statistical rule of thumb: minimum 7-10 data points for median stability
4007
+ * - Average is more stable than median for small datasets (n < 20)
4008
+ *
4009
+ * Example: 3 candles = 12 points (use average), 5 candles = 20 points (use median)
4010
+ */
4011
+ CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN: number;
4012
+ /**
4013
+ * Controls visibility of signal notes in markdown report tables.
4014
+ * When enabled, the "Note" column will be displayed in all markdown reports
4015
+ * (backtest, live, schedule, risk, etc.)
4016
+ *
4017
+ * Default: false (notes are hidden to reduce table width and improve readability)
4018
+ */
4019
+ CC_REPORT_SHOW_SIGNAL_NOTE: boolean;
4020
+ /**
4021
+ * Breakeven threshold percentage - minimum profit distance from entry to enable breakeven.
4022
+ * When price moves this percentage in profit direction, stop-loss can be moved to entry (breakeven).
4023
+ *
4024
+ * Calculation:
4025
+ * - Slippage effect: ~0.2% (0.1% × 2 transactions)
4026
+ * - Fees: 0.2% (0.1% × 2 transactions)
4027
+ * - Total: 0.4%
4028
+ * - Added buffer: 0.2%
4029
+ * - Overall: 0.6%
4030
+ *
4031
+ * Default: 0.2% (additional buffer above costs to ensure no loss when moving to breakeven)
4032
+ */
4033
+ CC_BREAKEVEN_THRESHOLD: number;
4034
+ /**
4035
+ * Time offset in minutes for order book fetching.
4036
+ * Subtracts this amount from the current time when fetching order book data.
4037
+ * This helps get a more stable snapshot of the order book by avoiding real-time volatility.
4038
+ *
4039
+ * Default: 10 minutes
4040
+ */
4041
+ CC_ORDER_BOOK_TIME_OFFSET_MINUTES: number;
4042
+ /**
4043
+ * Maximum depth levels for order book fetching.
4044
+ * Specifies how many price levels to fetch from both bids and asks.
4045
+ *
4046
+ * Default: 20 levels
4047
+ */
4048
+ CC_ORDER_BOOK_MAX_DEPTH_LEVELS: number;
4049
+ };
4050
+ /**
4051
+ * Type for global configuration object.
4052
+ */
4053
+ type GlobalConfig = typeof GLOBAL_CONFIG;
4054
+
4055
+ /**
4056
+ * Mapping of available table/markdown reports to their column definitions.
4057
+ *
4058
+ * Each property references a column definition object imported from
4059
+ * `src/assets/*.columns`. These are used by markdown/report generators
4060
+ * (backtest, live, schedule, risk, heat, performance, partial, walker).
4061
+ */
4062
+ declare const COLUMN_CONFIG: {
4063
+ /** Columns used in backtest markdown tables and reports */
4064
+ backtest_columns: ColumnModel<IStrategyTickResultClosed>[];
4065
+ /** Columns used by heatmap / heat reports */
4066
+ heat_columns: ColumnModel<IHeatmapRow>[];
4067
+ /** Columns for live trading reports and logs */
4068
+ live_columns: ColumnModel<TickEvent>[];
4069
+ /** Columns for partial-results / incremental reports */
4070
+ partial_columns: ColumnModel<PartialEvent>[];
4071
+ /** Columns for breakeven protection events */
4072
+ breakeven_columns: ColumnModel<BreakevenEvent>[];
4073
+ /** Columns for performance summary reports */
4074
+ performance_columns: ColumnModel<MetricStats>[];
4075
+ /** Columns for risk-related reports */
4076
+ risk_columns: ColumnModel<RiskEvent>[];
4077
+ /** Columns for scheduled report output */
4078
+ schedule_columns: ColumnModel<ScheduledEvent>[];
4079
+ /** Walker: PnL summary columns */
4080
+ walker_pnl_columns: ColumnModel<SignalData$1>[];
4081
+ /** Walker: strategy-level summary columns */
4082
+ walker_strategy_columns: ColumnModel<IStrategyResult>[];
4083
+ };
4084
+ /**
4085
+ * Type for the column configuration object.
4086
+ */
4087
+ type ColumnConfig = typeof COLUMN_CONFIG;
4088
+
4089
+ /**
4090
+ * Sets custom logger implementation for the framework.
4091
+ *
4092
+ * All log messages from internal services will be forwarded to the provided logger
4093
+ * with automatic context injection (strategyName, exchangeName, symbol, etc.).
4094
+ *
4095
+ * @param logger - Custom logger implementing ILogger interface
4096
+ *
4097
+ * @example
4098
+ * ```typescript
4099
+ * setLogger({
4100
+ * log: (topic, ...args) => console.log(topic, args),
4101
+ * debug: (topic, ...args) => console.debug(topic, args),
4102
+ * info: (topic, ...args) => console.info(topic, args),
4103
+ * });
4104
+ * ```
4105
+ */
4106
+ declare function setLogger(logger: ILogger): void;
4107
+ /**
4108
+ * Sets global configuration parameters for the framework.
4109
+ * @param config - Partial configuration object to override default settings
4110
+ * @param _unsafe - Skip config validations - required for testbed
4111
+ *
4112
+ * @example
4113
+ * ```typescript
4114
+ * setConfig({
4115
+ * CC_SCHEDULE_AWAIT_MINUTES: 90,
4116
+ * });
4117
+ * ```
4118
+ */
4119
+ declare function setConfig(config: Partial<GlobalConfig>, _unsafe?: boolean): void;
4120
+ /**
4121
+ * Retrieves a copy of the current global configuration.
4122
+ *
4123
+ * Returns a shallow copy of the current GLOBAL_CONFIG to prevent accidental mutations.
4124
+ * Use this to inspect the current configuration state without modifying it.
4125
+ *
4126
+ * @returns {GlobalConfig} A copy of the current global configuration object
4127
+ *
4128
+ * @example
4129
+ * ```typescript
4130
+ * const currentConfig = getConfig();
4131
+ * console.log(currentConfig.CC_SCHEDULE_AWAIT_MINUTES);
4132
+ * ```
4133
+ */
4134
+ declare function getConfig(): {
4135
+ CC_SCHEDULE_AWAIT_MINUTES: number;
4136
+ CC_AVG_PRICE_CANDLES_COUNT: number;
4137
+ CC_PERCENT_SLIPPAGE: number;
4138
+ CC_PERCENT_FEE: number;
4139
+ CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: number;
4140
+ CC_MIN_STOPLOSS_DISTANCE_PERCENT: number;
4141
+ CC_MAX_STOPLOSS_DISTANCE_PERCENT: number;
4142
+ CC_MAX_SIGNAL_LIFETIME_MINUTES: number;
4143
+ CC_MAX_SIGNAL_GENERATION_SECONDS: number;
4144
+ CC_GET_CANDLES_RETRY_COUNT: number;
4145
+ CC_GET_CANDLES_RETRY_DELAY_MS: number;
4146
+ CC_MAX_CANDLES_PER_REQUEST: number;
4147
+ CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR: number;
4148
+ CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN: number;
4149
+ CC_REPORT_SHOW_SIGNAL_NOTE: boolean;
4150
+ CC_BREAKEVEN_THRESHOLD: number;
4151
+ CC_ORDER_BOOK_TIME_OFFSET_MINUTES: number;
4152
+ CC_ORDER_BOOK_MAX_DEPTH_LEVELS: number;
4153
+ };
4154
+ /**
4155
+ * Retrieves the default configuration object for the framework.
4156
+ *
4157
+ * Returns a reference to the default configuration with all preset values.
4158
+ * Use this to see what configuration options are available and their default values.
4159
+ *
4160
+ * @returns {GlobalConfig} The default configuration object
4161
+ *
4162
+ * @example
4163
+ * ```typescript
4164
+ * const defaultConfig = getDefaultConfig();
4165
+ * console.log(defaultConfig.CC_SCHEDULE_AWAIT_MINUTES);
4166
+ * ```
4167
+ */
4168
+ declare function getDefaultConfig(): Readonly<{
4169
+ CC_SCHEDULE_AWAIT_MINUTES: number;
4170
+ CC_AVG_PRICE_CANDLES_COUNT: number;
4171
+ CC_PERCENT_SLIPPAGE: number;
4172
+ CC_PERCENT_FEE: number;
4173
+ CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: number;
4174
+ CC_MIN_STOPLOSS_DISTANCE_PERCENT: number;
4175
+ CC_MAX_STOPLOSS_DISTANCE_PERCENT: number;
4176
+ CC_MAX_SIGNAL_LIFETIME_MINUTES: number;
4177
+ CC_MAX_SIGNAL_GENERATION_SECONDS: number;
4178
+ CC_GET_CANDLES_RETRY_COUNT: number;
4179
+ CC_GET_CANDLES_RETRY_DELAY_MS: number;
4180
+ CC_MAX_CANDLES_PER_REQUEST: number;
4181
+ CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR: number;
4182
+ CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN: number;
4183
+ CC_REPORT_SHOW_SIGNAL_NOTE: boolean;
4184
+ CC_BREAKEVEN_THRESHOLD: number;
4185
+ CC_ORDER_BOOK_TIME_OFFSET_MINUTES: number;
4186
+ CC_ORDER_BOOK_MAX_DEPTH_LEVELS: number;
4187
+ }>;
4188
+ /**
4189
+ * Sets custom column configurations for markdown report generation.
4190
+ *
4191
+ * Allows overriding default column definitions for any report type.
4192
+ * All columns are validated before assignment to ensure structural correctness.
4193
+ *
4194
+ * @param columns - Partial column configuration object to override default column settings
4195
+ * @param _unsafe - Skip column validations - required for testbed
4196
+ *
4197
+ * @example
4198
+ * ```typescript
4199
+ * setColumns({
4200
+ * backtest_columns: [
4201
+ * {
4202
+ * key: "customId",
4203
+ * label: "Custom ID",
4204
+ * format: (data) => data.signal.id,
4205
+ * isVisible: () => true
4206
+ * }
4207
+ * ],
4208
+ * });
4209
+ * ```
4210
+ *
4211
+ * @throws {Error} If column configuration is invalid
4212
+ */
4213
+ declare function setColumns(columns: Partial<ColumnConfig>, _unsafe?: boolean): void;
4214
+ /**
4215
+ * Retrieves a copy of the current column configuration for markdown report generation.
4216
+ *
4217
+ * Returns a shallow copy of the current COLUMN_CONFIG to prevent accidental mutations.
4218
+ * Use this to inspect the current column definitions without modifying them.
4219
+ *
4220
+ * @returns {ColumnConfig} A copy of the current column configuration object
4221
+ *
4222
+ * @example
4223
+ * ```typescript
4224
+ * const currentColumns = getColumns();
4225
+ * console.log(currentColumns.backtest_columns.length);
4226
+ * ```
4227
+ */
4228
+ declare function getColumns(): {
4229
+ backtest_columns: ColumnModel<IStrategyTickResultClosed>[];
4230
+ heat_columns: ColumnModel<IHeatmapRow>[];
4231
+ live_columns: ColumnModel<TickEvent>[];
4232
+ partial_columns: ColumnModel<PartialEvent>[];
4233
+ breakeven_columns: ColumnModel<BreakevenEvent>[];
4234
+ performance_columns: ColumnModel<MetricStats>[];
4235
+ risk_columns: ColumnModel<RiskEvent>[];
4236
+ schedule_columns: ColumnModel<ScheduledEvent>[];
4237
+ walker_pnl_columns: ColumnModel<SignalData$1>[];
4238
+ walker_strategy_columns: ColumnModel<IStrategyResult>[];
4239
+ };
4240
+ /**
4241
+ * Retrieves the default column configuration object for markdown report generation.
4242
+ *
4243
+ * Returns a reference to the default column definitions with all preset values.
4244
+ * Use this to see what column options are available and their default definitions.
4245
+ *
4246
+ * @returns {ColumnConfig} The default column configuration object
4247
+ *
4248
+ * @example
4249
+ * ```typescript
4250
+ * const defaultColumns = getDefaultColumns();
4251
+ * console.log(defaultColumns.backtest_columns);
4252
+ * ```
4253
+ */
4254
+ declare function getDefaultColumns(): Readonly<{
4255
+ backtest_columns: ColumnModel<IStrategyTickResultClosed>[];
4256
+ heat_columns: ColumnModel<IHeatmapRow>[];
4257
+ live_columns: ColumnModel<TickEvent>[];
4258
+ partial_columns: ColumnModel<PartialEvent>[];
4259
+ breakeven_columns: ColumnModel<BreakevenEvent>[];
4260
+ performance_columns: ColumnModel<MetricStats>[];
4261
+ risk_columns: ColumnModel<RiskEvent>[];
4262
+ schedule_columns: ColumnModel<ScheduledEvent>[];
4263
+ walker_pnl_columns: ColumnModel<SignalData$1>[];
4264
+ walker_strategy_columns: ColumnModel<IStrategyResult>[];
4265
+ }>;
4266
+
4132
4267
  /**
4133
4268
  * Registers a trading strategy in the framework.
4134
4269
  *
@@ -18393,4 +18528,4 @@ declare const backtest: {
18393
18528
  loggerService: LoggerService;
18394
18529
  };
18395
18530
 
18396
- export { ActionBase, Backtest, type BacktestDoneNotification, type BacktestStatisticsModel, type BootstrapNotification, Breakeven, type BreakevenContract, type BreakevenData, Cache, type CandleInterval, type ColumnConfig, type ColumnModel, Constant, type CriticalErrorNotification, type DoneContract, type EntityId, Exchange, ExecutionContextService, type FrameInterval, type GlobalConfig, Heat, type HeatmapStatisticsModel, type IBidData, type ICandleData, type IExchangeSchema, type IFrameSchema, type IHeatmapRow, type IMarkdownDumpOptions, type IOptimizerCallbacks, type IOptimizerData, type IOptimizerFetchArgs, type IOptimizerFilterArgs, type IOptimizerRange, type IOptimizerSchema, type IOptimizerSource, type IOptimizerStrategy, type IOptimizerTemplate, type IOrderBookData, type IPersistBase, type IPositionSizeATRParams, type IPositionSizeFixedPercentageParams, type IPositionSizeKellyParams, type IPublicSignalRow, type IReportDumpOptions, type IRiskActivePosition, type IRiskCheckArgs, type IRiskSchema, type IRiskValidation, type IRiskValidationFn, type IRiskValidationPayload, type IScheduledSignalCancelRow, type IScheduledSignalRow, type ISignalDto, type ISignalRow, type ISizingCalculateParams, type ISizingCalculateParamsATR, type ISizingCalculateParamsFixedPercentage, type ISizingCalculateParamsKelly, type ISizingSchema, type ISizingSchemaATR, type ISizingSchemaFixedPercentage, type ISizingSchemaKelly, type IStrategyPnL, type IStrategyResult, type IStrategySchema, type IStrategyTickResult, type IStrategyTickResultActive, type IStrategyTickResultCancelled, type IStrategyTickResultClosed, type IStrategyTickResultIdle, type IStrategyTickResultOpened, type IStrategyTickResultScheduled, type IWalkerResults, type IWalkerSchema, type IWalkerStrategyResult, type InfoErrorNotification, Live, type LiveDoneNotification, type LiveStatisticsModel, Markdown, MarkdownFileBase, MarkdownFolderBase, type MarkdownName, type MessageModel, type MessageRole, MethodContextService, type MetricStats, Notification, type NotificationModel, Optimizer, Partial$1 as Partial, type PartialData, type PartialEvent, type PartialLossContract, type PartialLossNotification, type PartialProfitContract, type PartialProfitNotification, type PartialStatisticsModel, Performance, type PerformanceContract, type PerformanceMetricType, type PerformanceStatisticsModel, PersistBase, PersistBreakevenAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, type PingContract, PositionSize, type ProgressBacktestContract, type ProgressBacktestNotification, type ProgressOptimizerContract, type ProgressWalkerContract, Report, ReportBase, type ReportName, Risk, type RiskContract, type RiskData, type RiskEvent, type RiskRejectionNotification, type RiskStatisticsModel, Schedule, type ScheduleData, type ScheduleStatisticsModel, type ScheduledEvent, type SignalCancelledNotification, type SignalClosedNotification, type SignalData, type SignalInterval, type SignalOpenedNotification, type SignalScheduledNotification, type TMarkdownBase, type TPersistBase, type TPersistBaseCtor, type TReportBase, type TickEvent, type ValidationErrorNotification, Walker, type WalkerCompleteContract, type WalkerContract, type WalkerMetric, type SignalData$1 as WalkerSignalData, type WalkerStatisticsModel, addAction, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, breakeven, cancel, dumpSignal, emitters, formatPrice, formatQuantity, get, getAveragePrice, getCandles, getColumns, getConfig, getDate, getDefaultColumns, getDefaultConfig, getMode, getOrderBook, hasTradeContext, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenBreakeven, listenBreakevenOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenPing, listenPingOnce, listenRisk, listenRiskOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideAction, overrideExchange, overrideFrame, overrideOptimizer, overrideRisk, overrideSizing, overrideStrategy, overrideWalker, partialLoss, partialProfit, roundTicks, set, setColumns, setConfig, setLogger, stop, trailingStop, trailingTake, validate };
18531
+ export { ActionBase, Backtest, type BacktestDoneNotification, type BacktestStatisticsModel, type BootstrapNotification, Breakeven, type BreakevenContract, type BreakevenData, Cache, type CandleInterval, type ColumnConfig, type ColumnModel, Constant, type CriticalErrorNotification, type DoneContract, type EntityId, Exchange, ExecutionContextService, type FrameInterval, type GlobalConfig, Heat, type HeatmapStatisticsModel, type IBidData, type ICandleData, type IExchangeSchema, type IFrameSchema, type IHeatmapRow, type IMarkdownDumpOptions, type IOptimizerCallbacks, type IOptimizerData, type IOptimizerFetchArgs, type IOptimizerFilterArgs, type IOptimizerRange, type IOptimizerSchema, type IOptimizerSource, type IOptimizerStrategy, type IOptimizerTemplate, type IOrderBookData, type IPersistBase, type IPositionSizeATRParams, type IPositionSizeFixedPercentageParams, type IPositionSizeKellyParams, type IPublicSignalRow, type IReportDumpOptions, type IRiskActivePosition, type IRiskCheckArgs, type IRiskSchema, type IRiskValidation, type IRiskValidationFn, type IRiskValidationPayload, type IScheduledSignalCancelRow, type IScheduledSignalRow, type ISignalDto, type ISignalRow, type ISizingCalculateParams, type ISizingCalculateParamsATR, type ISizingCalculateParamsFixedPercentage, type ISizingCalculateParamsKelly, type ISizingSchema, type ISizingSchemaATR, type ISizingSchemaFixedPercentage, type ISizingSchemaKelly, type IStrategyPnL, type IStrategyResult, type IStrategySchema, type IStrategyTickResult, type IStrategyTickResultActive, type IStrategyTickResultCancelled, type IStrategyTickResultClosed, type IStrategyTickResultIdle, type IStrategyTickResultOpened, type IStrategyTickResultScheduled, type IWalkerResults, type IWalkerSchema, type IWalkerStrategyResult, type InfoErrorNotification, Live, type LiveDoneNotification, type LiveStatisticsModel, Markdown, MarkdownFileBase, MarkdownFolderBase, type MarkdownName, type MessageModel, type MessageRole, MethodContextService, type MetricStats, Notification, type NotificationModel, Optimizer, Partial$1 as Partial, type PartialData, type PartialEvent, type PartialLossContract, type PartialLossNotification, type PartialProfitContract, type PartialProfitNotification, type PartialStatisticsModel, Performance, type PerformanceContract, type PerformanceMetricType, type PerformanceStatisticsModel, PersistBase, PersistBreakevenAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, type PingContract, PositionSize, type ProgressBacktestContract, type ProgressBacktestNotification, type ProgressOptimizerContract, type ProgressWalkerContract, Report, ReportBase, type ReportName, Risk, type RiskContract, type RiskData, type RiskEvent, type RiskRejectionNotification, type RiskStatisticsModel, Schedule, type ScheduleData, type ScheduleStatisticsModel, type ScheduledEvent, type SignalCancelledNotification, type SignalClosedNotification, type SignalData, type SignalInterval, type SignalOpenedNotification, type SignalScheduledNotification, type TMarkdownBase, type TPersistBase, type TPersistBaseCtor, type TReportBase, type TickEvent, type ValidationErrorNotification, Walker, type WalkerCompleteContract, type WalkerContract, type WalkerMetric, type SignalData$1 as WalkerSignalData, type WalkerStatisticsModel, addAction, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, breakeven, cancel, dumpSignal, emitters, formatPrice, formatQuantity, get, getAction, getAveragePrice, getCandles, getColumns, getConfig, getCurrentTimeframe, getDate, getDefaultColumns, getDefaultConfig, getExchange, getFrame, getMode, getOptimizer, getOrderBook, getRisk, getSizing, getStrategy, getWalker, hasTradeContext, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenBreakeven, listenBreakevenOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenPing, listenPingOnce, listenRisk, listenRiskOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideAction, overrideExchange, overrideFrame, overrideOptimizer, overrideRisk, overrideSizing, overrideStrategy, overrideWalker, partialLoss, partialProfit, roundTicks, set, setColumns, setConfig, setLogger, stop, trailingStop, trailingTake, validate };