backtest-kit 3.4.2 → 3.5.1
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/build/index.cjs +250 -32
- package/build/index.mjs +250 -32
- package/package.json +1 -1
- package/types.d.ts +2127 -2114
package/types.d.ts
CHANGED
|
@@ -58,2161 +58,1623 @@ declare const ExecutionContextService: (new () => {
|
|
|
58
58
|
type TExecutionContextService = InstanceType<typeof ExecutionContextService>;
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* Timeframe interval for backtest period generation.
|
|
62
|
+
* Determines the granularity of timestamps in the generated timeframe array.
|
|
63
|
+
*
|
|
64
|
+
* Minutes: 1m, 3m, 5m, 15m, 30m
|
|
65
|
+
* Hours: 1h, 2h, 4h, 6h, 8h, 12h
|
|
66
|
+
* Days: 1d, 3d
|
|
62
67
|
*/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
createdAt: string;
|
|
72
|
-
/** Log topic / method name */
|
|
73
|
-
topic: string;
|
|
74
|
-
/** Additional arguments passed to the log call */
|
|
75
|
-
args: unknown[];
|
|
68
|
+
type FrameInterval = "1m" | "3m" | "5m" | "15m" | "30m" | "1h" | "2h" | "4h" | "6h" | "8h" | "12h" | "1d" | "3d";
|
|
69
|
+
/**
|
|
70
|
+
* Frame parameters passed to ClientFrame constructor.
|
|
71
|
+
* Extends IFrameSchema with logger instance for internal logging.
|
|
72
|
+
*/
|
|
73
|
+
interface IFrameParams extends IFrameSchema {
|
|
74
|
+
/** Logger service for debug output */
|
|
75
|
+
logger: ILogger;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
*/
|
|
82
|
-
interface ILogger {
|
|
83
|
-
/**
|
|
84
|
-
* Logs a general-purpose message.
|
|
85
|
-
* Used throughout the swarm system to record significant events or state changes, such as agent execution, session connections, or storage updates.
|
|
86
|
-
*/
|
|
87
|
-
log(topic: string, ...args: any[]): void;
|
|
88
|
-
/**
|
|
89
|
-
* Logs a debug-level message.
|
|
90
|
-
* Employed for detailed diagnostic information, such as intermediate states during agent tool calls, swarm navigation changes, or embedding creation processes, typically enabled in development or troubleshooting scenarios.
|
|
91
|
-
*/
|
|
92
|
-
debug(topic: string, ...args: any[]): void;
|
|
93
|
-
/**
|
|
94
|
-
* Logs an info-level message.
|
|
95
|
-
* Used to record informational updates, such as successful completions, policy validations, or history commits, providing a high-level overview of system activity without excessive detail.
|
|
96
|
-
*/
|
|
97
|
-
info(topic: string, ...args: any[]): void;
|
|
78
|
+
* Callbacks for frame lifecycle events.
|
|
79
|
+
*/
|
|
80
|
+
interface IFrameCallbacks {
|
|
98
81
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
82
|
+
* Called after timeframe array generation.
|
|
83
|
+
* Useful for logging or validating the generated timeframes.
|
|
84
|
+
*
|
|
85
|
+
* @param timeframe - Array of Date objects representing tick timestamps
|
|
86
|
+
* @param startDate - Start of the backtest period
|
|
87
|
+
* @param endDate - End of the backtest period
|
|
88
|
+
* @param interval - Interval used for generation
|
|
101
89
|
*/
|
|
102
|
-
|
|
90
|
+
onTimeframe: (timeframe: Date[], startDate: Date, endDate: Date, interval: FrameInterval) => void | Promise<void>;
|
|
103
91
|
}
|
|
104
|
-
|
|
105
92
|
/**
|
|
106
|
-
*
|
|
93
|
+
* Frame schema registered via addFrame().
|
|
94
|
+
* Defines backtest period and interval for timestamp generation.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```typescript
|
|
98
|
+
* addFrame({
|
|
99
|
+
* frameName: "1d-backtest",
|
|
100
|
+
* interval: "1m",
|
|
101
|
+
* startDate: new Date("2024-01-01T00:00:00Z"),
|
|
102
|
+
* endDate: new Date("2024-01-02T00:00:00Z"),
|
|
103
|
+
* callbacks: {
|
|
104
|
+
* onTimeframe: (timeframe, startDate, endDate, interval) => {
|
|
105
|
+
* console.log(`Generated ${timeframe.length} timestamps`);
|
|
106
|
+
* },
|
|
107
|
+
* },
|
|
108
|
+
* });
|
|
109
|
+
* ```
|
|
107
110
|
*/
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
timestamp
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
close: Num;
|
|
122
|
-
/** Trading volume during candle period */
|
|
123
|
-
volume: Num;
|
|
111
|
+
interface IFrameSchema {
|
|
112
|
+
/** Unique identifier for this frame */
|
|
113
|
+
frameName: FrameName;
|
|
114
|
+
/** Optional developer note for documentation */
|
|
115
|
+
note?: string;
|
|
116
|
+
/** Interval for timestamp generation */
|
|
117
|
+
interval: FrameInterval;
|
|
118
|
+
/** Start of backtest period (inclusive) */
|
|
119
|
+
startDate: Date;
|
|
120
|
+
/** End of backtest period (inclusive) */
|
|
121
|
+
endDate: Date;
|
|
122
|
+
/** Optional lifecycle callbacks */
|
|
123
|
+
callbacks?: Partial<IFrameCallbacks>;
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
|
-
*
|
|
127
|
-
* Used
|
|
126
|
+
* Frame interface for timeframe generation.
|
|
127
|
+
* Used internally by backtest orchestration.
|
|
128
128
|
*/
|
|
129
|
-
interface
|
|
130
|
-
/**
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
/** Closing price at candle end */
|
|
139
|
-
close: number;
|
|
140
|
-
/** Trading volume during candle period */
|
|
141
|
-
volume: number;
|
|
129
|
+
interface IFrame {
|
|
130
|
+
/**
|
|
131
|
+
* Generates array of timestamps for backtest iteration.
|
|
132
|
+
* Timestamps are spaced according to the configured interval.
|
|
133
|
+
*
|
|
134
|
+
* @param symbol - Trading pair symbol (unused, for API consistency)
|
|
135
|
+
* @returns Promise resolving to array of Date objects
|
|
136
|
+
*/
|
|
137
|
+
getTimeframe: (symbol: string, frameName: FrameName) => Promise<Date[]>;
|
|
142
138
|
}
|
|
143
139
|
/**
|
|
144
|
-
*
|
|
140
|
+
* Unique identifier for a frame schema.
|
|
141
|
+
* Used to retrieve frame instances via dependency injection.
|
|
145
142
|
*/
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
price: string;
|
|
149
|
-
/** Quantity at this price level as string */
|
|
150
|
-
quantity: string;
|
|
151
|
-
}
|
|
143
|
+
type FrameName = string;
|
|
144
|
+
|
|
152
145
|
/**
|
|
153
|
-
*
|
|
146
|
+
* Risk rejection result type.
|
|
147
|
+
* Can be void, null, or an IRiskRejectionResult object.
|
|
154
148
|
*/
|
|
155
|
-
|
|
156
|
-
|
|
149
|
+
type RiskRejection = void | IRiskRejectionResult | string | null;
|
|
150
|
+
/**
|
|
151
|
+
* Risk check arguments for evaluating whether to allow opening a new position.
|
|
152
|
+
* Called BEFORE signal creation to validate if conditions allow new signals.
|
|
153
|
+
* Contains only passthrough arguments from ClientStrategy context.
|
|
154
|
+
*/
|
|
155
|
+
interface IRiskCheckArgs {
|
|
156
|
+
/** Trading pair symbol (e.g., "BTCUSDT") */
|
|
157
157
|
symbol: string;
|
|
158
|
-
/**
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
|
|
158
|
+
/** Pending signal to apply */
|
|
159
|
+
currentSignal: IPublicSignalRow;
|
|
160
|
+
/** Strategy name requesting to open a position */
|
|
161
|
+
strategyName: StrategyName;
|
|
162
|
+
/** Exchange name */
|
|
163
|
+
exchangeName: ExchangeName;
|
|
164
|
+
/** Risk name */
|
|
165
|
+
riskName: RiskName;
|
|
166
|
+
/** Frame name */
|
|
167
|
+
frameName: FrameName;
|
|
168
|
+
/** Current VWAP price */
|
|
169
|
+
currentPrice: number;
|
|
170
|
+
/** Current timestamp */
|
|
171
|
+
timestamp: number;
|
|
162
172
|
}
|
|
163
173
|
/**
|
|
164
|
-
*
|
|
165
|
-
* Represents a single trade that has occurred, used for detailed analysis and backtesting.
|
|
166
|
-
* Includes price, quantity, timestamp, and whether the buyer is the market maker (which can indicate trade direction).
|
|
167
|
-
*
|
|
174
|
+
* Active position tracked by ClientRisk for cross-strategy analysis.
|
|
168
175
|
*/
|
|
169
|
-
interface
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
|
|
176
|
+
interface IRiskActivePosition {
|
|
177
|
+
/** Strategy name owning the position */
|
|
178
|
+
strategyName: StrategyName;
|
|
179
|
+
/** Exchange name */
|
|
180
|
+
exchangeName: ExchangeName;
|
|
181
|
+
/** Frame name */
|
|
182
|
+
frameName: FrameName;
|
|
183
|
+
/** Trading pair symbol (e.g., "BTCUSDT") */
|
|
184
|
+
symbol: string;
|
|
185
|
+
/** Position direction ("long" or "short") */
|
|
186
|
+
position: "long" | "short";
|
|
187
|
+
/** Entry price */
|
|
188
|
+
priceOpen: number;
|
|
189
|
+
/** Stop loss price */
|
|
190
|
+
priceStopLoss: number;
|
|
191
|
+
/** Take profit price */
|
|
192
|
+
priceTakeProfit: number;
|
|
193
|
+
/** Estimated time in minutes */
|
|
194
|
+
minuteEstimatedTime: number;
|
|
195
|
+
/** Timestamp when the position was opened */
|
|
196
|
+
openTimestamp: number;
|
|
180
197
|
}
|
|
181
198
|
/**
|
|
182
|
-
*
|
|
183
|
-
* Combines schema with runtime dependencies.
|
|
184
|
-
* Note: All exchange methods are required in params (defaults are applied during initialization).
|
|
199
|
+
* Optional callbacks for risk events.
|
|
185
200
|
*/
|
|
186
|
-
interface
|
|
187
|
-
/**
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
|
|
191
|
-
/** Fetch candles from data source (required, defaults applied) */
|
|
192
|
-
getCandles: (symbol: string, interval: CandleInterval, since: Date, limit: number, backtest: boolean) => Promise<ICandleData[]>;
|
|
193
|
-
/** Format quantity according to exchange precision rules (required, defaults applied) */
|
|
194
|
-
formatQuantity: (symbol: string, quantity: number, backtest: boolean) => Promise<string>;
|
|
195
|
-
/** Format price according to exchange precision rules (required, defaults applied) */
|
|
196
|
-
formatPrice: (symbol: string, price: number, backtest: boolean) => Promise<string>;
|
|
197
|
-
/** Fetch order book for a trading pair (required, defaults applied) */
|
|
198
|
-
getOrderBook: (symbol: string, depth: number, from: Date, to: Date, backtest: boolean) => Promise<IOrderBookData>;
|
|
199
|
-
/** Fetch aggregated trades for a trading pair (required, defaults applied) */
|
|
200
|
-
getAggregatedTrades: (symbol: string, from: Date, to: Date, backtest: boolean) => Promise<IAggregatedTradeData[]>;
|
|
201
|
+
interface IRiskCallbacks {
|
|
202
|
+
/** Called when a signal is rejected due to risk limits */
|
|
203
|
+
onRejected: (symbol: string, params: IRiskCheckArgs) => void | Promise<void>;
|
|
204
|
+
/** Called when a signal passes risk checks */
|
|
205
|
+
onAllowed: (symbol: string, params: IRiskCheckArgs) => void | Promise<void>;
|
|
201
206
|
}
|
|
202
207
|
/**
|
|
203
|
-
*
|
|
208
|
+
* Payload passed to risk validation functions.
|
|
209
|
+
* Extends IRiskCheckArgs with portfolio state data.
|
|
204
210
|
*/
|
|
205
|
-
interface
|
|
206
|
-
/**
|
|
207
|
-
|
|
211
|
+
interface IRiskValidationPayload extends IRiskCheckArgs {
|
|
212
|
+
/** Current signal being validated (IRiskSignalRow is calculated internally so priceOpen always exist) */
|
|
213
|
+
currentSignal: IRiskSignalRow;
|
|
214
|
+
/** Number of currently active positions across all strategies */
|
|
215
|
+
activePositionCount: number;
|
|
216
|
+
/** List of currently active positions across all strategies */
|
|
217
|
+
activePositions: IRiskActivePosition[];
|
|
208
218
|
}
|
|
209
219
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
220
|
+
* Risk validation rejection result.
|
|
221
|
+
* Returned when validation fails, contains debugging information.
|
|
212
222
|
*/
|
|
213
|
-
interface
|
|
214
|
-
/** Unique
|
|
215
|
-
|
|
223
|
+
interface IRiskRejectionResult {
|
|
224
|
+
/** Unique identifier for this rejection instance */
|
|
225
|
+
id: string | null;
|
|
226
|
+
/** Human-readable reason for rejection */
|
|
227
|
+
note: string;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Risk validation function type.
|
|
231
|
+
* Returns null/void if validation passes, IRiskRejectionResult if validation fails.
|
|
232
|
+
* Can also throw error which will be caught and converted to IRiskRejectionResult.
|
|
233
|
+
*/
|
|
234
|
+
interface IRiskValidationFn {
|
|
235
|
+
(payload: IRiskValidationPayload): RiskRejection | Promise<RiskRejection>;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Risk validation configuration.
|
|
239
|
+
* Defines validation logic with optional documentation.
|
|
240
|
+
*/
|
|
241
|
+
interface IRiskValidation {
|
|
242
|
+
/**
|
|
243
|
+
* The validation function to apply to the risk check parameters.
|
|
244
|
+
*/
|
|
245
|
+
validate: IRiskValidationFn;
|
|
246
|
+
/**
|
|
247
|
+
* Optional description for documentation purposes.
|
|
248
|
+
* Aids in understanding the purpose or behavior of the validation.
|
|
249
|
+
*/
|
|
250
|
+
note?: string;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Risk schema registered via addRisk().
|
|
254
|
+
* Defines portfolio-level risk controls via custom validations.
|
|
255
|
+
*/
|
|
256
|
+
interface IRiskSchema {
|
|
257
|
+
/** Unique risk profile identifier */
|
|
258
|
+
riskName: RiskName;
|
|
216
259
|
/** Optional developer note for documentation */
|
|
217
260
|
note?: string;
|
|
261
|
+
/** Optional lifecycle event callbacks (onRejected, onAllowed) */
|
|
262
|
+
callbacks?: Partial<IRiskCallbacks>;
|
|
263
|
+
/** Custom validations array for risk logic */
|
|
264
|
+
validations: (IRiskValidation | IRiskValidationFn)[];
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Risk parameters passed to ClientRisk constructor.
|
|
268
|
+
* Combines schema with runtime dependencies and emission callbacks.
|
|
269
|
+
*/
|
|
270
|
+
interface IRiskParams extends IRiskSchema {
|
|
271
|
+
/** Exchange name (e.g., "binance") */
|
|
272
|
+
exchangeName: ExchangeName;
|
|
273
|
+
/** Logger service for debug output */
|
|
274
|
+
logger: ILogger;
|
|
275
|
+
/** True if backtest mode, false if live mode */
|
|
276
|
+
backtest: boolean;
|
|
218
277
|
/**
|
|
219
|
-
*
|
|
278
|
+
* Callback invoked when a signal is rejected due to risk limits.
|
|
279
|
+
* Called before emitting to riskSubject.
|
|
280
|
+
* Used for event emission to riskSubject (separate from schema callbacks).
|
|
220
281
|
*
|
|
221
|
-
* @param symbol - Trading pair symbol
|
|
222
|
-
* @param
|
|
223
|
-
* @param
|
|
224
|
-
* @param
|
|
225
|
-
* @param
|
|
226
|
-
* @
|
|
282
|
+
* @param symbol - Trading pair symbol
|
|
283
|
+
* @param params - Risk check arguments
|
|
284
|
+
* @param activePositionCount - Number of active positions at rejection time
|
|
285
|
+
* @param rejectionResult - Rejection result with id and note
|
|
286
|
+
* @param timestamp - Event timestamp in milliseconds
|
|
287
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
227
288
|
*/
|
|
228
|
-
|
|
289
|
+
onRejected: (symbol: string, params: IRiskCheckArgs, activePositionCount: number, rejectionResult: IRiskRejectionResult, timestamp: number, backtest: boolean) => void | Promise<void>;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Risk interface implemented by ClientRisk.
|
|
293
|
+
* Provides risk checking for signals and position tracking.
|
|
294
|
+
*/
|
|
295
|
+
interface IRisk {
|
|
229
296
|
/**
|
|
230
|
-
*
|
|
297
|
+
* Check if a signal should be allowed based on risk limits.
|
|
231
298
|
*
|
|
232
|
-
*
|
|
299
|
+
* @param params - Risk check arguments (position size, portfolio state, etc.)
|
|
300
|
+
* @returns Promise resolving to risk check result
|
|
301
|
+
*/
|
|
302
|
+
checkSignal: (params: IRiskCheckArgs) => Promise<boolean>;
|
|
303
|
+
/**
|
|
304
|
+
* Register a new opened signal/position.
|
|
233
305
|
*
|
|
234
306
|
* @param symbol - Trading pair symbol
|
|
235
|
-
* @param
|
|
236
|
-
* @param
|
|
237
|
-
* @returns Promise resolving to formatted quantity string
|
|
307
|
+
* @param context - Context information (strategyName, riskName, exchangeName, frameName)
|
|
308
|
+
* @param positionData - Position data (position, prices, timing)
|
|
238
309
|
*/
|
|
239
|
-
|
|
310
|
+
addSignal: (symbol: string, context: {
|
|
311
|
+
strategyName: StrategyName;
|
|
312
|
+
riskName: RiskName;
|
|
313
|
+
exchangeName: ExchangeName;
|
|
314
|
+
frameName: FrameName;
|
|
315
|
+
}, positionData: {
|
|
316
|
+
position: "long" | "short";
|
|
317
|
+
priceOpen: number;
|
|
318
|
+
priceStopLoss: number;
|
|
319
|
+
priceTakeProfit: number;
|
|
320
|
+
minuteEstimatedTime: number;
|
|
321
|
+
openTimestamp: number;
|
|
322
|
+
}) => Promise<void>;
|
|
240
323
|
/**
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* Optional. If not provided, defaults to Bitcoin precision on Binance (2 decimal places).
|
|
324
|
+
* Remove a closed signal/position.
|
|
244
325
|
*
|
|
245
326
|
* @param symbol - Trading pair symbol
|
|
246
|
-
* @param
|
|
247
|
-
* @param backtest - Whether running in backtest mode
|
|
248
|
-
* @returns Promise resolving to formatted price string
|
|
327
|
+
* @param context - Context information (strategyName, riskName, exchangeName, frameName)
|
|
249
328
|
*/
|
|
250
|
-
|
|
329
|
+
removeSignal: (symbol: string, context: {
|
|
330
|
+
strategyName: StrategyName;
|
|
331
|
+
riskName: RiskName;
|
|
332
|
+
exchangeName: ExchangeName;
|
|
333
|
+
frameName: FrameName;
|
|
334
|
+
}) => Promise<void>;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Unique risk profile identifier.
|
|
338
|
+
*/
|
|
339
|
+
type RiskName = string;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Profit or loss level milestone in percentage points.
|
|
343
|
+
* Represents 10%, 20%, 30%, ..., 100% profit or loss thresholds.
|
|
344
|
+
*
|
|
345
|
+
* Used to track when a signal reaches specific profit/loss milestones.
|
|
346
|
+
* Each level is emitted only once per signal (deduplication via Set).
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```typescript
|
|
350
|
+
* const level: PartialLevel = 50; // 50% profit or loss milestone
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
type PartialLevel = 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100;
|
|
354
|
+
/**
|
|
355
|
+
* Serializable partial data for persistence layer.
|
|
356
|
+
* Converts Sets to arrays for JSON serialization.
|
|
357
|
+
*
|
|
358
|
+
* Stored in PersistPartialAdapter as Record<signalId, IPartialData>.
|
|
359
|
+
* Loaded on initialization and converted back to IPartialState.
|
|
360
|
+
*/
|
|
361
|
+
interface IPartialData {
|
|
251
362
|
/**
|
|
252
|
-
*
|
|
363
|
+
* Array of profit levels that have been reached for this signal.
|
|
364
|
+
* Serialized form of IPartialState.profitLevels Set.
|
|
365
|
+
*/
|
|
366
|
+
profitLevels: PartialLevel[];
|
|
367
|
+
/**
|
|
368
|
+
* Array of loss levels that have been reached for this signal.
|
|
369
|
+
* Serialized form of IPartialState.lossLevels Set.
|
|
370
|
+
*/
|
|
371
|
+
lossLevels: PartialLevel[];
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Partial profit/loss tracking interface.
|
|
375
|
+
* Implemented by ClientPartial and PartialConnectionService.
|
|
376
|
+
*
|
|
377
|
+
* Tracks profit/loss level milestones for active trading signals.
|
|
378
|
+
* Emits events when signals reach 10%, 20%, 30%, etc profit or loss.
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* import { ClientPartial } from "./client/ClientPartial";
|
|
383
|
+
*
|
|
384
|
+
* const partial = new ClientPartial({
|
|
385
|
+
* logger: loggerService,
|
|
386
|
+
* onProfit: (symbol, data, price, level, backtest, timestamp) => {
|
|
387
|
+
* console.log(`Signal ${data.id} reached ${level}% profit`);
|
|
388
|
+
* },
|
|
389
|
+
* onLoss: (symbol, data, price, level, backtest, timestamp) => {
|
|
390
|
+
* console.log(`Signal ${data.id} reached ${level}% loss`);
|
|
391
|
+
* }
|
|
392
|
+
* });
|
|
393
|
+
*
|
|
394
|
+
* await partial.waitForInit("BTCUSDT");
|
|
395
|
+
*
|
|
396
|
+
* // During signal monitoring
|
|
397
|
+
* await partial.profit("BTCUSDT", signal, 51000, 15.5, false, new Date());
|
|
398
|
+
* // Emits event when reaching 10% profit milestone
|
|
399
|
+
*
|
|
400
|
+
* // When signal closes
|
|
401
|
+
* await partial.clear("BTCUSDT", signal, 52000);
|
|
402
|
+
* ```
|
|
403
|
+
*/
|
|
404
|
+
interface IPartial {
|
|
405
|
+
/**
|
|
406
|
+
* Processes profit state and emits events for new profit levels reached.
|
|
253
407
|
*
|
|
254
|
-
*
|
|
408
|
+
* Called by ClientStrategy during signal monitoring when revenuePercent > 0.
|
|
409
|
+
* Checks which profit levels (10%, 20%, 30%, etc) have been reached
|
|
410
|
+
* and emits events for new levels only (Set-based deduplication).
|
|
255
411
|
*
|
|
256
412
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
257
|
-
* @param
|
|
258
|
-
* @param
|
|
259
|
-
* @param
|
|
260
|
-
* @param backtest -
|
|
261
|
-
* @
|
|
413
|
+
* @param data - Signal row data
|
|
414
|
+
* @param currentPrice - Current market price
|
|
415
|
+
* @param revenuePercent - Current profit percentage (positive value)
|
|
416
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
417
|
+
* @param when - Event timestamp (current time for live, candle time for backtest)
|
|
418
|
+
* @returns Promise that resolves when profit processing is complete
|
|
262
419
|
*
|
|
263
420
|
* @example
|
|
264
421
|
* ```typescript
|
|
265
|
-
* //
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
* }
|
|
270
|
-
* return await exchange.fetchOrderBook(symbol, depth);
|
|
271
|
-
* };
|
|
422
|
+
* // Signal opened at $50000, current price $51500
|
|
423
|
+
* // Revenue: 3% profit
|
|
424
|
+
* await partial.profit("BTCUSDT", signal, 51500, 3.0, false, new Date());
|
|
425
|
+
* // No events emitted (below 10% threshold)
|
|
272
426
|
*
|
|
273
|
-
* //
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
427
|
+
* // Price rises to $55000
|
|
428
|
+
* // Revenue: 10% profit
|
|
429
|
+
* await partial.profit("BTCUSDT", signal, 55000, 10.0, false, new Date());
|
|
430
|
+
* // Emits partialProfitSubject event for 10% level
|
|
431
|
+
*
|
|
432
|
+
* // Price rises to $61000
|
|
433
|
+
* // Revenue: 22% profit
|
|
434
|
+
* await partial.profit("BTCUSDT", signal, 61000, 22.0, false, new Date());
|
|
435
|
+
* // Emits events for 20% level only (10% already emitted)
|
|
277
436
|
* ```
|
|
278
437
|
*/
|
|
279
|
-
|
|
438
|
+
profit(symbol: string, data: IPublicSignalRow, currentPrice: number, revenuePercent: number, backtest: boolean, when: Date): Promise<void>;
|
|
280
439
|
/**
|
|
281
|
-
*
|
|
282
|
-
*
|
|
440
|
+
* Processes loss state and emits events for new loss levels reached.
|
|
441
|
+
*
|
|
442
|
+
* Called by ClientStrategy during signal monitoring when revenuePercent < 0.
|
|
443
|
+
* Checks which loss levels (10%, 20%, 30%, etc) have been reached
|
|
444
|
+
* and emits events for new levels only (Set-based deduplication).
|
|
445
|
+
*
|
|
283
446
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
284
|
-
* @param
|
|
285
|
-
* @param
|
|
286
|
-
* @param
|
|
287
|
-
* @
|
|
447
|
+
* @param data - Signal row data
|
|
448
|
+
* @param currentPrice - Current market price
|
|
449
|
+
* @param lossPercent - Current loss percentage (negative value)
|
|
450
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
451
|
+
* @param when - Event timestamp (current time for live, candle time for backtest)
|
|
452
|
+
* @returns Promise that resolves when loss processing is complete
|
|
453
|
+
*
|
|
288
454
|
* @example
|
|
289
455
|
* ```typescript
|
|
290
|
-
* //
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* }
|
|
295
|
-
* return await exchange.fetchAggregatedTrades(symbol);
|
|
296
|
-
* };
|
|
456
|
+
* // Signal opened at $50000, current price $48000
|
|
457
|
+
* // Loss: -4% loss
|
|
458
|
+
* await partial.loss("BTCUSDT", signal, 48000, -4.0, false, new Date());
|
|
459
|
+
* // No events emitted (below -10% threshold)
|
|
297
460
|
*
|
|
298
|
-
* //
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
461
|
+
* // Price drops to $45000
|
|
462
|
+
* // Loss: -10% loss
|
|
463
|
+
* await partial.loss("BTCUSDT", signal, 45000, -10.0, false, new Date());
|
|
464
|
+
* // Emits partialLossSubject event for 10% level
|
|
465
|
+
*
|
|
466
|
+
* // Price drops to $39000
|
|
467
|
+
* // Loss: -22% loss
|
|
468
|
+
* await partial.loss("BTCUSDT", signal, 39000, -22.0, false, new Date());
|
|
469
|
+
* // Emits events for 20% level only (10% already emitted)
|
|
302
470
|
* ```
|
|
303
471
|
*/
|
|
304
|
-
|
|
305
|
-
/** Optional lifecycle event callbacks (onCandleData) */
|
|
306
|
-
callbacks?: Partial<IExchangeCallbacks>;
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Exchange interface implemented by ClientExchange.
|
|
310
|
-
* Provides candle data access and VWAP calculation.
|
|
311
|
-
*/
|
|
312
|
-
interface IExchange {
|
|
472
|
+
loss(symbol: string, data: IPublicSignalRow, currentPrice: number, lossPercent: number, backtest: boolean, when: Date): Promise<void>;
|
|
313
473
|
/**
|
|
314
|
-
*
|
|
474
|
+
* Clears partial profit/loss state when signal closes.
|
|
315
475
|
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
* @returns Promise resolving to array of candle data
|
|
320
|
-
*/
|
|
321
|
-
getCandles: (symbol: string, interval: CandleInterval, limit: number) => Promise<ICandleData[]>;
|
|
322
|
-
/**
|
|
323
|
-
* Fetch future candles forward from execution context time (for backtest).
|
|
476
|
+
* Called by ClientStrategy when signal completes (TP/SL/time_expired).
|
|
477
|
+
* Removes signal state from memory and persists changes to disk.
|
|
478
|
+
* Cleans up memoized ClientPartial instance in PartialConnectionService.
|
|
324
479
|
*
|
|
325
480
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
326
|
-
* @param
|
|
327
|
-
* @param
|
|
328
|
-
* @returns Promise
|
|
329
|
-
*/
|
|
330
|
-
getNextCandles: (symbol: string, interval: CandleInterval, limit: number) => Promise<ICandleData[]>;
|
|
331
|
-
/**
|
|
332
|
-
* Format quantity for exchange precision.
|
|
481
|
+
* @param data - Signal row data
|
|
482
|
+
* @param priceClose - Final closing price
|
|
483
|
+
* @returns Promise that resolves when clear is complete
|
|
333
484
|
*
|
|
334
|
-
* @
|
|
335
|
-
*
|
|
336
|
-
*
|
|
485
|
+
* @example
|
|
486
|
+
* ```typescript
|
|
487
|
+
* // Signal closes at take profit
|
|
488
|
+
* await partial.clear("BTCUSDT", signal, 52000);
|
|
489
|
+
* // State removed from _states Map
|
|
490
|
+
* // Persisted to disk without this signal's data
|
|
491
|
+
* // Memoized instance cleared from getPartial cache
|
|
492
|
+
* ```
|
|
337
493
|
*/
|
|
338
|
-
|
|
494
|
+
clear(symbol: string, data: IPublicSignalRow, priceClose: number, backtest: boolean): Promise<void>;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Serializable breakeven data for persistence layer.
|
|
499
|
+
* Converts state to simple boolean for JSON serialization.
|
|
500
|
+
*
|
|
501
|
+
* Stored in PersistBreakevenAdapter as Record<signalId, IBreakevenData>.
|
|
502
|
+
* Loaded on initialization and converted back to IBreakevenState.
|
|
503
|
+
*/
|
|
504
|
+
interface IBreakevenData {
|
|
339
505
|
/**
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* @param symbol - Trading pair symbol
|
|
343
|
-
* @param price - Raw price value
|
|
344
|
-
* @returns Promise resolving to formatted price string
|
|
506
|
+
* Whether breakeven has been reached for this signal.
|
|
507
|
+
* Serialized form of IBreakevenState.reached.
|
|
345
508
|
*/
|
|
346
|
-
|
|
509
|
+
reached: boolean;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Breakeven tracking interface.
|
|
513
|
+
* Implemented by ClientBreakeven and BreakevenConnectionService.
|
|
514
|
+
*
|
|
515
|
+
* Tracks when a signal's stop-loss is moved to breakeven (entry price).
|
|
516
|
+
* Emits events when threshold is reached (price moves far enough to cover transaction costs).
|
|
517
|
+
*
|
|
518
|
+
* @example
|
|
519
|
+
* ```typescript
|
|
520
|
+
* import { ClientBreakeven } from "./client/ClientBreakeven";
|
|
521
|
+
*
|
|
522
|
+
* const breakeven = new ClientBreakeven({
|
|
523
|
+
* logger: loggerService,
|
|
524
|
+
* onBreakeven: (symbol, data, price, backtest, timestamp) => {
|
|
525
|
+
* console.log(`Signal ${data.id} reached breakeven at ${price}`);
|
|
526
|
+
* }
|
|
527
|
+
* });
|
|
528
|
+
*
|
|
529
|
+
* await breakeven.waitForInit("BTCUSDT");
|
|
530
|
+
*
|
|
531
|
+
* // During signal monitoring
|
|
532
|
+
* await breakeven.check("BTCUSDT", signal, 100.5, false, new Date());
|
|
533
|
+
* // Emits event when threshold reached and SL moved to entry
|
|
534
|
+
*
|
|
535
|
+
* // When signal closes
|
|
536
|
+
* await breakeven.clear("BTCUSDT", signal, 101, false);
|
|
537
|
+
* ```
|
|
538
|
+
*/
|
|
539
|
+
interface IBreakeven {
|
|
347
540
|
/**
|
|
348
|
-
*
|
|
541
|
+
* Checks if breakeven should be triggered and emits event if conditions met.
|
|
349
542
|
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
543
|
+
* Called by ClientStrategy during signal monitoring.
|
|
544
|
+
* Checks if:
|
|
545
|
+
* 1. Breakeven not already reached
|
|
546
|
+
* 2. Price has moved far enough to cover transaction costs
|
|
547
|
+
* 3. Stop-loss can be moved to entry price
|
|
352
548
|
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Fetch order book for a trading pair.
|
|
549
|
+
* If all conditions met:
|
|
550
|
+
* - Marks breakeven as reached
|
|
551
|
+
* - Calls onBreakeven callback (emits to breakevenSubject)
|
|
552
|
+
* - Persists state to disk
|
|
359
553
|
*
|
|
360
554
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
361
|
-
* @param
|
|
362
|
-
* @
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
* Fetch aggregated trades for a trading pair.
|
|
555
|
+
* @param data - Signal row data
|
|
556
|
+
* @param currentPrice - Current market price
|
|
557
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
558
|
+
* @param when - Event timestamp (current time for live, candle time for backtest)
|
|
559
|
+
* @returns Promise that resolves when breakeven check is complete
|
|
367
560
|
*
|
|
368
|
-
* @
|
|
369
|
-
*
|
|
370
|
-
*
|
|
561
|
+
* @example
|
|
562
|
+
* ```typescript
|
|
563
|
+
* // LONG: entry=100, slippage=0.1%, fee=0.1%, threshold=0.4%
|
|
564
|
+
* // Price at 100.3 - threshold not reached
|
|
565
|
+
* await breakeven.check("BTCUSDT", signal, 100.3, false, new Date());
|
|
566
|
+
* // No event emitted (price < 100.4)
|
|
567
|
+
*
|
|
568
|
+
* // Price at 100.5 - threshold reached!
|
|
569
|
+
* await breakeven.check("BTCUSDT", signal, 100.5, false, new Date());
|
|
570
|
+
* // Emits breakevenSubject event
|
|
571
|
+
*
|
|
572
|
+
* // Price at 101 - already at breakeven
|
|
573
|
+
* await breakeven.check("BTCUSDT", signal, 101, false, new Date());
|
|
574
|
+
* // No event emitted (already reached)
|
|
575
|
+
* ```
|
|
371
576
|
*/
|
|
372
|
-
|
|
577
|
+
check(symbol: string, data: IPublicSignalRow, currentPrice: number, backtest: boolean, when: Date): Promise<boolean>;
|
|
373
578
|
/**
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
* All modes respect execution context and prevent look-ahead bias.
|
|
579
|
+
* Clears breakeven state when signal closes.
|
|
377
580
|
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
* 3. eDate + limit: calculates sDate backward, validates eDate <= when
|
|
382
|
-
* 4. sDate + limit: fetches forward, validates calculated endTimestamp <= when
|
|
383
|
-
* 5. Only limit: uses execution.context.when as reference (backward)
|
|
581
|
+
* Called by ClientStrategy when signal completes (TP/SL/time_expired).
|
|
582
|
+
* Removes signal state from memory and persists changes to disk.
|
|
583
|
+
* Cleans up memoized ClientBreakeven instance in BreakevenConnectionService.
|
|
384
584
|
*
|
|
385
585
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
386
|
-
* @param
|
|
387
|
-
* @param
|
|
388
|
-
* @param
|
|
389
|
-
* @
|
|
390
|
-
*
|
|
586
|
+
* @param data - Signal row data
|
|
587
|
+
* @param priceClose - Final closing price
|
|
588
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
589
|
+
* @returns Promise that resolves when clear is complete
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* ```typescript
|
|
593
|
+
* // Signal closes at take profit
|
|
594
|
+
* await breakeven.clear("BTCUSDT", signal, 101);
|
|
595
|
+
* // State removed from _states Map
|
|
596
|
+
* // Persisted to disk without this signal's data
|
|
597
|
+
* // Memoized instance cleared from getBreakeven cache
|
|
598
|
+
* ```
|
|
391
599
|
*/
|
|
392
|
-
|
|
600
|
+
clear(symbol: string, data: IPublicSignalRow, priceClose: number, backtest: boolean): Promise<void>;
|
|
393
601
|
}
|
|
394
|
-
/**
|
|
395
|
-
* Unique exchange identifier.
|
|
396
|
-
*/
|
|
397
|
-
type ExchangeName = string;
|
|
398
602
|
|
|
399
603
|
/**
|
|
400
|
-
*
|
|
401
|
-
*
|
|
604
|
+
* Contract for breakeven events.
|
|
605
|
+
*
|
|
606
|
+
* Emitted by breakevenSubject when a signal's stop-loss is moved to breakeven (entry price).
|
|
607
|
+
* Used for tracking risk reduction milestones and monitoring strategy safety.
|
|
608
|
+
*
|
|
609
|
+
* Events are emitted only once per signal (idempotent - protected by ClientBreakeven state).
|
|
610
|
+
* Breakeven is triggered when price moves far enough in profit direction to cover transaction costs.
|
|
611
|
+
*
|
|
612
|
+
* Consumers:
|
|
613
|
+
* - BreakevenMarkdownService: Accumulates events for report generation
|
|
614
|
+
* - User callbacks via listenBreakeven() / listenBreakevenOnce()
|
|
615
|
+
*
|
|
616
|
+
* @example
|
|
617
|
+
* ```typescript
|
|
618
|
+
* import { listenBreakeven } from "backtest-kit";
|
|
619
|
+
*
|
|
620
|
+
* // Listen to all breakeven events
|
|
621
|
+
* listenBreakeven((event) => {
|
|
622
|
+
* console.log(`[${event.backtest ? "Backtest" : "Live"}] Signal ${event.data.id} moved to breakeven`);
|
|
623
|
+
* console.log(`Symbol: ${event.symbol}, Price: ${event.currentPrice}`);
|
|
624
|
+
* console.log(`Position: ${event.data.position}, Entry: ${event.data.priceOpen}`);
|
|
625
|
+
* console.log(`Original SL: ${event.data.priceStopLoss}, New SL: ${event.data.priceOpen}`);
|
|
626
|
+
* });
|
|
627
|
+
*
|
|
628
|
+
* // Wait for specific signal to reach breakeven
|
|
629
|
+
* listenBreakevenOnce(
|
|
630
|
+
* (event) => event.data.id === "target-signal-id",
|
|
631
|
+
* (event) => console.log("Signal reached breakeven:", event.data.id)
|
|
632
|
+
* );
|
|
633
|
+
* ```
|
|
402
634
|
*/
|
|
403
|
-
interface
|
|
404
|
-
/**
|
|
635
|
+
interface BreakevenContract {
|
|
636
|
+
/**
|
|
637
|
+
* Trading pair symbol (e.g., "BTCUSDT").
|
|
638
|
+
* Identifies which market this breakeven event belongs to.
|
|
639
|
+
*/
|
|
405
640
|
symbol: string;
|
|
406
|
-
/**
|
|
641
|
+
/**
|
|
642
|
+
* Strategy name that generated this signal.
|
|
643
|
+
* Identifies which strategy execution this breakeven event belongs to.
|
|
644
|
+
*/
|
|
645
|
+
strategyName: StrategyName;
|
|
646
|
+
/**
|
|
647
|
+
* Exchange name where this signal is being executed.
|
|
648
|
+
* Identifies which exchange this breakeven event belongs to.
|
|
649
|
+
*/
|
|
407
650
|
exchangeName: ExchangeName;
|
|
408
|
-
/**
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
651
|
+
/**
|
|
652
|
+
* Frame name where this signal is being executed.
|
|
653
|
+
* Identifies which frame this breakeven event belongs to (empty string for live mode).
|
|
654
|
+
*/
|
|
655
|
+
frameName: FrameName;
|
|
656
|
+
/**
|
|
657
|
+
* Complete signal row data with original prices.
|
|
658
|
+
* Contains all signal information including originalPriceStopLoss, originalPriceTakeProfit, and partialExecuted.
|
|
659
|
+
*/
|
|
660
|
+
data: IPublicSignalRow;
|
|
661
|
+
/**
|
|
662
|
+
* Current market price at which breakeven was triggered.
|
|
663
|
+
* Used to verify threshold calculation.
|
|
664
|
+
*/
|
|
665
|
+
currentPrice: number;
|
|
666
|
+
/**
|
|
667
|
+
* Execution mode flag.
|
|
668
|
+
* - true: Event from backtest execution (historical candle data)
|
|
669
|
+
* - false: Event from live trading (real-time tick)
|
|
670
|
+
*/
|
|
671
|
+
backtest: boolean;
|
|
672
|
+
/**
|
|
673
|
+
* Event timestamp in milliseconds since Unix epoch.
|
|
674
|
+
*
|
|
675
|
+
* Timing semantics:
|
|
676
|
+
* - Live mode: when.getTime() at the moment breakeven was set
|
|
677
|
+
* - Backtest mode: candle.timestamp of the candle that triggered breakeven
|
|
678
|
+
*
|
|
679
|
+
* @example
|
|
680
|
+
* ```typescript
|
|
681
|
+
* const eventDate = new Date(event.timestamp);
|
|
682
|
+
* console.log(`Breakeven set at: ${eventDate.toISOString()}`);
|
|
683
|
+
* ```
|
|
684
|
+
*/
|
|
685
|
+
timestamp: number;
|
|
432
686
|
}
|
|
687
|
+
|
|
433
688
|
/**
|
|
434
|
-
*
|
|
435
|
-
* Reads JSON files directly from persist storage without using abstractions.
|
|
689
|
+
* Contract for partial profit level events.
|
|
436
690
|
*
|
|
437
|
-
*
|
|
438
|
-
|
|
439
|
-
declare function checkCandles(params: ICheckCandlesParams): Promise<void>;
|
|
440
|
-
/**
|
|
441
|
-
* Pre-caches candles for a date range into persist storage.
|
|
442
|
-
* Downloads all candles matching the interval from `from` to `to`.
|
|
691
|
+
* Emitted by partialProfitSubject when a signal reaches a profit level milestone (10%, 20%, 30%, etc).
|
|
692
|
+
* Used for tracking partial take-profit execution and monitoring strategy performance.
|
|
443
693
|
*
|
|
444
|
-
*
|
|
445
|
-
|
|
446
|
-
declare function warmCandles(params: ICacheCandlesParams): Promise<void>;
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Type alias for enum objects with string key-value pairs
|
|
450
|
-
*/
|
|
451
|
-
type Enum = Record<string, string>;
|
|
452
|
-
/**
|
|
453
|
-
* Type alias for ValidateArgs with any enum type
|
|
454
|
-
*/
|
|
455
|
-
type Args = ValidateArgs<any>;
|
|
456
|
-
/**
|
|
457
|
-
* Interface defining validation arguments for all entity types.
|
|
694
|
+
* Events are emitted only once per level per signal (Set-based deduplication in ClientPartial).
|
|
695
|
+
* Multiple levels can be emitted in a single tick if price jumps significantly.
|
|
458
696
|
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
697
|
+
* Consumers:
|
|
698
|
+
* - PartialMarkdownService: Accumulates events for report generation
|
|
699
|
+
* - User callbacks via listenPartialProfit() / listenPartialProfitOnce()
|
|
461
700
|
*
|
|
462
|
-
* @
|
|
701
|
+
* @example
|
|
702
|
+
* ```typescript
|
|
703
|
+
* import { listenPartialProfit } from "backtest-kit";
|
|
704
|
+
*
|
|
705
|
+
* // Listen to all partial profit events
|
|
706
|
+
* listenPartialProfit((event) => {
|
|
707
|
+
* console.log(`[${event.backtest ? "Backtest" : "Live"}] Signal ${event.data.id} reached ${event.level}% profit`);
|
|
708
|
+
* console.log(`Symbol: ${event.symbol}, Price: ${event.currentPrice}`);
|
|
709
|
+
* console.log(`Position: ${event.data.position}, Entry: ${event.data.priceOpen}`);
|
|
710
|
+
* });
|
|
711
|
+
*
|
|
712
|
+
* // Wait for first 50% profit level
|
|
713
|
+
* listenPartialProfitOnce(
|
|
714
|
+
* (event) => event.level === 50,
|
|
715
|
+
* (event) => console.log("50% profit reached:", event.data.id)
|
|
716
|
+
* );
|
|
717
|
+
* ```
|
|
463
718
|
*/
|
|
464
|
-
interface
|
|
719
|
+
interface PartialProfitContract {
|
|
465
720
|
/**
|
|
466
|
-
*
|
|
467
|
-
*
|
|
721
|
+
* Trading pair symbol (e.g., "BTCUSDT").
|
|
722
|
+
* Identifies which market this profit event belongs to.
|
|
468
723
|
*/
|
|
469
|
-
|
|
724
|
+
symbol: string;
|
|
470
725
|
/**
|
|
471
|
-
*
|
|
472
|
-
*
|
|
726
|
+
* Strategy name that generated this signal.
|
|
727
|
+
* Identifies which strategy execution this profit event belongs to.
|
|
473
728
|
*/
|
|
474
|
-
|
|
729
|
+
strategyName: StrategyName;
|
|
475
730
|
/**
|
|
476
|
-
*
|
|
477
|
-
*
|
|
731
|
+
* Exchange name where this signal is being executed.
|
|
732
|
+
* Identifies which exchange this profit event belongs to.
|
|
478
733
|
*/
|
|
479
|
-
|
|
734
|
+
exchangeName: ExchangeName;
|
|
480
735
|
/**
|
|
481
|
-
*
|
|
482
|
-
*
|
|
736
|
+
* Frame name where this signal is being executed.
|
|
737
|
+
* Identifies which frame this profit event belongs to (empty string for live mode).
|
|
483
738
|
*/
|
|
484
|
-
|
|
739
|
+
frameName: FrameName;
|
|
485
740
|
/**
|
|
486
|
-
*
|
|
487
|
-
*
|
|
741
|
+
* Complete signal row data with original prices.
|
|
742
|
+
* Contains all signal information including originalPriceStopLoss, originalPriceTakeProfit, and partialExecuted.
|
|
488
743
|
*/
|
|
489
|
-
|
|
744
|
+
data: IPublicSignalRow;
|
|
490
745
|
/**
|
|
491
|
-
*
|
|
492
|
-
*
|
|
746
|
+
* Current market price at which this profit level was reached.
|
|
747
|
+
* Used to calculate actual profit percentage.
|
|
493
748
|
*/
|
|
494
|
-
|
|
749
|
+
currentPrice: number;
|
|
495
750
|
/**
|
|
496
|
-
*
|
|
497
|
-
*
|
|
751
|
+
* Profit level milestone reached (10, 20, 30, 40, 50, 60, 70, 80, 90, or 100).
|
|
752
|
+
* Represents percentage profit relative to entry price.
|
|
753
|
+
*
|
|
754
|
+
* @example
|
|
755
|
+
* ```typescript
|
|
756
|
+
* // If entry was $50000 and level is 20:
|
|
757
|
+
* // currentPrice >= $60000 (20% profit)
|
|
758
|
+
* ```
|
|
498
759
|
*/
|
|
499
|
-
|
|
760
|
+
level: PartialLevel;
|
|
761
|
+
/**
|
|
762
|
+
* Execution mode flag.
|
|
763
|
+
* - true: Event from backtest execution (historical candle data)
|
|
764
|
+
* - false: Event from live trading (real-time tick)
|
|
765
|
+
*/
|
|
766
|
+
backtest: boolean;
|
|
767
|
+
/**
|
|
768
|
+
* Event timestamp in milliseconds since Unix epoch.
|
|
769
|
+
*
|
|
770
|
+
* Timing semantics:
|
|
771
|
+
* - Live mode: when.getTime() at the moment profit level was detected
|
|
772
|
+
* - Backtest mode: candle.timestamp of the candle that triggered the level
|
|
773
|
+
*
|
|
774
|
+
* @example
|
|
775
|
+
* ```typescript
|
|
776
|
+
* const eventDate = new Date(event.timestamp);
|
|
777
|
+
* console.log(`Profit reached at: ${eventDate.toISOString()}`);
|
|
778
|
+
* ```
|
|
779
|
+
*/
|
|
780
|
+
timestamp: number;
|
|
500
781
|
}
|
|
782
|
+
|
|
501
783
|
/**
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
* This function accepts enum objects for various entity types (exchanges, frames,
|
|
505
|
-
* strategies, risks, sizings, walkers) and validates that each entity
|
|
506
|
-
* name exists in its respective registry. Validation results are memoized for performance.
|
|
507
|
-
*
|
|
508
|
-
* If no arguments are provided (or specific entity types are omitted), the function
|
|
509
|
-
* automatically fetches and validates ALL registered entities from their respective
|
|
510
|
-
* validation services. This is useful for comprehensive validation of the entire setup.
|
|
784
|
+
* Contract for partial loss level events.
|
|
511
785
|
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
786
|
+
* Emitted by partialLossSubject when a signal reaches a loss level milestone (-10%, -20%, -30%, etc).
|
|
787
|
+
* Used for tracking partial stop-loss execution and monitoring strategy drawdown.
|
|
514
788
|
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
* If empty or omitted, validates all registered entities.
|
|
518
|
-
* @throws {Error} If any entity name is not found in its validation service
|
|
789
|
+
* Events are emitted only once per level per signal (Set-based deduplication in ClientPartial).
|
|
790
|
+
* Multiple levels can be emitted in a single tick if price drops significantly.
|
|
519
791
|
*
|
|
520
|
-
*
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
* await validate({});
|
|
524
|
-
* ```
|
|
792
|
+
* Consumers:
|
|
793
|
+
* - PartialMarkdownService: Accumulates events for report generation
|
|
794
|
+
* - User callbacks via listenPartialLoss() / listenPartialLossOnce()
|
|
525
795
|
*
|
|
526
796
|
* @example
|
|
527
797
|
* ```typescript
|
|
528
|
-
*
|
|
529
|
-
* enum ExchangeName {
|
|
530
|
-
* BINANCE = "binance",
|
|
531
|
-
* BYBIT = "bybit"
|
|
532
|
-
* }
|
|
798
|
+
* import { listenPartialLoss } from "backtest-kit";
|
|
533
799
|
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
* }
|
|
800
|
+
* // Listen to all partial loss events
|
|
801
|
+
* listenPartialLoss((event) => {
|
|
802
|
+
* console.log(`[${event.backtest ? "Backtest" : "Live"}] Signal ${event.data.id} reached -${event.level}% loss`);
|
|
803
|
+
* console.log(`Symbol: ${event.symbol}, Price: ${event.currentPrice}`);
|
|
804
|
+
* console.log(`Position: ${event.data.position}, Entry: ${event.data.priceOpen}`);
|
|
537
805
|
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
806
|
+
* // Alert on significant loss
|
|
807
|
+
* if (event.level >= 30 && !event.backtest) {
|
|
808
|
+
* console.warn("HIGH LOSS ALERT:", event.data.id);
|
|
809
|
+
* }
|
|
542
810
|
* });
|
|
543
|
-
* ```
|
|
544
811
|
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
* SizingName: { FIXED_1000: "fixed-1000" },
|
|
551
|
-
* });
|
|
812
|
+
* // Wait for first 20% loss level
|
|
813
|
+
* listenPartialLossOnce(
|
|
814
|
+
* (event) => event.level === 20,
|
|
815
|
+
* (event) => console.log("20% loss reached:", event.data.id)
|
|
816
|
+
* );
|
|
552
817
|
* ```
|
|
553
818
|
*/
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* Timeframe interval for backtest period generation.
|
|
558
|
-
* Determines the granularity of timestamps in the generated timeframe array.
|
|
559
|
-
*
|
|
560
|
-
* Minutes: 1m, 3m, 5m, 15m, 30m
|
|
561
|
-
* Hours: 1h, 2h, 4h, 6h, 8h, 12h
|
|
562
|
-
* Days: 1d, 3d
|
|
563
|
-
*/
|
|
564
|
-
type FrameInterval = "1m" | "3m" | "5m" | "15m" | "30m" | "1h" | "2h" | "4h" | "6h" | "8h" | "12h" | "1d" | "3d";
|
|
565
|
-
/**
|
|
566
|
-
* Frame parameters passed to ClientFrame constructor.
|
|
567
|
-
* Extends IFrameSchema with logger instance for internal logging.
|
|
568
|
-
*/
|
|
569
|
-
interface IFrameParams extends IFrameSchema {
|
|
570
|
-
/** Logger service for debug output */
|
|
571
|
-
logger: ILogger;
|
|
572
|
-
}
|
|
573
|
-
/**
|
|
574
|
-
* Callbacks for frame lifecycle events.
|
|
575
|
-
*/
|
|
576
|
-
interface IFrameCallbacks {
|
|
819
|
+
interface PartialLossContract {
|
|
577
820
|
/**
|
|
578
|
-
*
|
|
579
|
-
*
|
|
821
|
+
* Trading pair symbol (e.g., "BTCUSDT").
|
|
822
|
+
* Identifies which market this loss event belongs to.
|
|
823
|
+
*/
|
|
824
|
+
symbol: string;
|
|
825
|
+
/**
|
|
826
|
+
* Strategy name that generated this signal.
|
|
827
|
+
* Identifies which strategy execution this loss event belongs to.
|
|
828
|
+
*/
|
|
829
|
+
strategyName: StrategyName;
|
|
830
|
+
/**
|
|
831
|
+
* Exchange name where this signal is being executed.
|
|
832
|
+
* Identifies which exchange this loss event belongs to.
|
|
833
|
+
*/
|
|
834
|
+
exchangeName: ExchangeName;
|
|
835
|
+
/**
|
|
836
|
+
* Frame name where this signal is being executed.
|
|
837
|
+
* Identifies which frame this loss event belongs to (empty string for live mode).
|
|
838
|
+
*/
|
|
839
|
+
frameName: FrameName;
|
|
840
|
+
/**
|
|
841
|
+
* Complete signal row data with original prices.
|
|
842
|
+
* Contains all signal information including originalPriceStopLoss, originalPriceTakeProfit, and partialExecuted.
|
|
843
|
+
*/
|
|
844
|
+
data: IPublicSignalRow;
|
|
845
|
+
/**
|
|
846
|
+
* Current market price at which this loss level was reached.
|
|
847
|
+
* Used to calculate actual loss percentage.
|
|
848
|
+
*/
|
|
849
|
+
currentPrice: number;
|
|
850
|
+
/**
|
|
851
|
+
* Loss level milestone reached (10, 20, 30, 40, 50, 60, 70, 80, 90, or 100).
|
|
852
|
+
* Represents percentage loss relative to entry price (absolute value).
|
|
580
853
|
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
* @
|
|
854
|
+
* Note: Stored as positive number, but represents negative loss.
|
|
855
|
+
* level=20 means -20% loss from entry price.
|
|
856
|
+
*
|
|
857
|
+
* @example
|
|
858
|
+
* ```typescript
|
|
859
|
+
* // If entry was $50000 and level is 20:
|
|
860
|
+
* // currentPrice <= $40000 (-20% loss)
|
|
861
|
+
* // Level is stored as 20, not -20
|
|
862
|
+
* ```
|
|
585
863
|
*/
|
|
586
|
-
|
|
864
|
+
level: PartialLevel;
|
|
865
|
+
/**
|
|
866
|
+
* Execution mode flag.
|
|
867
|
+
* - true: Event from backtest execution (historical candle data)
|
|
868
|
+
* - false: Event from live trading (real-time tick)
|
|
869
|
+
*/
|
|
870
|
+
backtest: boolean;
|
|
871
|
+
/**
|
|
872
|
+
* Event timestamp in milliseconds since Unix epoch.
|
|
873
|
+
*
|
|
874
|
+
* Timing semantics:
|
|
875
|
+
* - Live mode: when.getTime() at the moment loss level was detected
|
|
876
|
+
* - Backtest mode: candle.timestamp of the candle that triggered the level
|
|
877
|
+
*
|
|
878
|
+
* @example
|
|
879
|
+
* ```typescript
|
|
880
|
+
* const eventDate = new Date(event.timestamp);
|
|
881
|
+
* console.log(`Loss reached at: ${eventDate.toISOString()}`);
|
|
882
|
+
*
|
|
883
|
+
* // Calculate time in loss
|
|
884
|
+
* const entryTime = event.data.pendingAt;
|
|
885
|
+
* const timeInLoss = event.timestamp - entryTime;
|
|
886
|
+
* console.log(`In loss for ${timeInLoss / 1000 / 60} minutes`);
|
|
887
|
+
* ```
|
|
888
|
+
*/
|
|
889
|
+
timestamp: number;
|
|
587
890
|
}
|
|
891
|
+
|
|
588
892
|
/**
|
|
589
|
-
*
|
|
590
|
-
*
|
|
893
|
+
* Contract for schedule ping events during scheduled signal monitoring.
|
|
894
|
+
*
|
|
895
|
+
* Emitted by schedulePingSubject every minute when a scheduled signal is being monitored.
|
|
896
|
+
* Used for tracking scheduled signal lifecycle and custom monitoring logic.
|
|
897
|
+
*
|
|
898
|
+
* Events are emitted only when scheduled signal is active (not cancelled, not activated).
|
|
899
|
+
* Allows users to implement custom cancellation logic via onSchedulePing callback.
|
|
900
|
+
*
|
|
901
|
+
* Consumers:
|
|
902
|
+
* - User callbacks via listenSchedulePing() / listenSchedulePingOnce()
|
|
591
903
|
*
|
|
592
904
|
* @example
|
|
593
905
|
* ```typescript
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
602
|
-
* },
|
|
603
|
-
* },
|
|
906
|
+
* import { listenSchedulePing } from "backtest-kit";
|
|
907
|
+
*
|
|
908
|
+
* // Listen to all schedule ping events
|
|
909
|
+
* listenSchedulePing((event) => {
|
|
910
|
+
* console.log(`[${event.backtest ? "Backtest" : "Live"}] Schedule Ping for ${event.symbol}`);
|
|
911
|
+
* console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
|
|
912
|
+
* console.log(`Signal ID: ${event.data.id}, priceOpen: ${event.data.priceOpen}`);
|
|
913
|
+
* console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
|
|
604
914
|
* });
|
|
915
|
+
*
|
|
916
|
+
* // Wait for specific schedule ping
|
|
917
|
+
* listenSchedulePingOnce(
|
|
918
|
+
* (event) => event.symbol === "BTCUSDT",
|
|
919
|
+
* (event) => console.log("BTCUSDT schedule ping received:", event.timestamp)
|
|
920
|
+
* );
|
|
605
921
|
* ```
|
|
606
922
|
*/
|
|
607
|
-
interface
|
|
608
|
-
/** Unique identifier for this frame */
|
|
609
|
-
frameName: FrameName;
|
|
610
|
-
/** Optional developer note for documentation */
|
|
611
|
-
note?: string;
|
|
612
|
-
/** Interval for timestamp generation */
|
|
613
|
-
interval: FrameInterval;
|
|
614
|
-
/** Start of backtest period (inclusive) */
|
|
615
|
-
startDate: Date;
|
|
616
|
-
/** End of backtest period (inclusive) */
|
|
617
|
-
endDate: Date;
|
|
618
|
-
/** Optional lifecycle callbacks */
|
|
619
|
-
callbacks?: Partial<IFrameCallbacks>;
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Frame interface for timeframe generation.
|
|
623
|
-
* Used internally by backtest orchestration.
|
|
624
|
-
*/
|
|
625
|
-
interface IFrame {
|
|
923
|
+
interface SchedulePingContract {
|
|
626
924
|
/**
|
|
627
|
-
*
|
|
628
|
-
*
|
|
925
|
+
* Trading pair symbol (e.g., "BTCUSDT").
|
|
926
|
+
* Identifies which market this ping event belongs to.
|
|
927
|
+
*/
|
|
928
|
+
symbol: string;
|
|
929
|
+
/**
|
|
930
|
+
* Strategy name that is monitoring this scheduled signal.
|
|
931
|
+
* Identifies which strategy execution this ping event belongs to.
|
|
932
|
+
*/
|
|
933
|
+
strategyName: StrategyName;
|
|
934
|
+
/**
|
|
935
|
+
* Exchange name where this scheduled signal is being monitored.
|
|
936
|
+
* Identifies which exchange this ping event belongs to.
|
|
937
|
+
*/
|
|
938
|
+
exchangeName: ExchangeName;
|
|
939
|
+
/**
|
|
940
|
+
* Complete scheduled signal row data.
|
|
941
|
+
* Contains all signal information: id, position, priceOpen, priceTakeProfit, priceStopLoss, etc.
|
|
942
|
+
*/
|
|
943
|
+
data: IScheduledSignalRow;
|
|
944
|
+
/**
|
|
945
|
+
* Execution mode flag.
|
|
946
|
+
* - true: Event from backtest execution (historical candle data)
|
|
947
|
+
* - false: Event from live trading (real-time tick)
|
|
948
|
+
*/
|
|
949
|
+
backtest: boolean;
|
|
950
|
+
/**
|
|
951
|
+
* Event timestamp in milliseconds since Unix epoch.
|
|
629
952
|
*
|
|
630
|
-
*
|
|
631
|
-
*
|
|
953
|
+
* Timing semantics:
|
|
954
|
+
* - Live mode: when.getTime() at the moment of ping
|
|
955
|
+
* - Backtest mode: candle.timestamp of the candle being processed
|
|
956
|
+
*
|
|
957
|
+
* @example
|
|
958
|
+
* ```typescript
|
|
959
|
+
* const eventDate = new Date(event.timestamp);
|
|
960
|
+
* console.log(`Ping at: ${eventDate.toISOString()}`);
|
|
961
|
+
* ```
|
|
632
962
|
*/
|
|
633
|
-
|
|
963
|
+
timestamp: number;
|
|
634
964
|
}
|
|
635
|
-
/**
|
|
636
|
-
* Unique identifier for a frame schema.
|
|
637
|
-
* Used to retrieve frame instances via dependency injection.
|
|
638
|
-
*/
|
|
639
|
-
type FrameName = string;
|
|
640
965
|
|
|
641
966
|
/**
|
|
642
|
-
*
|
|
967
|
+
* Contract for active ping events during active pending signal monitoring.
|
|
643
968
|
*
|
|
644
|
-
*
|
|
645
|
-
* for
|
|
646
|
-
*/
|
|
647
|
-
interface IMethodContext {
|
|
648
|
-
/** Name of exchange schema to use */
|
|
649
|
-
exchangeName: ExchangeName;
|
|
650
|
-
/** Name of strategy schema to use */
|
|
651
|
-
strategyName: StrategyName;
|
|
652
|
-
/** Name of frame schema to use (empty string for live mode) */
|
|
653
|
-
frameName: FrameName;
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* Scoped service for method context propagation.
|
|
969
|
+
* Emitted by activePingSubject every minute when an active pending signal is being monitored.
|
|
970
|
+
* Used for tracking active signal lifecycle and custom dynamic management logic.
|
|
657
971
|
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
972
|
+
* Events are emitted only when pending signal is active (not closed yet).
|
|
973
|
+
* Allows users to implement custom management logic via onActivePing callback.
|
|
660
974
|
*
|
|
661
|
-
*
|
|
975
|
+
* Consumers:
|
|
976
|
+
* - User callbacks via listenActivePing() / listenActivePingOnce()
|
|
662
977
|
*
|
|
663
978
|
* @example
|
|
664
979
|
* ```typescript
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
* }
|
|
980
|
+
* import { listenActivePing } from "backtest-kit";
|
|
981
|
+
*
|
|
982
|
+
* // Listen to all active ping events
|
|
983
|
+
* listenActivePing((event) => {
|
|
984
|
+
* console.log(`[${event.backtest ? "Backtest" : "Live"}] Active Ping for ${event.symbol}`);
|
|
985
|
+
* console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
|
|
986
|
+
* console.log(`Signal ID: ${event.data.id}, Position: ${event.data.position}`);
|
|
987
|
+
* console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
|
|
988
|
+
* });
|
|
989
|
+
*
|
|
990
|
+
* // Wait for specific active ping
|
|
991
|
+
* listenActivePingOnce(
|
|
992
|
+
* (event) => event.symbol === "BTCUSDT",
|
|
993
|
+
* (event) => console.log("BTCUSDT active ping received:", event.timestamp)
|
|
672
994
|
* );
|
|
673
995
|
* ```
|
|
674
996
|
*/
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
};
|
|
681
|
-
}, "prototype"> & di_scoped.IScopedClassRun<[context: IMethodContext]>;
|
|
682
|
-
|
|
683
|
-
/**
|
|
684
|
-
* Risk rejection result type.
|
|
685
|
-
* Can be void, null, or an IRiskRejectionResult object.
|
|
686
|
-
*/
|
|
687
|
-
type RiskRejection = void | IRiskRejectionResult | string | null;
|
|
688
|
-
/**
|
|
689
|
-
* Risk check arguments for evaluating whether to allow opening a new position.
|
|
690
|
-
* Called BEFORE signal creation to validate if conditions allow new signals.
|
|
691
|
-
* Contains only passthrough arguments from ClientStrategy context.
|
|
692
|
-
*/
|
|
693
|
-
interface IRiskCheckArgs {
|
|
694
|
-
/** Trading pair symbol (e.g., "BTCUSDT") */
|
|
695
|
-
symbol: string;
|
|
696
|
-
/** Pending signal to apply */
|
|
697
|
-
currentSignal: IPublicSignalRow;
|
|
698
|
-
/** Strategy name requesting to open a position */
|
|
699
|
-
strategyName: StrategyName;
|
|
700
|
-
/** Exchange name */
|
|
701
|
-
exchangeName: ExchangeName;
|
|
702
|
-
/** Risk name */
|
|
703
|
-
riskName: RiskName;
|
|
704
|
-
/** Frame name */
|
|
705
|
-
frameName: FrameName;
|
|
706
|
-
/** Current VWAP price */
|
|
707
|
-
currentPrice: number;
|
|
708
|
-
/** Current timestamp */
|
|
709
|
-
timestamp: number;
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Active position tracked by ClientRisk for cross-strategy analysis.
|
|
713
|
-
*/
|
|
714
|
-
interface IRiskActivePosition {
|
|
715
|
-
/** Strategy name owning the position */
|
|
716
|
-
strategyName: StrategyName;
|
|
717
|
-
/** Exchange name */
|
|
718
|
-
exchangeName: ExchangeName;
|
|
719
|
-
/** Frame name */
|
|
720
|
-
frameName: FrameName;
|
|
721
|
-
/** Trading pair symbol (e.g., "BTCUSDT") */
|
|
997
|
+
interface ActivePingContract {
|
|
998
|
+
/**
|
|
999
|
+
* Trading pair symbol (e.g., "BTCUSDT").
|
|
1000
|
+
* Identifies which market this ping event belongs to.
|
|
1001
|
+
*/
|
|
722
1002
|
symbol: string;
|
|
723
|
-
/** Position direction ("long" or "short") */
|
|
724
|
-
position: "long" | "short";
|
|
725
|
-
/** Entry price */
|
|
726
|
-
priceOpen: number;
|
|
727
|
-
/** Stop loss price */
|
|
728
|
-
priceStopLoss: number;
|
|
729
|
-
/** Take profit price */
|
|
730
|
-
priceTakeProfit: number;
|
|
731
|
-
/** Estimated time in minutes */
|
|
732
|
-
minuteEstimatedTime: number;
|
|
733
|
-
/** Timestamp when the position was opened */
|
|
734
|
-
openTimestamp: number;
|
|
735
|
-
}
|
|
736
|
-
/**
|
|
737
|
-
* Optional callbacks for risk events.
|
|
738
|
-
*/
|
|
739
|
-
interface IRiskCallbacks {
|
|
740
|
-
/** Called when a signal is rejected due to risk limits */
|
|
741
|
-
onRejected: (symbol: string, params: IRiskCheckArgs) => void | Promise<void>;
|
|
742
|
-
/** Called when a signal passes risk checks */
|
|
743
|
-
onAllowed: (symbol: string, params: IRiskCheckArgs) => void | Promise<void>;
|
|
744
|
-
}
|
|
745
|
-
/**
|
|
746
|
-
* Payload passed to risk validation functions.
|
|
747
|
-
* Extends IRiskCheckArgs with portfolio state data.
|
|
748
|
-
*/
|
|
749
|
-
interface IRiskValidationPayload extends IRiskCheckArgs {
|
|
750
|
-
/** Current signal being validated (IRiskSignalRow is calculated internally so priceOpen always exist) */
|
|
751
|
-
currentSignal: IRiskSignalRow;
|
|
752
|
-
/** Number of currently active positions across all strategies */
|
|
753
|
-
activePositionCount: number;
|
|
754
|
-
/** List of currently active positions across all strategies */
|
|
755
|
-
activePositions: IRiskActivePosition[];
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* Risk validation rejection result.
|
|
759
|
-
* Returned when validation fails, contains debugging information.
|
|
760
|
-
*/
|
|
761
|
-
interface IRiskRejectionResult {
|
|
762
|
-
/** Unique identifier for this rejection instance */
|
|
763
|
-
id: string | null;
|
|
764
|
-
/** Human-readable reason for rejection */
|
|
765
|
-
note: string;
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Risk validation function type.
|
|
769
|
-
* Returns null/void if validation passes, IRiskRejectionResult if validation fails.
|
|
770
|
-
* Can also throw error which will be caught and converted to IRiskRejectionResult.
|
|
771
|
-
*/
|
|
772
|
-
interface IRiskValidationFn {
|
|
773
|
-
(payload: IRiskValidationPayload): RiskRejection | Promise<RiskRejection>;
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* Risk validation configuration.
|
|
777
|
-
* Defines validation logic with optional documentation.
|
|
778
|
-
*/
|
|
779
|
-
interface IRiskValidation {
|
|
780
1003
|
/**
|
|
781
|
-
*
|
|
1004
|
+
* Strategy name that is monitoring this active pending signal.
|
|
1005
|
+
* Identifies which strategy execution this ping event belongs to.
|
|
782
1006
|
*/
|
|
783
|
-
|
|
1007
|
+
strategyName: StrategyName;
|
|
784
1008
|
/**
|
|
785
|
-
*
|
|
786
|
-
*
|
|
1009
|
+
* Exchange name where this active pending signal is being monitored.
|
|
1010
|
+
* Identifies which exchange this ping event belongs to.
|
|
787
1011
|
*/
|
|
788
|
-
note?: string;
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Risk schema registered via addRisk().
|
|
792
|
-
* Defines portfolio-level risk controls via custom validations.
|
|
793
|
-
*/
|
|
794
|
-
interface IRiskSchema {
|
|
795
|
-
/** Unique risk profile identifier */
|
|
796
|
-
riskName: RiskName;
|
|
797
|
-
/** Optional developer note for documentation */
|
|
798
|
-
note?: string;
|
|
799
|
-
/** Optional lifecycle event callbacks (onRejected, onAllowed) */
|
|
800
|
-
callbacks?: Partial<IRiskCallbacks>;
|
|
801
|
-
/** Custom validations array for risk logic */
|
|
802
|
-
validations: (IRiskValidation | IRiskValidationFn)[];
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* Risk parameters passed to ClientRisk constructor.
|
|
806
|
-
* Combines schema with runtime dependencies and emission callbacks.
|
|
807
|
-
*/
|
|
808
|
-
interface IRiskParams extends IRiskSchema {
|
|
809
|
-
/** Exchange name (e.g., "binance") */
|
|
810
1012
|
exchangeName: ExchangeName;
|
|
811
|
-
/** Logger service for debug output */
|
|
812
|
-
logger: ILogger;
|
|
813
|
-
/** True if backtest mode, false if live mode */
|
|
814
|
-
backtest: boolean;
|
|
815
1013
|
/**
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
* Used for event emission to riskSubject (separate from schema callbacks).
|
|
819
|
-
*
|
|
820
|
-
* @param symbol - Trading pair symbol
|
|
821
|
-
* @param params - Risk check arguments
|
|
822
|
-
* @param activePositionCount - Number of active positions at rejection time
|
|
823
|
-
* @param rejectionResult - Rejection result with id and note
|
|
824
|
-
* @param timestamp - Event timestamp in milliseconds
|
|
825
|
-
* @param backtest - True if backtest mode, false if live mode
|
|
1014
|
+
* Complete pending signal row data.
|
|
1015
|
+
* Contains all signal information: id, position, priceOpen, priceTakeProfit, priceStopLoss, etc.
|
|
826
1016
|
*/
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
/**
|
|
830
|
-
* Risk interface implemented by ClientRisk.
|
|
831
|
-
* Provides risk checking for signals and position tracking.
|
|
832
|
-
*/
|
|
833
|
-
interface IRisk {
|
|
1017
|
+
data: ISignalRow;
|
|
834
1018
|
/**
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
* @returns Promise resolving to risk check result
|
|
1019
|
+
* Execution mode flag.
|
|
1020
|
+
* - true: Event from backtest execution (historical candle data)
|
|
1021
|
+
* - false: Event from live trading (real-time tick)
|
|
839
1022
|
*/
|
|
840
|
-
|
|
1023
|
+
backtest: boolean;
|
|
841
1024
|
/**
|
|
842
|
-
*
|
|
1025
|
+
* Event timestamp in milliseconds since Unix epoch.
|
|
843
1026
|
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*/
|
|
848
|
-
addSignal: (symbol: string, context: {
|
|
849
|
-
strategyName: StrategyName;
|
|
850
|
-
riskName: RiskName;
|
|
851
|
-
exchangeName: ExchangeName;
|
|
852
|
-
frameName: FrameName;
|
|
853
|
-
}, positionData: {
|
|
854
|
-
position: "long" | "short";
|
|
855
|
-
priceOpen: number;
|
|
856
|
-
priceStopLoss: number;
|
|
857
|
-
priceTakeProfit: number;
|
|
858
|
-
minuteEstimatedTime: number;
|
|
859
|
-
openTimestamp: number;
|
|
860
|
-
}) => Promise<void>;
|
|
861
|
-
/**
|
|
862
|
-
* Remove a closed signal/position.
|
|
1027
|
+
* Timing semantics:
|
|
1028
|
+
* - Live mode: when.getTime() at the moment of ping
|
|
1029
|
+
* - Backtest mode: candle.timestamp of the candle being processed
|
|
863
1030
|
*
|
|
864
|
-
* @
|
|
865
|
-
*
|
|
1031
|
+
* @example
|
|
1032
|
+
* ```typescript
|
|
1033
|
+
* const eventDate = new Date(event.timestamp);
|
|
1034
|
+
* console.log(`Active Ping at: ${eventDate.toISOString()}`);
|
|
1035
|
+
* ```
|
|
866
1036
|
*/
|
|
867
|
-
|
|
868
|
-
strategyName: StrategyName;
|
|
869
|
-
riskName: RiskName;
|
|
870
|
-
exchangeName: ExchangeName;
|
|
871
|
-
frameName: FrameName;
|
|
872
|
-
}) => Promise<void>;
|
|
1037
|
+
timestamp: number;
|
|
873
1038
|
}
|
|
874
|
-
/**
|
|
875
|
-
* Unique risk profile identifier.
|
|
876
|
-
*/
|
|
877
|
-
type RiskName = string;
|
|
878
1039
|
|
|
879
1040
|
/**
|
|
880
|
-
*
|
|
881
|
-
* Represents 10%, 20%, 30%, ..., 100% profit or loss thresholds.
|
|
1041
|
+
* Contract for risk rejection events.
|
|
882
1042
|
*
|
|
883
|
-
*
|
|
884
|
-
*
|
|
1043
|
+
* Emitted by riskSubject ONLY when a signal is REJECTED due to risk validation failure.
|
|
1044
|
+
* Used for tracking actual risk violations and monitoring rejected signals.
|
|
1045
|
+
*
|
|
1046
|
+
* Events are emitted only when risk limits are violated (not for allowed signals).
|
|
1047
|
+
* This prevents spam and allows focusing on actual risk management interventions.
|
|
1048
|
+
*
|
|
1049
|
+
* Consumers:
|
|
1050
|
+
* - RiskMarkdownService: Accumulates rejection events for report generation
|
|
1051
|
+
* - User callbacks via listenRisk() / listenRiskOnce()
|
|
885
1052
|
*
|
|
886
1053
|
* @example
|
|
887
1054
|
* ```typescript
|
|
888
|
-
*
|
|
889
|
-
* ```
|
|
890
|
-
*/
|
|
891
|
-
type PartialLevel = 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100;
|
|
892
|
-
/**
|
|
893
|
-
* Serializable partial data for persistence layer.
|
|
894
|
-
* Converts Sets to arrays for JSON serialization.
|
|
1055
|
+
* import { listenRisk } from "backtest-kit";
|
|
895
1056
|
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
1057
|
+
* // Listen to all risk rejection events
|
|
1058
|
+
* listenRisk((event) => {
|
|
1059
|
+
* console.log(`[RISK REJECTED] Signal for ${event.symbol}`);
|
|
1060
|
+
* console.log(`Strategy: ${event.strategyName}`);
|
|
1061
|
+
* console.log(`Active positions: ${event.activePositionCount}`);
|
|
1062
|
+
* console.log(`Price: ${event.currentPrice}`);
|
|
1063
|
+
* console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
|
|
1064
|
+
* });
|
|
1065
|
+
*
|
|
1066
|
+
* // Alert on risk rejections for specific symbol
|
|
1067
|
+
* listenRisk((event) => {
|
|
1068
|
+
* if (event.symbol === "BTCUSDT") {
|
|
1069
|
+
* console.warn("BTC signal rejected due to risk limits!");
|
|
1070
|
+
* }
|
|
1071
|
+
* });
|
|
1072
|
+
* ```
|
|
898
1073
|
*/
|
|
899
|
-
interface
|
|
1074
|
+
interface RiskContract {
|
|
900
1075
|
/**
|
|
901
|
-
*
|
|
902
|
-
*
|
|
1076
|
+
* Trading pair symbol (e.g., "BTCUSDT").
|
|
1077
|
+
* Identifies which market this rejected signal belongs to.
|
|
903
1078
|
*/
|
|
904
|
-
|
|
1079
|
+
symbol: string;
|
|
905
1080
|
/**
|
|
906
|
-
*
|
|
907
|
-
*
|
|
1081
|
+
* Pending signal to apply.
|
|
1082
|
+
* Contains signal details (position, priceOpen, priceTakeProfit, priceStopLoss, etc).
|
|
908
1083
|
*/
|
|
909
|
-
|
|
910
|
-
}
|
|
911
|
-
/**
|
|
912
|
-
* Partial profit/loss tracking interface.
|
|
913
|
-
* Implemented by ClientPartial and PartialConnectionService.
|
|
914
|
-
*
|
|
915
|
-
* Tracks profit/loss level milestones for active trading signals.
|
|
916
|
-
* Emits events when signals reach 10%, 20%, 30%, etc profit or loss.
|
|
917
|
-
*
|
|
918
|
-
* @example
|
|
919
|
-
* ```typescript
|
|
920
|
-
* import { ClientPartial } from "./client/ClientPartial";
|
|
921
|
-
*
|
|
922
|
-
* const partial = new ClientPartial({
|
|
923
|
-
* logger: loggerService,
|
|
924
|
-
* onProfit: (symbol, data, price, level, backtest, timestamp) => {
|
|
925
|
-
* console.log(`Signal ${data.id} reached ${level}% profit`);
|
|
926
|
-
* },
|
|
927
|
-
* onLoss: (symbol, data, price, level, backtest, timestamp) => {
|
|
928
|
-
* console.log(`Signal ${data.id} reached ${level}% loss`);
|
|
929
|
-
* }
|
|
930
|
-
* });
|
|
931
|
-
*
|
|
932
|
-
* await partial.waitForInit("BTCUSDT");
|
|
933
|
-
*
|
|
934
|
-
* // During signal monitoring
|
|
935
|
-
* await partial.profit("BTCUSDT", signal, 51000, 15.5, false, new Date());
|
|
936
|
-
* // Emits event when reaching 10% profit milestone
|
|
937
|
-
*
|
|
938
|
-
* // When signal closes
|
|
939
|
-
* await partial.clear("BTCUSDT", signal, 52000);
|
|
940
|
-
* ```
|
|
941
|
-
*/
|
|
942
|
-
interface IPartial {
|
|
1084
|
+
currentSignal: ISignalDto;
|
|
943
1085
|
/**
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
* Called by ClientStrategy during signal monitoring when revenuePercent > 0.
|
|
947
|
-
* Checks which profit levels (10%, 20%, 30%, etc) have been reached
|
|
948
|
-
* and emits events for new levels only (Set-based deduplication).
|
|
949
|
-
*
|
|
950
|
-
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
951
|
-
* @param data - Signal row data
|
|
952
|
-
* @param currentPrice - Current market price
|
|
953
|
-
* @param revenuePercent - Current profit percentage (positive value)
|
|
954
|
-
* @param backtest - True if backtest mode, false if live mode
|
|
955
|
-
* @param when - Event timestamp (current time for live, candle time for backtest)
|
|
956
|
-
* @returns Promise that resolves when profit processing is complete
|
|
957
|
-
*
|
|
958
|
-
* @example
|
|
959
|
-
* ```typescript
|
|
960
|
-
* // Signal opened at $50000, current price $51500
|
|
961
|
-
* // Revenue: 3% profit
|
|
962
|
-
* await partial.profit("BTCUSDT", signal, 51500, 3.0, false, new Date());
|
|
963
|
-
* // No events emitted (below 10% threshold)
|
|
964
|
-
*
|
|
965
|
-
* // Price rises to $55000
|
|
966
|
-
* // Revenue: 10% profit
|
|
967
|
-
* await partial.profit("BTCUSDT", signal, 55000, 10.0, false, new Date());
|
|
968
|
-
* // Emits partialProfitSubject event for 10% level
|
|
969
|
-
*
|
|
970
|
-
* // Price rises to $61000
|
|
971
|
-
* // Revenue: 22% profit
|
|
972
|
-
* await partial.profit("BTCUSDT", signal, 61000, 22.0, false, new Date());
|
|
973
|
-
* // Emits events for 20% level only (10% already emitted)
|
|
974
|
-
* ```
|
|
1086
|
+
* Strategy name requesting to open a position.
|
|
1087
|
+
* Identifies which strategy attempted to create the signal.
|
|
975
1088
|
*/
|
|
976
|
-
|
|
1089
|
+
strategyName: StrategyName;
|
|
977
1090
|
/**
|
|
978
|
-
*
|
|
979
|
-
*
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
*
|
|
989
|
-
*
|
|
990
|
-
|
|
1091
|
+
* Frame name used in backtest execution.
|
|
1092
|
+
* Identifies which frame this signal was for in backtest execution.
|
|
1093
|
+
*/
|
|
1094
|
+
frameName: FrameName;
|
|
1095
|
+
/**
|
|
1096
|
+
* Exchange name.
|
|
1097
|
+
* Identifies which exchange this signal was for.
|
|
1098
|
+
*/
|
|
1099
|
+
exchangeName: ExchangeName;
|
|
1100
|
+
/**
|
|
1101
|
+
* Current VWAP price at the time of rejection.
|
|
1102
|
+
* Market price when risk check was performed.
|
|
1103
|
+
*/
|
|
1104
|
+
currentPrice: number;
|
|
1105
|
+
/**
|
|
1106
|
+
* Number of currently active positions across all strategies at rejection time.
|
|
1107
|
+
* Used to track portfolio-level exposure when signal was rejected.
|
|
1108
|
+
*/
|
|
1109
|
+
activePositionCount: number;
|
|
1110
|
+
/**
|
|
1111
|
+
* Unique identifier for this rejection instance.
|
|
1112
|
+
* Generated by ClientRisk for tracking and debugging purposes.
|
|
1113
|
+
* Null if validation threw exception without custom ID.
|
|
1114
|
+
*/
|
|
1115
|
+
rejectionId: string | null;
|
|
1116
|
+
/**
|
|
1117
|
+
* Human-readable reason why the signal was rejected.
|
|
1118
|
+
* Captured from IRiskValidation.note or error message.
|
|
991
1119
|
*
|
|
992
1120
|
* @example
|
|
993
1121
|
* ```typescript
|
|
994
|
-
*
|
|
995
|
-
* //
|
|
996
|
-
* await partial.loss("BTCUSDT", signal, 48000, -4.0, false, new Date());
|
|
997
|
-
* // No events emitted (below -10% threshold)
|
|
998
|
-
*
|
|
999
|
-
* // Price drops to $45000
|
|
1000
|
-
* // Loss: -10% loss
|
|
1001
|
-
* await partial.loss("BTCUSDT", signal, 45000, -10.0, false, new Date());
|
|
1002
|
-
* // Emits partialLossSubject event for 10% level
|
|
1003
|
-
*
|
|
1004
|
-
* // Price drops to $39000
|
|
1005
|
-
* // Loss: -22% loss
|
|
1006
|
-
* await partial.loss("BTCUSDT", signal, 39000, -22.0, false, new Date());
|
|
1007
|
-
* // Emits events for 20% level only (10% already emitted)
|
|
1122
|
+
* console.log(`Rejection reason: ${event.rejectionNote}`);
|
|
1123
|
+
* // Output: "Rejection reason: Max 3 positions allowed"
|
|
1008
1124
|
* ```
|
|
1009
1125
|
*/
|
|
1010
|
-
|
|
1126
|
+
rejectionNote: string;
|
|
1011
1127
|
/**
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
* Called by ClientStrategy when signal completes (TP/SL/time_expired).
|
|
1015
|
-
* Removes signal state from memory and persists changes to disk.
|
|
1016
|
-
* Cleans up memoized ClientPartial instance in PartialConnectionService.
|
|
1017
|
-
*
|
|
1018
|
-
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
1019
|
-
* @param data - Signal row data
|
|
1020
|
-
* @param priceClose - Final closing price
|
|
1021
|
-
* @returns Promise that resolves when clear is complete
|
|
1128
|
+
* Event timestamp in milliseconds since Unix epoch.
|
|
1129
|
+
* Represents when the signal was rejected.
|
|
1022
1130
|
*
|
|
1023
1131
|
* @example
|
|
1024
1132
|
* ```typescript
|
|
1025
|
-
*
|
|
1026
|
-
*
|
|
1027
|
-
* // State removed from _states Map
|
|
1028
|
-
* // Persisted to disk without this signal's data
|
|
1029
|
-
* // Memoized instance cleared from getPartial cache
|
|
1133
|
+
* const eventDate = new Date(event.timestamp);
|
|
1134
|
+
* console.log(`Signal rejected at: ${eventDate.toISOString()}`);
|
|
1030
1135
|
* ```
|
|
1031
1136
|
*/
|
|
1032
|
-
|
|
1137
|
+
timestamp: number;
|
|
1138
|
+
/**
|
|
1139
|
+
* Whether this event is from backtest mode (true) or live mode (false).
|
|
1140
|
+
* Used to separate backtest and live risk rejection tracking.
|
|
1141
|
+
*/
|
|
1142
|
+
backtest: boolean;
|
|
1033
1143
|
}
|
|
1034
1144
|
|
|
1035
1145
|
/**
|
|
1036
|
-
*
|
|
1037
|
-
* Converts state to simple boolean for JSON serialization.
|
|
1146
|
+
* Constructor type for action handlers with strategy context.
|
|
1038
1147
|
*
|
|
1039
|
-
*
|
|
1040
|
-
*
|
|
1148
|
+
* @param strategyName - Strategy identifier (e.g., "rsi_divergence", "macd_cross")
|
|
1149
|
+
* @param frameName - Timeframe identifier (e.g., "1m", "5m", "1h")
|
|
1150
|
+
* @param backtest - True for backtest mode, false for live trading
|
|
1151
|
+
* @returns Partial implementation of IAction (only required handlers)
|
|
1152
|
+
*
|
|
1153
|
+
* @example
|
|
1154
|
+
* ```typescript
|
|
1155
|
+
* class TelegramNotifier implements Partial<IAction> {
|
|
1156
|
+
* constructor(
|
|
1157
|
+
* private strategyName: StrategyName,
|
|
1158
|
+
* private frameName: FrameName,
|
|
1159
|
+
* private backtest: boolean
|
|
1160
|
+
* ) {}
|
|
1161
|
+
*
|
|
1162
|
+
* signal(event: IStrategyTickResult): void {
|
|
1163
|
+
* if (!this.backtest && event.state === 'opened') {
|
|
1164
|
+
* telegram.send(`[${this.strategyName}/${this.frameName}] New signal`);
|
|
1165
|
+
* }
|
|
1166
|
+
* }
|
|
1167
|
+
* }
|
|
1168
|
+
*
|
|
1169
|
+
* const actionCtors: TActionCtor[] = [TelegramNotifier, ReduxLogger];
|
|
1170
|
+
* ```
|
|
1041
1171
|
*/
|
|
1042
|
-
|
|
1043
|
-
/**
|
|
1044
|
-
* Whether breakeven has been reached for this signal.
|
|
1045
|
-
* Serialized form of IBreakevenState.reached.
|
|
1046
|
-
*/
|
|
1047
|
-
reached: boolean;
|
|
1048
|
-
}
|
|
1172
|
+
type TActionCtor = new (strategyName: StrategyName, frameName: FrameName, actionName: ActionName, backtest: boolean) => Partial<IPublicAction>;
|
|
1049
1173
|
/**
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
1174
|
+
* Action parameters passed to ClientAction constructor.
|
|
1175
|
+
* Combines schema with runtime dependencies and execution context.
|
|
1052
1176
|
*
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
1177
|
+
* Extended from IActionSchema with:
|
|
1178
|
+
* - Logger instance for debugging and monitoring
|
|
1179
|
+
* - Strategy context (strategyName, frameName)
|
|
1180
|
+
* - Runtime environment flags
|
|
1055
1181
|
*
|
|
1056
1182
|
* @example
|
|
1057
1183
|
* ```typescript
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1184
|
+
* const params: IActionParams = {
|
|
1185
|
+
* actionName: "telegram-notifier",
|
|
1186
|
+
* handler: TelegramNotifier,
|
|
1187
|
+
* callbacks: { onInit, onDispose, onSignal },
|
|
1061
1188
|
* logger: loggerService,
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
* });
|
|
1189
|
+
* strategyName: "rsi_divergence",
|
|
1190
|
+
* frameName: "1h"
|
|
1191
|
+
* };
|
|
1066
1192
|
*
|
|
1067
|
-
*
|
|
1193
|
+
* const actionClient = new ClientAction(params);
|
|
1194
|
+
* ```
|
|
1195
|
+
*/
|
|
1196
|
+
interface IActionParams extends IActionSchema {
|
|
1197
|
+
/** Logger service for debugging and monitoring action execution */
|
|
1198
|
+
logger: ILogger;
|
|
1199
|
+
/** Strategy identifier this action is attached to */
|
|
1200
|
+
strategyName: StrategyName;
|
|
1201
|
+
/** Exchange name (e.g., "binance") */
|
|
1202
|
+
exchangeName: ExchangeName;
|
|
1203
|
+
/** Timeframe identifier this action is attached to */
|
|
1204
|
+
frameName: FrameName;
|
|
1205
|
+
/** Whether running in backtest mode */
|
|
1206
|
+
backtest: boolean;
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Lifecycle and event callbacks for action handlers.
|
|
1068
1210
|
*
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
1071
|
-
* // Emits event when threshold reached and SL moved to entry
|
|
1211
|
+
* Provides hooks for initialization, disposal, and event handling.
|
|
1212
|
+
* All callbacks are optional and support both sync and async execution.
|
|
1072
1213
|
*
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
1214
|
+
* Use cases:
|
|
1215
|
+
* - Resource initialization (database connections, file handles)
|
|
1216
|
+
* - Resource cleanup (close connections, flush buffers)
|
|
1217
|
+
* - Event logging and monitoring
|
|
1218
|
+
* - State persistence
|
|
1219
|
+
*
|
|
1220
|
+
* @example
|
|
1221
|
+
* ```typescript
|
|
1222
|
+
* const callbacks: IActionCallbacks = {
|
|
1223
|
+
* onInit: async (strategyName, frameName, backtest) => {
|
|
1224
|
+
* console.log(`[${strategyName}/${frameName}] Action initialized (backtest=${backtest})`);
|
|
1225
|
+
* await db.connect();
|
|
1226
|
+
* },
|
|
1227
|
+
* onSignal: (event, strategyName, frameName, backtest) => {
|
|
1228
|
+
* if (event.action === 'opened') {
|
|
1229
|
+
* console.log(`New signal opened: ${event.signal.id}`);
|
|
1230
|
+
* }
|
|
1231
|
+
* },
|
|
1232
|
+
* onDispose: async (strategyName, frameName, backtest) => {
|
|
1233
|
+
* await db.disconnect();
|
|
1234
|
+
* console.log(`[${strategyName}/${frameName}] Action disposed`);
|
|
1235
|
+
* }
|
|
1236
|
+
* };
|
|
1075
1237
|
* ```
|
|
1076
1238
|
*/
|
|
1077
|
-
interface
|
|
1239
|
+
interface IActionCallbacks {
|
|
1078
1240
|
/**
|
|
1079
|
-
*
|
|
1241
|
+
* Called when action handler is initialized.
|
|
1080
1242
|
*
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1083
|
-
*
|
|
1084
|
-
*
|
|
1085
|
-
*
|
|
1086
|
-
*
|
|
1087
|
-
* If all conditions met:
|
|
1088
|
-
* - Marks breakeven as reached
|
|
1089
|
-
* - Calls onBreakeven callback (emits to breakevenSubject)
|
|
1090
|
-
* - Persists state to disk
|
|
1091
|
-
*
|
|
1092
|
-
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
1093
|
-
* @param data - Signal row data
|
|
1094
|
-
* @param currentPrice - Current market price
|
|
1095
|
-
* @param backtest - True if backtest mode, false if live mode
|
|
1096
|
-
* @param when - Event timestamp (current time for live, candle time for backtest)
|
|
1097
|
-
* @returns Promise that resolves when breakeven check is complete
|
|
1243
|
+
* Use for:
|
|
1244
|
+
* - Opening database connections
|
|
1245
|
+
* - Initializing external services
|
|
1246
|
+
* - Loading persisted state
|
|
1247
|
+
* - Setting up subscriptions
|
|
1098
1248
|
*
|
|
1099
|
-
* @
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1103
|
-
|
|
1104
|
-
|
|
1249
|
+
* @param actionName - Action identifier
|
|
1250
|
+
* @param strategyName - Strategy identifier
|
|
1251
|
+
* @param frameName - Timeframe identifier
|
|
1252
|
+
* @param backtest - True for backtest mode, false for live trading
|
|
1253
|
+
*/
|
|
1254
|
+
onInit(actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1255
|
+
/**
|
|
1256
|
+
* Called when action handler is disposed.
|
|
1105
1257
|
*
|
|
1106
|
-
*
|
|
1107
|
-
*
|
|
1108
|
-
*
|
|
1258
|
+
* Use for:
|
|
1259
|
+
* - Closing database connections
|
|
1260
|
+
* - Flushing buffers
|
|
1261
|
+
* - Saving state to disk
|
|
1262
|
+
* - Unsubscribing from observables
|
|
1109
1263
|
*
|
|
1110
|
-
*
|
|
1111
|
-
*
|
|
1112
|
-
*
|
|
1113
|
-
*
|
|
1264
|
+
* @param actionName - Action identifier
|
|
1265
|
+
* @param strategyName - Strategy identifier
|
|
1266
|
+
* @param frameName - Timeframe identifier
|
|
1267
|
+
* @param backtest - True for backtest mode, false for live trading
|
|
1114
1268
|
*/
|
|
1115
|
-
|
|
1269
|
+
onDispose(actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1116
1270
|
/**
|
|
1117
|
-
*
|
|
1118
|
-
*
|
|
1119
|
-
* Called by ClientStrategy when signal completes (TP/SL/time_expired).
|
|
1120
|
-
* Removes signal state from memory and persists changes to disk.
|
|
1121
|
-
* Cleans up memoized ClientBreakeven instance in BreakevenConnectionService.
|
|
1271
|
+
* Called on signal events from all modes (live + backtest).
|
|
1122
1272
|
*
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1125
|
-
* @param priceClose - Final closing price
|
|
1126
|
-
* @param backtest - True if backtest mode, false if live mode
|
|
1127
|
-
* @returns Promise that resolves when clear is complete
|
|
1273
|
+
* Triggered by: StrategyConnectionService via signalEmitter
|
|
1274
|
+
* Frequency: Every tick/candle when strategy is evaluated
|
|
1128
1275
|
*
|
|
1129
|
-
* @
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1134
|
-
* // Persisted to disk without this signal's data
|
|
1135
|
-
* // Memoized instance cleared from getBreakeven cache
|
|
1136
|
-
* ```
|
|
1276
|
+
* @param event - Signal state result (idle, scheduled, opened, active, closed, cancelled)
|
|
1277
|
+
* @param actionName - Action identifier
|
|
1278
|
+
* @param strategyName - Strategy identifier
|
|
1279
|
+
* @param frameName - Timeframe identifier
|
|
1280
|
+
* @param backtest - True for backtest mode, false for live trading
|
|
1137
1281
|
*/
|
|
1138
|
-
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
/**
|
|
1142
|
-
* Contract for breakeven events.
|
|
1143
|
-
*
|
|
1144
|
-
* Emitted by breakevenSubject when a signal's stop-loss is moved to breakeven (entry price).
|
|
1145
|
-
* Used for tracking risk reduction milestones and monitoring strategy safety.
|
|
1146
|
-
*
|
|
1147
|
-
* Events are emitted only once per signal (idempotent - protected by ClientBreakeven state).
|
|
1148
|
-
* Breakeven is triggered when price moves far enough in profit direction to cover transaction costs.
|
|
1149
|
-
*
|
|
1150
|
-
* Consumers:
|
|
1151
|
-
* - BreakevenMarkdownService: Accumulates events for report generation
|
|
1152
|
-
* - User callbacks via listenBreakeven() / listenBreakevenOnce()
|
|
1153
|
-
*
|
|
1154
|
-
* @example
|
|
1155
|
-
* ```typescript
|
|
1156
|
-
* import { listenBreakeven } from "backtest-kit";
|
|
1157
|
-
*
|
|
1158
|
-
* // Listen to all breakeven events
|
|
1159
|
-
* listenBreakeven((event) => {
|
|
1160
|
-
* console.log(`[${event.backtest ? "Backtest" : "Live"}] Signal ${event.data.id} moved to breakeven`);
|
|
1161
|
-
* console.log(`Symbol: ${event.symbol}, Price: ${event.currentPrice}`);
|
|
1162
|
-
* console.log(`Position: ${event.data.position}, Entry: ${event.data.priceOpen}`);
|
|
1163
|
-
* console.log(`Original SL: ${event.data.priceStopLoss}, New SL: ${event.data.priceOpen}`);
|
|
1164
|
-
* });
|
|
1165
|
-
*
|
|
1166
|
-
* // Wait for specific signal to reach breakeven
|
|
1167
|
-
* listenBreakevenOnce(
|
|
1168
|
-
* (event) => event.data.id === "target-signal-id",
|
|
1169
|
-
* (event) => console.log("Signal reached breakeven:", event.data.id)
|
|
1170
|
-
* );
|
|
1171
|
-
* ```
|
|
1172
|
-
*/
|
|
1173
|
-
interface BreakevenContract {
|
|
1282
|
+
onSignal(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1174
1283
|
/**
|
|
1175
|
-
*
|
|
1176
|
-
*
|
|
1284
|
+
* Called on signal events from live trading only.
|
|
1285
|
+
*
|
|
1286
|
+
* Triggered by: StrategyConnectionService via signalLiveEmitter
|
|
1287
|
+
* Frequency: Every tick in live mode
|
|
1288
|
+
*
|
|
1289
|
+
* @param event - Signal state result from live trading
|
|
1290
|
+
* @param actionName - Action identifier
|
|
1291
|
+
* @param strategyName - Strategy identifier
|
|
1292
|
+
* @param frameName - Timeframe identifier
|
|
1293
|
+
* @param backtest - Always false (live mode only)
|
|
1177
1294
|
*/
|
|
1178
|
-
|
|
1295
|
+
onSignalLive(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1179
1296
|
/**
|
|
1180
|
-
*
|
|
1181
|
-
*
|
|
1297
|
+
* Called on signal events from backtest only.
|
|
1298
|
+
*
|
|
1299
|
+
* Triggered by: StrategyConnectionService via signalBacktestEmitter
|
|
1300
|
+
* Frequency: Every candle in backtest mode
|
|
1301
|
+
*
|
|
1302
|
+
* @param event - Signal state result from backtest
|
|
1303
|
+
* @param actionName - Action identifier
|
|
1304
|
+
* @param strategyName - Strategy identifier
|
|
1305
|
+
* @param frameName - Timeframe identifier
|
|
1306
|
+
* @param backtest - Always true (backtest mode only)
|
|
1182
1307
|
*/
|
|
1183
|
-
strategyName: StrategyName
|
|
1308
|
+
onSignalBacktest(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1184
1309
|
/**
|
|
1185
|
-
*
|
|
1186
|
-
*
|
|
1310
|
+
* Called when breakeven is triggered (stop-loss moved to entry price).
|
|
1311
|
+
*
|
|
1312
|
+
* Triggered by: BreakevenConnectionService via breakevenSubject
|
|
1313
|
+
* Frequency: Once per signal when breakeven threshold is reached
|
|
1314
|
+
*
|
|
1315
|
+
* @param event - Breakeven milestone data
|
|
1316
|
+
* @param actionName - Action identifier
|
|
1317
|
+
* @param strategyName - Strategy identifier
|
|
1318
|
+
* @param frameName - Timeframe identifier
|
|
1319
|
+
* @param backtest - True for backtest mode, false for live trading
|
|
1187
1320
|
*/
|
|
1188
|
-
|
|
1321
|
+
onBreakevenAvailable(event: BreakevenContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1189
1322
|
/**
|
|
1190
|
-
*
|
|
1191
|
-
*
|
|
1323
|
+
* Called when partial profit level is reached (10%, 20%, 30%, etc).
|
|
1324
|
+
*
|
|
1325
|
+
* Triggered by: PartialConnectionService via partialProfitSubject
|
|
1326
|
+
* Frequency: Once per profit level per signal (deduplicated)
|
|
1327
|
+
*
|
|
1328
|
+
* @param event - Profit milestone data with level and price
|
|
1329
|
+
* @param actionName - Action identifier
|
|
1330
|
+
* @param strategyName - Strategy identifier
|
|
1331
|
+
* @param frameName - Timeframe identifier
|
|
1332
|
+
* @param backtest - True for backtest mode, false for live trading
|
|
1192
1333
|
*/
|
|
1193
|
-
frameName: FrameName
|
|
1334
|
+
onPartialProfitAvailable(event: PartialProfitContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1194
1335
|
/**
|
|
1195
|
-
*
|
|
1196
|
-
*
|
|
1336
|
+
* Called when partial loss level is reached (-10%, -20%, -30%, etc).
|
|
1337
|
+
*
|
|
1338
|
+
* Triggered by: PartialConnectionService via partialLossSubject
|
|
1339
|
+
* Frequency: Once per loss level per signal (deduplicated)
|
|
1340
|
+
*
|
|
1341
|
+
* @param event - Loss milestone data with level and price
|
|
1342
|
+
* @param actionName - Action identifier
|
|
1343
|
+
* @param strategyName - Strategy identifier
|
|
1344
|
+
* @param frameName - Timeframe identifier
|
|
1345
|
+
* @param backtest - True for backtest mode, false for live trading
|
|
1197
1346
|
*/
|
|
1198
|
-
|
|
1347
|
+
onPartialLossAvailable(event: PartialLossContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1199
1348
|
/**
|
|
1200
|
-
*
|
|
1201
|
-
*
|
|
1349
|
+
* Called during scheduled signal monitoring (every minute while waiting for activation).
|
|
1350
|
+
*
|
|
1351
|
+
* Triggered by: StrategyConnectionService via schedulePingSubject
|
|
1352
|
+
* Frequency: Every minute while scheduled signal is waiting
|
|
1353
|
+
*
|
|
1354
|
+
* @param event - Scheduled signal monitoring data
|
|
1355
|
+
* @param actionName - Action identifier
|
|
1356
|
+
* @param strategyName - Strategy identifier
|
|
1357
|
+
* @param frameName - Timeframe identifier
|
|
1358
|
+
* @param backtest - True for backtest mode, false for live trading
|
|
1202
1359
|
*/
|
|
1203
|
-
|
|
1360
|
+
onPingScheduled(event: SchedulePingContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1204
1361
|
/**
|
|
1205
|
-
*
|
|
1206
|
-
*
|
|
1207
|
-
*
|
|
1362
|
+
* Called during active pending signal monitoring (every minute while position is active).
|
|
1363
|
+
*
|
|
1364
|
+
* Triggered by: StrategyConnectionService via activePingSubject
|
|
1365
|
+
* Frequency: Every minute while pending signal is active
|
|
1366
|
+
*
|
|
1367
|
+
* @param event - Active pending signal monitoring data
|
|
1368
|
+
* @param actionName - Action identifier
|
|
1369
|
+
* @param strategyName - Strategy identifier
|
|
1370
|
+
* @param frameName - Timeframe identifier
|
|
1371
|
+
* @param backtest - True for backtest mode, false for live trading
|
|
1208
1372
|
*/
|
|
1209
|
-
backtest: boolean
|
|
1373
|
+
onPingActive(event: ActivePingContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1210
1374
|
/**
|
|
1211
|
-
*
|
|
1375
|
+
* Called when signal is rejected by risk management.
|
|
1212
1376
|
*
|
|
1213
|
-
*
|
|
1214
|
-
*
|
|
1215
|
-
* - Backtest mode: candle.timestamp of the candle that triggered breakeven
|
|
1377
|
+
* Triggered by: RiskConnectionService via riskSubject
|
|
1378
|
+
* Frequency: Only when signal fails risk validation (not emitted for allowed signals)
|
|
1216
1379
|
*
|
|
1217
|
-
* @
|
|
1218
|
-
*
|
|
1219
|
-
*
|
|
1220
|
-
*
|
|
1221
|
-
*
|
|
1380
|
+
* @param event - Risk rejection data with reason and context
|
|
1381
|
+
* @param actionName - Action identifier
|
|
1382
|
+
* @param strategyName - Strategy identifier
|
|
1383
|
+
* @param frameName - Timeframe identifier
|
|
1384
|
+
* @param backtest - True for backtest mode, false for live trading
|
|
1222
1385
|
*/
|
|
1223
|
-
|
|
1386
|
+
onRiskRejection(event: RiskContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1224
1387
|
}
|
|
1225
|
-
|
|
1226
1388
|
/**
|
|
1227
|
-
*
|
|
1228
|
-
*
|
|
1229
|
-
* Emitted by partialProfitSubject when a signal reaches a profit level milestone (10%, 20%, 30%, etc).
|
|
1230
|
-
* Used for tracking partial take-profit execution and monitoring strategy performance.
|
|
1389
|
+
* Action schema registered via addActionSchema().
|
|
1390
|
+
* Defines event handler implementation and lifecycle callbacks for state management integration.
|
|
1231
1391
|
*
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
1392
|
+
* Actions provide a way to attach custom event handlers to strategies for:
|
|
1393
|
+
* - State management (Redux, Zustand, MobX)
|
|
1394
|
+
* - Event logging and monitoring
|
|
1395
|
+
* - Real-time notifications (Telegram, Discord, email)
|
|
1396
|
+
* - Analytics and metrics collection
|
|
1397
|
+
* - Custom business logic triggers
|
|
1234
1398
|
*
|
|
1235
|
-
*
|
|
1236
|
-
*
|
|
1237
|
-
* - User callbacks via listenPartialProfit() / listenPartialProfitOnce()
|
|
1399
|
+
* Each action instance is created per strategy-frame pair and receives all events
|
|
1400
|
+
* emitted during strategy execution. Multiple actions can be attached to a single strategy.
|
|
1238
1401
|
*
|
|
1239
1402
|
* @example
|
|
1240
1403
|
* ```typescript
|
|
1241
|
-
* import {
|
|
1404
|
+
* import { addActionSchema } from "backtest-kit";
|
|
1242
1405
|
*
|
|
1243
|
-
* //
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
*
|
|
1406
|
+
* // Define action handler class
|
|
1407
|
+
* class TelegramNotifier implements Partial<IAction> {
|
|
1408
|
+
* constructor(
|
|
1409
|
+
* private strategyName: StrategyName,
|
|
1410
|
+
* private frameName: FrameName,
|
|
1411
|
+
* private backtest: boolean
|
|
1412
|
+
* ) {}
|
|
1413
|
+
*
|
|
1414
|
+
* signal(event: IStrategyTickResult): void {
|
|
1415
|
+
* if (!this.backtest && event.action === 'opened') {
|
|
1416
|
+
* telegram.send(`[${this.strategyName}/${this.frameName}] New signal`);
|
|
1417
|
+
* }
|
|
1418
|
+
* }
|
|
1419
|
+
*
|
|
1420
|
+
* dispose(): void {
|
|
1421
|
+
* telegram.close();
|
|
1422
|
+
* }
|
|
1423
|
+
* }
|
|
1424
|
+
*
|
|
1425
|
+
* // Register action schema
|
|
1426
|
+
* addActionSchema({
|
|
1427
|
+
* actionName: "telegram-notifier",
|
|
1428
|
+
* handler: TelegramNotifier,
|
|
1429
|
+
* callbacks: {
|
|
1430
|
+
* onInit: async (strategyName, frameName, backtest) => {
|
|
1431
|
+
* console.log(`Telegram notifier initialized for ${strategyName}/${frameName}`);
|
|
1432
|
+
* },
|
|
1433
|
+
* onSignal: (event, strategyName, frameName, backtest) => {
|
|
1434
|
+
* console.log(`Signal event: ${event.action}`);
|
|
1435
|
+
* }
|
|
1436
|
+
* }
|
|
1248
1437
|
* });
|
|
1438
|
+
* ```
|
|
1439
|
+
*/
|
|
1440
|
+
interface IActionSchema {
|
|
1441
|
+
/** Unique action identifier for registration */
|
|
1442
|
+
actionName: ActionName;
|
|
1443
|
+
/** Optional developer note for documentation */
|
|
1444
|
+
note?: string;
|
|
1445
|
+
/** Action handler constructor (instantiated per strategy-frame pair) */
|
|
1446
|
+
handler: TActionCtor | Partial<IPublicAction>;
|
|
1447
|
+
/** Optional lifecycle and event callbacks */
|
|
1448
|
+
callbacks?: Partial<IActionCallbacks>;
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Public action interface for custom action handler implementations.
|
|
1249
1452
|
*
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
1253
|
-
*
|
|
1254
|
-
* )
|
|
1453
|
+
* Extends IAction with an initialization lifecycle method.
|
|
1454
|
+
* Action handlers implement this interface to receive strategy events and perform custom logic.
|
|
1455
|
+
*
|
|
1456
|
+
* Lifecycle:
|
|
1457
|
+
* 1. Constructor called with (strategyName, frameName, actionName)
|
|
1458
|
+
* 2. init() called once for async initialization (setup connections, load resources)
|
|
1459
|
+
* 3. Event methods called as strategy executes (signal, breakeven, partialProfit, etc.)
|
|
1460
|
+
* 4. dispose() called once for cleanup (close connections, flush buffers)
|
|
1461
|
+
*
|
|
1462
|
+
* Key features:
|
|
1463
|
+
* - init() for async initialization (database connections, API clients, file handles)
|
|
1464
|
+
* - All IAction methods available for event handling
|
|
1465
|
+
* - dispose() guaranteed to run exactly once via singleshot pattern
|
|
1466
|
+
*
|
|
1467
|
+
* Common use cases:
|
|
1468
|
+
* - State management: Redux/Zustand store integration
|
|
1469
|
+
* - Notifications: Telegram/Discord/Email alerts
|
|
1470
|
+
* - Logging: Custom event tracking and monitoring
|
|
1471
|
+
* - Analytics: Metrics collection and reporting
|
|
1472
|
+
* - External systems: Database writes, API calls, file operations
|
|
1473
|
+
*
|
|
1474
|
+
* @example
|
|
1475
|
+
* ```typescript
|
|
1476
|
+
* class TelegramNotifier implements Partial<IPublicAction> {
|
|
1477
|
+
* private bot: TelegramBot | null = null;
|
|
1478
|
+
*
|
|
1479
|
+
* constructor(
|
|
1480
|
+
* private strategyName: string,
|
|
1481
|
+
* private frameName: string,
|
|
1482
|
+
* private actionName: string
|
|
1483
|
+
* ) {}
|
|
1484
|
+
*
|
|
1485
|
+
* // Called once during initialization
|
|
1486
|
+
* async init() {
|
|
1487
|
+
* this.bot = new TelegramBot(process.env.TELEGRAM_TOKEN);
|
|
1488
|
+
* await this.bot.connect();
|
|
1489
|
+
* }
|
|
1490
|
+
*
|
|
1491
|
+
* // Called on every signal event
|
|
1492
|
+
* async signal(event: IStrategyTickResult) {
|
|
1493
|
+
* if (event.action === 'opened') {
|
|
1494
|
+
* await this.bot.send(
|
|
1495
|
+
* `[${this.strategyName}/${this.frameName}] Signal opened: ${event.signal.side}`
|
|
1496
|
+
* );
|
|
1497
|
+
* }
|
|
1498
|
+
* }
|
|
1499
|
+
*
|
|
1500
|
+
* // Called once during cleanup
|
|
1501
|
+
* async dispose() {
|
|
1502
|
+
* await this.bot?.disconnect();
|
|
1503
|
+
* this.bot = null;
|
|
1504
|
+
* }
|
|
1505
|
+
* }
|
|
1255
1506
|
* ```
|
|
1507
|
+
*
|
|
1508
|
+
* @see IAction for all available event methods
|
|
1509
|
+
* @see TActionCtor for constructor signature requirements
|
|
1510
|
+
* @see ClientAction for internal wrapper that manages lifecycle
|
|
1256
1511
|
*/
|
|
1257
|
-
|
|
1512
|
+
type IPublicAction = {
|
|
1513
|
+
[key in keyof IAction]?: IAction[key];
|
|
1514
|
+
} & {
|
|
1515
|
+
init?(): void | Promise<void>;
|
|
1516
|
+
};
|
|
1517
|
+
/**
|
|
1518
|
+
* Action interface for state manager integration.
|
|
1519
|
+
*
|
|
1520
|
+
* Provides methods to handle all events emitted by connection services.
|
|
1521
|
+
* Each method corresponds to a specific event type emitted via .next() calls.
|
|
1522
|
+
*
|
|
1523
|
+
* Use this interface to implement custom state management logic:
|
|
1524
|
+
* - Redux/Zustand action dispatchers
|
|
1525
|
+
* - Event logging systems
|
|
1526
|
+
* - Real-time monitoring dashboards
|
|
1527
|
+
* - Analytics and metrics collection
|
|
1528
|
+
*
|
|
1529
|
+
* @example
|
|
1530
|
+
* ```typescript
|
|
1531
|
+
* class ReduxStateManager implements IAction {
|
|
1532
|
+
* constructor(private store: Store) {}
|
|
1533
|
+
*
|
|
1534
|
+
* signal(event: IStrategyTickResult): void {
|
|
1535
|
+
* this.store.dispatch({ type: 'SIGNAL', payload: event });
|
|
1536
|
+
* }
|
|
1537
|
+
*
|
|
1538
|
+
* breakeven(event: BreakevenContract): void {
|
|
1539
|
+
* this.store.dispatch({ type: 'BREAKEVEN', payload: event });
|
|
1540
|
+
* }
|
|
1541
|
+
*
|
|
1542
|
+
* // ... implement other methods
|
|
1543
|
+
* }
|
|
1544
|
+
* ```
|
|
1545
|
+
*/
|
|
1546
|
+
interface IAction {
|
|
1258
1547
|
/**
|
|
1259
|
-
*
|
|
1260
|
-
*
|
|
1548
|
+
* Handles signal events from all modes (live + backtest).
|
|
1549
|
+
*
|
|
1550
|
+
* Emitted by: StrategyConnectionService via signalEmitter
|
|
1551
|
+
* Source: StrategyConnectionService.tick() and StrategyConnectionService.backtest()
|
|
1552
|
+
* Frequency: Every tick/candle when strategy is evaluated
|
|
1553
|
+
*
|
|
1554
|
+
* @param event - Signal state result (idle, scheduled, opened, active, closed, cancelled)
|
|
1261
1555
|
*/
|
|
1262
|
-
|
|
1556
|
+
signal(event: IStrategyTickResult): void | Promise<void>;
|
|
1263
1557
|
/**
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1558
|
+
* Handles signal events from live trading only.
|
|
1559
|
+
*
|
|
1560
|
+
* Emitted by: StrategyConnectionService via signalLiveEmitter
|
|
1561
|
+
* Source: StrategyConnectionService.tick() when backtest=false
|
|
1562
|
+
* Frequency: Every tick in live mode
|
|
1563
|
+
*
|
|
1564
|
+
* @param event - Signal state result from live trading
|
|
1266
1565
|
*/
|
|
1267
|
-
|
|
1566
|
+
signalLive(event: IStrategyTickResult): void | Promise<void>;
|
|
1268
1567
|
/**
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1568
|
+
* Handles signal events from backtest only.
|
|
1569
|
+
*
|
|
1570
|
+
* Emitted by: StrategyConnectionService via signalBacktestEmitter
|
|
1571
|
+
* Source: StrategyConnectionService.backtest() when backtest=true
|
|
1572
|
+
* Frequency: Every candle in backtest mode
|
|
1573
|
+
*
|
|
1574
|
+
* @param event - Signal state result from backtest
|
|
1271
1575
|
*/
|
|
1272
|
-
|
|
1576
|
+
signalBacktest(event: IStrategyTickResult): void | Promise<void>;
|
|
1273
1577
|
/**
|
|
1274
|
-
*
|
|
1275
|
-
*
|
|
1578
|
+
* Handles breakeven events when stop-loss is moved to entry price.
|
|
1579
|
+
*
|
|
1580
|
+
* Emitted by: BreakevenConnectionService via breakevenSubject
|
|
1581
|
+
* Source: COMMIT_BREAKEVEN_FN callback in BreakevenConnectionService
|
|
1582
|
+
* Frequency: Once per signal when breakeven threshold is reached
|
|
1583
|
+
*
|
|
1584
|
+
* @param event - Breakeven milestone data
|
|
1276
1585
|
*/
|
|
1277
|
-
|
|
1586
|
+
breakevenAvailable(event: BreakevenContract): void | Promise<void>;
|
|
1278
1587
|
/**
|
|
1279
|
-
*
|
|
1280
|
-
*
|
|
1588
|
+
* Handles partial profit level events (10%, 20%, 30%, etc).
|
|
1589
|
+
*
|
|
1590
|
+
* Emitted by: PartialConnectionService via partialProfitSubject
|
|
1591
|
+
* Source: COMMIT_PROFIT_FN callback in PartialConnectionService
|
|
1592
|
+
* Frequency: Once per profit level per signal (deduplicated)
|
|
1593
|
+
*
|
|
1594
|
+
* @param event - Profit milestone data with level and price
|
|
1281
1595
|
*/
|
|
1282
|
-
|
|
1596
|
+
partialProfitAvailable(event: PartialProfitContract): void | Promise<void>;
|
|
1283
1597
|
/**
|
|
1284
|
-
*
|
|
1285
|
-
*
|
|
1598
|
+
* Handles partial loss level events (-10%, -20%, -30%, etc).
|
|
1599
|
+
*
|
|
1600
|
+
* Emitted by: PartialConnectionService via partialLossSubject
|
|
1601
|
+
* Source: COMMIT_LOSS_FN callback in PartialConnectionService
|
|
1602
|
+
* Frequency: Once per loss level per signal (deduplicated)
|
|
1603
|
+
*
|
|
1604
|
+
* @param event - Loss milestone data with level and price
|
|
1286
1605
|
*/
|
|
1287
|
-
|
|
1606
|
+
partialLossAvailable(event: PartialLossContract): void | Promise<void>;
|
|
1288
1607
|
/**
|
|
1289
|
-
*
|
|
1290
|
-
* Represents percentage profit relative to entry price.
|
|
1608
|
+
* Handles scheduled ping events during scheduled signal monitoring.
|
|
1291
1609
|
*
|
|
1292
|
-
*
|
|
1293
|
-
*
|
|
1294
|
-
*
|
|
1295
|
-
*
|
|
1296
|
-
*
|
|
1610
|
+
* Emitted by: StrategyConnectionService via schedulePingSubject
|
|
1611
|
+
* Source: CREATE_COMMIT_SCHEDULE_PING_FN callback in StrategyConnectionService
|
|
1612
|
+
* Frequency: Every minute while scheduled signal is waiting for activation
|
|
1613
|
+
*
|
|
1614
|
+
* @param event - Scheduled signal monitoring data
|
|
1297
1615
|
*/
|
|
1298
|
-
|
|
1616
|
+
pingScheduled(event: SchedulePingContract): void | Promise<void>;
|
|
1299
1617
|
/**
|
|
1300
|
-
*
|
|
1301
|
-
*
|
|
1302
|
-
*
|
|
1618
|
+
* Handles active ping events during active pending signal monitoring.
|
|
1619
|
+
*
|
|
1620
|
+
* Emitted by: StrategyConnectionService via activePingSubject
|
|
1621
|
+
* Source: CREATE_COMMIT_ACTIVE_PING_FN callback in StrategyConnectionService
|
|
1622
|
+
* Frequency: Every minute while pending signal is active
|
|
1623
|
+
*
|
|
1624
|
+
* @param event - Active pending signal monitoring data
|
|
1303
1625
|
*/
|
|
1304
|
-
|
|
1626
|
+
pingActive(event: ActivePingContract): void | Promise<void>;
|
|
1305
1627
|
/**
|
|
1306
|
-
*
|
|
1628
|
+
* Handles risk rejection events when signals fail risk validation.
|
|
1307
1629
|
*
|
|
1308
|
-
*
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1630
|
+
* Emitted by: RiskConnectionService via riskSubject
|
|
1631
|
+
* Source: COMMIT_REJECTION_FN callback in RiskConnectionService
|
|
1632
|
+
* Frequency: Only when signal is rejected (not emitted for allowed signals)
|
|
1311
1633
|
*
|
|
1312
|
-
* @
|
|
1313
|
-
* ```typescript
|
|
1314
|
-
* const eventDate = new Date(event.timestamp);
|
|
1315
|
-
* console.log(`Profit reached at: ${eventDate.toISOString()}`);
|
|
1316
|
-
* ```
|
|
1634
|
+
* @param event - Risk rejection data with reason and context
|
|
1317
1635
|
*/
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
/**
|
|
1322
|
-
* Contract for partial loss level events.
|
|
1323
|
-
*
|
|
1324
|
-
* Emitted by partialLossSubject when a signal reaches a loss level milestone (-10%, -20%, -30%, etc).
|
|
1325
|
-
* Used for tracking partial stop-loss execution and monitoring strategy drawdown.
|
|
1326
|
-
*
|
|
1327
|
-
* Events are emitted only once per level per signal (Set-based deduplication in ClientPartial).
|
|
1328
|
-
* Multiple levels can be emitted in a single tick if price drops significantly.
|
|
1329
|
-
*
|
|
1330
|
-
* Consumers:
|
|
1331
|
-
* - PartialMarkdownService: Accumulates events for report generation
|
|
1332
|
-
* - User callbacks via listenPartialLoss() / listenPartialLossOnce()
|
|
1333
|
-
*
|
|
1334
|
-
* @example
|
|
1335
|
-
* ```typescript
|
|
1336
|
-
* import { listenPartialLoss } from "backtest-kit";
|
|
1337
|
-
*
|
|
1338
|
-
* // Listen to all partial loss events
|
|
1339
|
-
* listenPartialLoss((event) => {
|
|
1340
|
-
* console.log(`[${event.backtest ? "Backtest" : "Live"}] Signal ${event.data.id} reached -${event.level}% loss`);
|
|
1341
|
-
* console.log(`Symbol: ${event.symbol}, Price: ${event.currentPrice}`);
|
|
1342
|
-
* console.log(`Position: ${event.data.position}, Entry: ${event.data.priceOpen}`);
|
|
1343
|
-
*
|
|
1344
|
-
* // Alert on significant loss
|
|
1345
|
-
* if (event.level >= 30 && !event.backtest) {
|
|
1346
|
-
* console.warn("HIGH LOSS ALERT:", event.data.id);
|
|
1347
|
-
* }
|
|
1348
|
-
* });
|
|
1349
|
-
*
|
|
1350
|
-
* // Wait for first 20% loss level
|
|
1351
|
-
* listenPartialLossOnce(
|
|
1352
|
-
* (event) => event.level === 20,
|
|
1353
|
-
* (event) => console.log("20% loss reached:", event.data.id)
|
|
1354
|
-
* );
|
|
1355
|
-
* ```
|
|
1356
|
-
*/
|
|
1357
|
-
interface PartialLossContract {
|
|
1358
|
-
/**
|
|
1359
|
-
* Trading pair symbol (e.g., "BTCUSDT").
|
|
1360
|
-
* Identifies which market this loss event belongs to.
|
|
1361
|
-
*/
|
|
1362
|
-
symbol: string;
|
|
1363
|
-
/**
|
|
1364
|
-
* Strategy name that generated this signal.
|
|
1365
|
-
* Identifies which strategy execution this loss event belongs to.
|
|
1366
|
-
*/
|
|
1367
|
-
strategyName: StrategyName;
|
|
1368
|
-
/**
|
|
1369
|
-
* Exchange name where this signal is being executed.
|
|
1370
|
-
* Identifies which exchange this loss event belongs to.
|
|
1371
|
-
*/
|
|
1372
|
-
exchangeName: ExchangeName;
|
|
1373
|
-
/**
|
|
1374
|
-
* Frame name where this signal is being executed.
|
|
1375
|
-
* Identifies which frame this loss event belongs to (empty string for live mode).
|
|
1376
|
-
*/
|
|
1377
|
-
frameName: FrameName;
|
|
1378
|
-
/**
|
|
1379
|
-
* Complete signal row data with original prices.
|
|
1380
|
-
* Contains all signal information including originalPriceStopLoss, originalPriceTakeProfit, and partialExecuted.
|
|
1381
|
-
*/
|
|
1382
|
-
data: IPublicSignalRow;
|
|
1383
|
-
/**
|
|
1384
|
-
* Current market price at which this loss level was reached.
|
|
1385
|
-
* Used to calculate actual loss percentage.
|
|
1386
|
-
*/
|
|
1387
|
-
currentPrice: number;
|
|
1388
|
-
/**
|
|
1389
|
-
* Loss level milestone reached (10, 20, 30, 40, 50, 60, 70, 80, 90, or 100).
|
|
1390
|
-
* Represents percentage loss relative to entry price (absolute value).
|
|
1391
|
-
*
|
|
1392
|
-
* Note: Stored as positive number, but represents negative loss.
|
|
1393
|
-
* level=20 means -20% loss from entry price.
|
|
1394
|
-
*
|
|
1395
|
-
* @example
|
|
1396
|
-
* ```typescript
|
|
1397
|
-
* // If entry was $50000 and level is 20:
|
|
1398
|
-
* // currentPrice <= $40000 (-20% loss)
|
|
1399
|
-
* // Level is stored as 20, not -20
|
|
1400
|
-
* ```
|
|
1401
|
-
*/
|
|
1402
|
-
level: PartialLevel;
|
|
1403
|
-
/**
|
|
1404
|
-
* Execution mode flag.
|
|
1405
|
-
* - true: Event from backtest execution (historical candle data)
|
|
1406
|
-
* - false: Event from live trading (real-time tick)
|
|
1407
|
-
*/
|
|
1408
|
-
backtest: boolean;
|
|
1636
|
+
riskRejection(event: RiskContract): void | Promise<void>;
|
|
1409
1637
|
/**
|
|
1410
|
-
*
|
|
1411
|
-
*
|
|
1412
|
-
* Timing semantics:
|
|
1413
|
-
* - Live mode: when.getTime() at the moment loss level was detected
|
|
1414
|
-
* - Backtest mode: candle.timestamp of the candle that triggered the level
|
|
1415
|
-
*
|
|
1416
|
-
* @example
|
|
1417
|
-
* ```typescript
|
|
1418
|
-
* const eventDate = new Date(event.timestamp);
|
|
1419
|
-
* console.log(`Loss reached at: ${eventDate.toISOString()}`);
|
|
1638
|
+
* Cleans up resources and subscriptions when action handler is no longer needed.
|
|
1420
1639
|
*
|
|
1421
|
-
*
|
|
1422
|
-
*
|
|
1423
|
-
* const timeInLoss = event.timestamp - entryTime;
|
|
1424
|
-
* console.log(`In loss for ${timeInLoss / 1000 / 60} minutes`);
|
|
1425
|
-
* ```
|
|
1640
|
+
* Called by: Connection services during shutdown
|
|
1641
|
+
* Use for: Unsubscribing from observables, closing connections, flushing buffers
|
|
1426
1642
|
*/
|
|
1427
|
-
|
|
1643
|
+
dispose(): void | Promise<void>;
|
|
1428
1644
|
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Unique action identifier.
|
|
1647
|
+
*/
|
|
1648
|
+
type ActionName = string;
|
|
1429
1649
|
|
|
1430
1650
|
/**
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
1433
|
-
* Emitted by schedulePingSubject every minute when a scheduled signal is being monitored.
|
|
1434
|
-
* Used for tracking scheduled signal lifecycle and custom monitoring logic.
|
|
1435
|
-
*
|
|
1436
|
-
* Events are emitted only when scheduled signal is active (not cancelled, not activated).
|
|
1437
|
-
* Allows users to implement custom cancellation logic via onSchedulePing callback.
|
|
1438
|
-
*
|
|
1439
|
-
* Consumers:
|
|
1440
|
-
* - User callbacks via listenSchedulePing() / listenSchedulePingOnce()
|
|
1441
|
-
*
|
|
1442
|
-
* @example
|
|
1443
|
-
* ```typescript
|
|
1444
|
-
* import { listenSchedulePing } from "backtest-kit";
|
|
1445
|
-
*
|
|
1446
|
-
* // Listen to all schedule ping events
|
|
1447
|
-
* listenSchedulePing((event) => {
|
|
1448
|
-
* console.log(`[${event.backtest ? "Backtest" : "Live"}] Schedule Ping for ${event.symbol}`);
|
|
1449
|
-
* console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
|
|
1450
|
-
* console.log(`Signal ID: ${event.data.id}, priceOpen: ${event.data.priceOpen}`);
|
|
1451
|
-
* console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
|
|
1452
|
-
* });
|
|
1453
|
-
*
|
|
1454
|
-
* // Wait for specific schedule ping
|
|
1455
|
-
* listenSchedulePingOnce(
|
|
1456
|
-
* (event) => event.symbol === "BTCUSDT",
|
|
1457
|
-
* (event) => console.log("BTCUSDT schedule ping received:", event.timestamp)
|
|
1458
|
-
* );
|
|
1459
|
-
* ```
|
|
1651
|
+
* Base fields for all signal commit events.
|
|
1460
1652
|
*/
|
|
1461
|
-
interface
|
|
1462
|
-
/**
|
|
1463
|
-
* Trading pair symbol (e.g., "BTCUSDT").
|
|
1464
|
-
* Identifies which market this ping event belongs to.
|
|
1465
|
-
*/
|
|
1653
|
+
interface SignalCommitBase {
|
|
1654
|
+
/** Trading pair symbol (e.g., "BTCUSDT") */
|
|
1466
1655
|
symbol: string;
|
|
1467
|
-
/**
|
|
1468
|
-
* Strategy name that is monitoring this scheduled signal.
|
|
1469
|
-
* Identifies which strategy execution this ping event belongs to.
|
|
1470
|
-
*/
|
|
1656
|
+
/** Strategy name that generated this signal */
|
|
1471
1657
|
strategyName: StrategyName;
|
|
1472
|
-
/**
|
|
1473
|
-
* Exchange name where this scheduled signal is being monitored.
|
|
1474
|
-
* Identifies which exchange this ping event belongs to.
|
|
1475
|
-
*/
|
|
1658
|
+
/** Exchange name where signal was executed */
|
|
1476
1659
|
exchangeName: ExchangeName;
|
|
1477
|
-
/**
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
*/
|
|
1481
|
-
data: IScheduledSignalRow;
|
|
1482
|
-
/**
|
|
1483
|
-
* Execution mode flag.
|
|
1484
|
-
* - true: Event from backtest execution (historical candle data)
|
|
1485
|
-
* - false: Event from live trading (real-time tick)
|
|
1486
|
-
*/
|
|
1660
|
+
/** Timeframe name (used in backtest mode, empty string in live mode) */
|
|
1661
|
+
frameName: FrameName;
|
|
1662
|
+
/** Whether this event is from backtest mode (true) or live mode (false) */
|
|
1487
1663
|
backtest: boolean;
|
|
1664
|
+
/** Unique signal identifier (UUID v4) */
|
|
1665
|
+
signalId: string;
|
|
1666
|
+
/** Timestamp from execution context (tick's when or backtest candle timestamp) */
|
|
1667
|
+
timestamp: number;
|
|
1488
1668
|
/**
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1491
|
-
* Timing semantics:
|
|
1492
|
-
* - Live mode: when.getTime() at the moment of ping
|
|
1493
|
-
* - Backtest mode: candle.timestamp of the candle being processed
|
|
1494
|
-
*
|
|
1495
|
-
* @example
|
|
1496
|
-
* ```typescript
|
|
1497
|
-
* const eventDate = new Date(event.timestamp);
|
|
1498
|
-
* console.log(`Ping at: ${eventDate.toISOString()}`);
|
|
1499
|
-
* ```
|
|
1669
|
+
* Total number of DCA entries at the time of this event (_entry.length).
|
|
1670
|
+
* 1 = no averaging done (only initial entry). 2+ = averaged positions.
|
|
1500
1671
|
*/
|
|
1501
|
-
|
|
1672
|
+
totalEntries: number;
|
|
1673
|
+
/** Original entry price at signal creation (unchanged by DCA averaging). */
|
|
1674
|
+
originalPriceOpen: number;
|
|
1502
1675
|
}
|
|
1503
|
-
|
|
1504
1676
|
/**
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1507
|
-
* Emitted by activePingSubject every minute when an active pending signal is being monitored.
|
|
1508
|
-
* Used for tracking active signal lifecycle and custom dynamic management logic.
|
|
1509
|
-
*
|
|
1510
|
-
* Events are emitted only when pending signal is active (not closed yet).
|
|
1511
|
-
* Allows users to implement custom management logic via onActivePing callback.
|
|
1512
|
-
*
|
|
1513
|
-
* Consumers:
|
|
1514
|
-
* - User callbacks via listenActivePing() / listenActivePingOnce()
|
|
1515
|
-
*
|
|
1516
|
-
* @example
|
|
1517
|
-
* ```typescript
|
|
1518
|
-
* import { listenActivePing } from "backtest-kit";
|
|
1519
|
-
*
|
|
1520
|
-
* // Listen to all active ping events
|
|
1521
|
-
* listenActivePing((event) => {
|
|
1522
|
-
* console.log(`[${event.backtest ? "Backtest" : "Live"}] Active Ping for ${event.symbol}`);
|
|
1523
|
-
* console.log(`Strategy: ${event.strategyName}, Exchange: ${event.exchangeName}`);
|
|
1524
|
-
* console.log(`Signal ID: ${event.data.id}, Position: ${event.data.position}`);
|
|
1525
|
-
* console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
|
|
1526
|
-
* });
|
|
1527
|
-
*
|
|
1528
|
-
* // Wait for specific active ping
|
|
1529
|
-
* listenActivePingOnce(
|
|
1530
|
-
* (event) => event.symbol === "BTCUSDT",
|
|
1531
|
-
* (event) => console.log("BTCUSDT active ping received:", event.timestamp)
|
|
1532
|
-
* );
|
|
1533
|
-
* ```
|
|
1534
|
-
*/
|
|
1535
|
-
interface ActivePingContract {
|
|
1536
|
-
/**
|
|
1537
|
-
* Trading pair symbol (e.g., "BTCUSDT").
|
|
1538
|
-
* Identifies which market this ping event belongs to.
|
|
1539
|
-
*/
|
|
1540
|
-
symbol: string;
|
|
1541
|
-
/**
|
|
1542
|
-
* Strategy name that is monitoring this active pending signal.
|
|
1543
|
-
* Identifies which strategy execution this ping event belongs to.
|
|
1544
|
-
*/
|
|
1545
|
-
strategyName: StrategyName;
|
|
1546
|
-
/**
|
|
1547
|
-
* Exchange name where this active pending signal is being monitored.
|
|
1548
|
-
* Identifies which exchange this ping event belongs to.
|
|
1549
|
-
*/
|
|
1550
|
-
exchangeName: ExchangeName;
|
|
1551
|
-
/**
|
|
1552
|
-
* Complete pending signal row data.
|
|
1553
|
-
* Contains all signal information: id, position, priceOpen, priceTakeProfit, priceStopLoss, etc.
|
|
1554
|
-
*/
|
|
1555
|
-
data: ISignalRow;
|
|
1556
|
-
/**
|
|
1557
|
-
* Execution mode flag.
|
|
1558
|
-
* - true: Event from backtest execution (historical candle data)
|
|
1559
|
-
* - false: Event from live trading (real-time tick)
|
|
1560
|
-
*/
|
|
1561
|
-
backtest: boolean;
|
|
1562
|
-
/**
|
|
1563
|
-
* Event timestamp in milliseconds since Unix epoch.
|
|
1564
|
-
*
|
|
1565
|
-
* Timing semantics:
|
|
1566
|
-
* - Live mode: when.getTime() at the moment of ping
|
|
1567
|
-
* - Backtest mode: candle.timestamp of the candle being processed
|
|
1568
|
-
*
|
|
1569
|
-
* @example
|
|
1570
|
-
* ```typescript
|
|
1571
|
-
* const eventDate = new Date(event.timestamp);
|
|
1572
|
-
* console.log(`Active Ping at: ${eventDate.toISOString()}`);
|
|
1573
|
-
* ```
|
|
1574
|
-
*/
|
|
1575
|
-
timestamp: number;
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
/**
|
|
1579
|
-
* Contract for risk rejection events.
|
|
1580
|
-
*
|
|
1581
|
-
* Emitted by riskSubject ONLY when a signal is REJECTED due to risk validation failure.
|
|
1582
|
-
* Used for tracking actual risk violations and monitoring rejected signals.
|
|
1583
|
-
*
|
|
1584
|
-
* Events are emitted only when risk limits are violated (not for allowed signals).
|
|
1585
|
-
* This prevents spam and allows focusing on actual risk management interventions.
|
|
1586
|
-
*
|
|
1587
|
-
* Consumers:
|
|
1588
|
-
* - RiskMarkdownService: Accumulates rejection events for report generation
|
|
1589
|
-
* - User callbacks via listenRisk() / listenRiskOnce()
|
|
1590
|
-
*
|
|
1591
|
-
* @example
|
|
1592
|
-
* ```typescript
|
|
1593
|
-
* import { listenRisk } from "backtest-kit";
|
|
1594
|
-
*
|
|
1595
|
-
* // Listen to all risk rejection events
|
|
1596
|
-
* listenRisk((event) => {
|
|
1597
|
-
* console.log(`[RISK REJECTED] Signal for ${event.symbol}`);
|
|
1598
|
-
* console.log(`Strategy: ${event.strategyName}`);
|
|
1599
|
-
* console.log(`Active positions: ${event.activePositionCount}`);
|
|
1600
|
-
* console.log(`Price: ${event.currentPrice}`);
|
|
1601
|
-
* console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
|
|
1602
|
-
* });
|
|
1603
|
-
*
|
|
1604
|
-
* // Alert on risk rejections for specific symbol
|
|
1605
|
-
* listenRisk((event) => {
|
|
1606
|
-
* if (event.symbol === "BTCUSDT") {
|
|
1607
|
-
* console.warn("BTC signal rejected due to risk limits!");
|
|
1608
|
-
* }
|
|
1609
|
-
* });
|
|
1610
|
-
* ```
|
|
1611
|
-
*/
|
|
1612
|
-
interface RiskContract {
|
|
1613
|
-
/**
|
|
1614
|
-
* Trading pair symbol (e.g., "BTCUSDT").
|
|
1615
|
-
* Identifies which market this rejected signal belongs to.
|
|
1616
|
-
*/
|
|
1617
|
-
symbol: string;
|
|
1618
|
-
/**
|
|
1619
|
-
* Pending signal to apply.
|
|
1620
|
-
* Contains signal details (position, priceOpen, priceTakeProfit, priceStopLoss, etc).
|
|
1621
|
-
*/
|
|
1622
|
-
currentSignal: ISignalDto;
|
|
1623
|
-
/**
|
|
1624
|
-
* Strategy name requesting to open a position.
|
|
1625
|
-
* Identifies which strategy attempted to create the signal.
|
|
1626
|
-
*/
|
|
1627
|
-
strategyName: StrategyName;
|
|
1628
|
-
/**
|
|
1629
|
-
* Frame name used in backtest execution.
|
|
1630
|
-
* Identifies which frame this signal was for in backtest execution.
|
|
1631
|
-
*/
|
|
1632
|
-
frameName: FrameName;
|
|
1633
|
-
/**
|
|
1634
|
-
* Exchange name.
|
|
1635
|
-
* Identifies which exchange this signal was for.
|
|
1636
|
-
*/
|
|
1637
|
-
exchangeName: ExchangeName;
|
|
1638
|
-
/**
|
|
1639
|
-
* Current VWAP price at the time of rejection.
|
|
1640
|
-
* Market price when risk check was performed.
|
|
1641
|
-
*/
|
|
1642
|
-
currentPrice: number;
|
|
1643
|
-
/**
|
|
1644
|
-
* Number of currently active positions across all strategies at rejection time.
|
|
1645
|
-
* Used to track portfolio-level exposure when signal was rejected.
|
|
1646
|
-
*/
|
|
1647
|
-
activePositionCount: number;
|
|
1648
|
-
/**
|
|
1649
|
-
* Unique identifier for this rejection instance.
|
|
1650
|
-
* Generated by ClientRisk for tracking and debugging purposes.
|
|
1651
|
-
* Null if validation threw exception without custom ID.
|
|
1652
|
-
*/
|
|
1653
|
-
rejectionId: string | null;
|
|
1654
|
-
/**
|
|
1655
|
-
* Human-readable reason why the signal was rejected.
|
|
1656
|
-
* Captured from IRiskValidation.note or error message.
|
|
1657
|
-
*
|
|
1658
|
-
* @example
|
|
1659
|
-
* ```typescript
|
|
1660
|
-
* console.log(`Rejection reason: ${event.rejectionNote}`);
|
|
1661
|
-
* // Output: "Rejection reason: Max 3 positions allowed"
|
|
1662
|
-
* ```
|
|
1663
|
-
*/
|
|
1664
|
-
rejectionNote: string;
|
|
1665
|
-
/**
|
|
1666
|
-
* Event timestamp in milliseconds since Unix epoch.
|
|
1667
|
-
* Represents when the signal was rejected.
|
|
1668
|
-
*
|
|
1669
|
-
* @example
|
|
1670
|
-
* ```typescript
|
|
1671
|
-
* const eventDate = new Date(event.timestamp);
|
|
1672
|
-
* console.log(`Signal rejected at: ${eventDate.toISOString()}`);
|
|
1673
|
-
* ```
|
|
1674
|
-
*/
|
|
1675
|
-
timestamp: number;
|
|
1676
|
-
/**
|
|
1677
|
-
* Whether this event is from backtest mode (true) or live mode (false).
|
|
1678
|
-
* Used to separate backtest and live risk rejection tracking.
|
|
1679
|
-
*/
|
|
1680
|
-
backtest: boolean;
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
/**
|
|
1684
|
-
* Constructor type for action handlers with strategy context.
|
|
1685
|
-
*
|
|
1686
|
-
* @param strategyName - Strategy identifier (e.g., "rsi_divergence", "macd_cross")
|
|
1687
|
-
* @param frameName - Timeframe identifier (e.g., "1m", "5m", "1h")
|
|
1688
|
-
* @param backtest - True for backtest mode, false for live trading
|
|
1689
|
-
* @returns Partial implementation of IAction (only required handlers)
|
|
1690
|
-
*
|
|
1691
|
-
* @example
|
|
1692
|
-
* ```typescript
|
|
1693
|
-
* class TelegramNotifier implements Partial<IAction> {
|
|
1694
|
-
* constructor(
|
|
1695
|
-
* private strategyName: StrategyName,
|
|
1696
|
-
* private frameName: FrameName,
|
|
1697
|
-
* private backtest: boolean
|
|
1698
|
-
* ) {}
|
|
1699
|
-
*
|
|
1700
|
-
* signal(event: IStrategyTickResult): void {
|
|
1701
|
-
* if (!this.backtest && event.state === 'opened') {
|
|
1702
|
-
* telegram.send(`[${this.strategyName}/${this.frameName}] New signal`);
|
|
1703
|
-
* }
|
|
1704
|
-
* }
|
|
1705
|
-
* }
|
|
1706
|
-
*
|
|
1707
|
-
* const actionCtors: TActionCtor[] = [TelegramNotifier, ReduxLogger];
|
|
1708
|
-
* ```
|
|
1709
|
-
*/
|
|
1710
|
-
type TActionCtor = new (strategyName: StrategyName, frameName: FrameName, actionName: ActionName, backtest: boolean) => Partial<IPublicAction>;
|
|
1711
|
-
/**
|
|
1712
|
-
* Action parameters passed to ClientAction constructor.
|
|
1713
|
-
* Combines schema with runtime dependencies and execution context.
|
|
1714
|
-
*
|
|
1715
|
-
* Extended from IActionSchema with:
|
|
1716
|
-
* - Logger instance for debugging and monitoring
|
|
1717
|
-
* - Strategy context (strategyName, frameName)
|
|
1718
|
-
* - Runtime environment flags
|
|
1719
|
-
*
|
|
1720
|
-
* @example
|
|
1721
|
-
* ```typescript
|
|
1722
|
-
* const params: IActionParams = {
|
|
1723
|
-
* actionName: "telegram-notifier",
|
|
1724
|
-
* handler: TelegramNotifier,
|
|
1725
|
-
* callbacks: { onInit, onDispose, onSignal },
|
|
1726
|
-
* logger: loggerService,
|
|
1727
|
-
* strategyName: "rsi_divergence",
|
|
1728
|
-
* frameName: "1h"
|
|
1729
|
-
* };
|
|
1730
|
-
*
|
|
1731
|
-
* const actionClient = new ClientAction(params);
|
|
1732
|
-
* ```
|
|
1733
|
-
*/
|
|
1734
|
-
interface IActionParams extends IActionSchema {
|
|
1735
|
-
/** Logger service for debugging and monitoring action execution */
|
|
1736
|
-
logger: ILogger;
|
|
1737
|
-
/** Strategy identifier this action is attached to */
|
|
1738
|
-
strategyName: StrategyName;
|
|
1739
|
-
/** Exchange name (e.g., "binance") */
|
|
1740
|
-
exchangeName: ExchangeName;
|
|
1741
|
-
/** Timeframe identifier this action is attached to */
|
|
1742
|
-
frameName: FrameName;
|
|
1743
|
-
/** Whether running in backtest mode */
|
|
1744
|
-
backtest: boolean;
|
|
1745
|
-
}
|
|
1746
|
-
/**
|
|
1747
|
-
* Lifecycle and event callbacks for action handlers.
|
|
1748
|
-
*
|
|
1749
|
-
* Provides hooks for initialization, disposal, and event handling.
|
|
1750
|
-
* All callbacks are optional and support both sync and async execution.
|
|
1751
|
-
*
|
|
1752
|
-
* Use cases:
|
|
1753
|
-
* - Resource initialization (database connections, file handles)
|
|
1754
|
-
* - Resource cleanup (close connections, flush buffers)
|
|
1755
|
-
* - Event logging and monitoring
|
|
1756
|
-
* - State persistence
|
|
1757
|
-
*
|
|
1758
|
-
* @example
|
|
1759
|
-
* ```typescript
|
|
1760
|
-
* const callbacks: IActionCallbacks = {
|
|
1761
|
-
* onInit: async (strategyName, frameName, backtest) => {
|
|
1762
|
-
* console.log(`[${strategyName}/${frameName}] Action initialized (backtest=${backtest})`);
|
|
1763
|
-
* await db.connect();
|
|
1764
|
-
* },
|
|
1765
|
-
* onSignal: (event, strategyName, frameName, backtest) => {
|
|
1766
|
-
* if (event.action === 'opened') {
|
|
1767
|
-
* console.log(`New signal opened: ${event.signal.id}`);
|
|
1768
|
-
* }
|
|
1769
|
-
* },
|
|
1770
|
-
* onDispose: async (strategyName, frameName, backtest) => {
|
|
1771
|
-
* await db.disconnect();
|
|
1772
|
-
* console.log(`[${strategyName}/${frameName}] Action disposed`);
|
|
1773
|
-
* }
|
|
1774
|
-
* };
|
|
1775
|
-
* ```
|
|
1776
|
-
*/
|
|
1777
|
-
interface IActionCallbacks {
|
|
1778
|
-
/**
|
|
1779
|
-
* Called when action handler is initialized.
|
|
1780
|
-
*
|
|
1781
|
-
* Use for:
|
|
1782
|
-
* - Opening database connections
|
|
1783
|
-
* - Initializing external services
|
|
1784
|
-
* - Loading persisted state
|
|
1785
|
-
* - Setting up subscriptions
|
|
1786
|
-
*
|
|
1787
|
-
* @param actionName - Action identifier
|
|
1788
|
-
* @param strategyName - Strategy identifier
|
|
1789
|
-
* @param frameName - Timeframe identifier
|
|
1790
|
-
* @param backtest - True for backtest mode, false for live trading
|
|
1791
|
-
*/
|
|
1792
|
-
onInit(actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1793
|
-
/**
|
|
1794
|
-
* Called when action handler is disposed.
|
|
1795
|
-
*
|
|
1796
|
-
* Use for:
|
|
1797
|
-
* - Closing database connections
|
|
1798
|
-
* - Flushing buffers
|
|
1799
|
-
* - Saving state to disk
|
|
1800
|
-
* - Unsubscribing from observables
|
|
1801
|
-
*
|
|
1802
|
-
* @param actionName - Action identifier
|
|
1803
|
-
* @param strategyName - Strategy identifier
|
|
1804
|
-
* @param frameName - Timeframe identifier
|
|
1805
|
-
* @param backtest - True for backtest mode, false for live trading
|
|
1806
|
-
*/
|
|
1807
|
-
onDispose(actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1808
|
-
/**
|
|
1809
|
-
* Called on signal events from all modes (live + backtest).
|
|
1810
|
-
*
|
|
1811
|
-
* Triggered by: StrategyConnectionService via signalEmitter
|
|
1812
|
-
* Frequency: Every tick/candle when strategy is evaluated
|
|
1813
|
-
*
|
|
1814
|
-
* @param event - Signal state result (idle, scheduled, opened, active, closed, cancelled)
|
|
1815
|
-
* @param actionName - Action identifier
|
|
1816
|
-
* @param strategyName - Strategy identifier
|
|
1817
|
-
* @param frameName - Timeframe identifier
|
|
1818
|
-
* @param backtest - True for backtest mode, false for live trading
|
|
1819
|
-
*/
|
|
1820
|
-
onSignal(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1821
|
-
/**
|
|
1822
|
-
* Called on signal events from live trading only.
|
|
1823
|
-
*
|
|
1824
|
-
* Triggered by: StrategyConnectionService via signalLiveEmitter
|
|
1825
|
-
* Frequency: Every tick in live mode
|
|
1826
|
-
*
|
|
1827
|
-
* @param event - Signal state result from live trading
|
|
1828
|
-
* @param actionName - Action identifier
|
|
1829
|
-
* @param strategyName - Strategy identifier
|
|
1830
|
-
* @param frameName - Timeframe identifier
|
|
1831
|
-
* @param backtest - Always false (live mode only)
|
|
1832
|
-
*/
|
|
1833
|
-
onSignalLive(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1834
|
-
/**
|
|
1835
|
-
* Called on signal events from backtest only.
|
|
1836
|
-
*
|
|
1837
|
-
* Triggered by: StrategyConnectionService via signalBacktestEmitter
|
|
1838
|
-
* Frequency: Every candle in backtest mode
|
|
1839
|
-
*
|
|
1840
|
-
* @param event - Signal state result from backtest
|
|
1841
|
-
* @param actionName - Action identifier
|
|
1842
|
-
* @param strategyName - Strategy identifier
|
|
1843
|
-
* @param frameName - Timeframe identifier
|
|
1844
|
-
* @param backtest - Always true (backtest mode only)
|
|
1845
|
-
*/
|
|
1846
|
-
onSignalBacktest(event: IStrategyTickResult, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1847
|
-
/**
|
|
1848
|
-
* Called when breakeven is triggered (stop-loss moved to entry price).
|
|
1849
|
-
*
|
|
1850
|
-
* Triggered by: BreakevenConnectionService via breakevenSubject
|
|
1851
|
-
* Frequency: Once per signal when breakeven threshold is reached
|
|
1852
|
-
*
|
|
1853
|
-
* @param event - Breakeven milestone data
|
|
1854
|
-
* @param actionName - Action identifier
|
|
1855
|
-
* @param strategyName - Strategy identifier
|
|
1856
|
-
* @param frameName - Timeframe identifier
|
|
1857
|
-
* @param backtest - True for backtest mode, false for live trading
|
|
1858
|
-
*/
|
|
1859
|
-
onBreakevenAvailable(event: BreakevenContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1860
|
-
/**
|
|
1861
|
-
* Called when partial profit level is reached (10%, 20%, 30%, etc).
|
|
1862
|
-
*
|
|
1863
|
-
* Triggered by: PartialConnectionService via partialProfitSubject
|
|
1864
|
-
* Frequency: Once per profit level per signal (deduplicated)
|
|
1865
|
-
*
|
|
1866
|
-
* @param event - Profit milestone data with level and price
|
|
1867
|
-
* @param actionName - Action identifier
|
|
1868
|
-
* @param strategyName - Strategy identifier
|
|
1869
|
-
* @param frameName - Timeframe identifier
|
|
1870
|
-
* @param backtest - True for backtest mode, false for live trading
|
|
1871
|
-
*/
|
|
1872
|
-
onPartialProfitAvailable(event: PartialProfitContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1873
|
-
/**
|
|
1874
|
-
* Called when partial loss level is reached (-10%, -20%, -30%, etc).
|
|
1875
|
-
*
|
|
1876
|
-
* Triggered by: PartialConnectionService via partialLossSubject
|
|
1877
|
-
* Frequency: Once per loss level per signal (deduplicated)
|
|
1878
|
-
*
|
|
1879
|
-
* @param event - Loss milestone data with level and price
|
|
1880
|
-
* @param actionName - Action identifier
|
|
1881
|
-
* @param strategyName - Strategy identifier
|
|
1882
|
-
* @param frameName - Timeframe identifier
|
|
1883
|
-
* @param backtest - True for backtest mode, false for live trading
|
|
1884
|
-
*/
|
|
1885
|
-
onPartialLossAvailable(event: PartialLossContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1886
|
-
/**
|
|
1887
|
-
* Called during scheduled signal monitoring (every minute while waiting for activation).
|
|
1888
|
-
*
|
|
1889
|
-
* Triggered by: StrategyConnectionService via schedulePingSubject
|
|
1890
|
-
* Frequency: Every minute while scheduled signal is waiting
|
|
1891
|
-
*
|
|
1892
|
-
* @param event - Scheduled signal monitoring data
|
|
1893
|
-
* @param actionName - Action identifier
|
|
1894
|
-
* @param strategyName - Strategy identifier
|
|
1895
|
-
* @param frameName - Timeframe identifier
|
|
1896
|
-
* @param backtest - True for backtest mode, false for live trading
|
|
1897
|
-
*/
|
|
1898
|
-
onPingScheduled(event: SchedulePingContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1899
|
-
/**
|
|
1900
|
-
* Called during active pending signal monitoring (every minute while position is active).
|
|
1901
|
-
*
|
|
1902
|
-
* Triggered by: StrategyConnectionService via activePingSubject
|
|
1903
|
-
* Frequency: Every minute while pending signal is active
|
|
1904
|
-
*
|
|
1905
|
-
* @param event - Active pending signal monitoring data
|
|
1906
|
-
* @param actionName - Action identifier
|
|
1907
|
-
* @param strategyName - Strategy identifier
|
|
1908
|
-
* @param frameName - Timeframe identifier
|
|
1909
|
-
* @param backtest - True for backtest mode, false for live trading
|
|
1910
|
-
*/
|
|
1911
|
-
onPingActive(event: ActivePingContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1912
|
-
/**
|
|
1913
|
-
* Called when signal is rejected by risk management.
|
|
1914
|
-
*
|
|
1915
|
-
* Triggered by: RiskConnectionService via riskSubject
|
|
1916
|
-
* Frequency: Only when signal fails risk validation (not emitted for allowed signals)
|
|
1917
|
-
*
|
|
1918
|
-
* @param event - Risk rejection data with reason and context
|
|
1919
|
-
* @param actionName - Action identifier
|
|
1920
|
-
* @param strategyName - Strategy identifier
|
|
1921
|
-
* @param frameName - Timeframe identifier
|
|
1922
|
-
* @param backtest - True for backtest mode, false for live trading
|
|
1923
|
-
*/
|
|
1924
|
-
onRiskRejection(event: RiskContract, actionName: ActionName, strategyName: StrategyName, frameName: FrameName, backtest: boolean): void | Promise<void>;
|
|
1925
|
-
}
|
|
1926
|
-
/**
|
|
1927
|
-
* Action schema registered via addActionSchema().
|
|
1928
|
-
* Defines event handler implementation and lifecycle callbacks for state management integration.
|
|
1929
|
-
*
|
|
1930
|
-
* Actions provide a way to attach custom event handlers to strategies for:
|
|
1931
|
-
* - State management (Redux, Zustand, MobX)
|
|
1932
|
-
* - Event logging and monitoring
|
|
1933
|
-
* - Real-time notifications (Telegram, Discord, email)
|
|
1934
|
-
* - Analytics and metrics collection
|
|
1935
|
-
* - Custom business logic triggers
|
|
1936
|
-
*
|
|
1937
|
-
* Each action instance is created per strategy-frame pair and receives all events
|
|
1938
|
-
* emitted during strategy execution. Multiple actions can be attached to a single strategy.
|
|
1939
|
-
*
|
|
1940
|
-
* @example
|
|
1941
|
-
* ```typescript
|
|
1942
|
-
* import { addActionSchema } from "backtest-kit";
|
|
1943
|
-
*
|
|
1944
|
-
* // Define action handler class
|
|
1945
|
-
* class TelegramNotifier implements Partial<IAction> {
|
|
1946
|
-
* constructor(
|
|
1947
|
-
* private strategyName: StrategyName,
|
|
1948
|
-
* private frameName: FrameName,
|
|
1949
|
-
* private backtest: boolean
|
|
1950
|
-
* ) {}
|
|
1951
|
-
*
|
|
1952
|
-
* signal(event: IStrategyTickResult): void {
|
|
1953
|
-
* if (!this.backtest && event.action === 'opened') {
|
|
1954
|
-
* telegram.send(`[${this.strategyName}/${this.frameName}] New signal`);
|
|
1955
|
-
* }
|
|
1956
|
-
* }
|
|
1957
|
-
*
|
|
1958
|
-
* dispose(): void {
|
|
1959
|
-
* telegram.close();
|
|
1960
|
-
* }
|
|
1961
|
-
* }
|
|
1962
|
-
*
|
|
1963
|
-
* // Register action schema
|
|
1964
|
-
* addActionSchema({
|
|
1965
|
-
* actionName: "telegram-notifier",
|
|
1966
|
-
* handler: TelegramNotifier,
|
|
1967
|
-
* callbacks: {
|
|
1968
|
-
* onInit: async (strategyName, frameName, backtest) => {
|
|
1969
|
-
* console.log(`Telegram notifier initialized for ${strategyName}/${frameName}`);
|
|
1970
|
-
* },
|
|
1971
|
-
* onSignal: (event, strategyName, frameName, backtest) => {
|
|
1972
|
-
* console.log(`Signal event: ${event.action}`);
|
|
1973
|
-
* }
|
|
1974
|
-
* }
|
|
1975
|
-
* });
|
|
1976
|
-
* ```
|
|
1977
|
-
*/
|
|
1978
|
-
interface IActionSchema {
|
|
1979
|
-
/** Unique action identifier for registration */
|
|
1980
|
-
actionName: ActionName;
|
|
1981
|
-
/** Optional developer note for documentation */
|
|
1982
|
-
note?: string;
|
|
1983
|
-
/** Action handler constructor (instantiated per strategy-frame pair) */
|
|
1984
|
-
handler: TActionCtor | Partial<IPublicAction>;
|
|
1985
|
-
/** Optional lifecycle and event callbacks */
|
|
1986
|
-
callbacks?: Partial<IActionCallbacks>;
|
|
1987
|
-
}
|
|
1988
|
-
/**
|
|
1989
|
-
* Public action interface for custom action handler implementations.
|
|
1990
|
-
*
|
|
1991
|
-
* Extends IAction with an initialization lifecycle method.
|
|
1992
|
-
* Action handlers implement this interface to receive strategy events and perform custom logic.
|
|
1993
|
-
*
|
|
1994
|
-
* Lifecycle:
|
|
1995
|
-
* 1. Constructor called with (strategyName, frameName, actionName)
|
|
1996
|
-
* 2. init() called once for async initialization (setup connections, load resources)
|
|
1997
|
-
* 3. Event methods called as strategy executes (signal, breakeven, partialProfit, etc.)
|
|
1998
|
-
* 4. dispose() called once for cleanup (close connections, flush buffers)
|
|
1999
|
-
*
|
|
2000
|
-
* Key features:
|
|
2001
|
-
* - init() for async initialization (database connections, API clients, file handles)
|
|
2002
|
-
* - All IAction methods available for event handling
|
|
2003
|
-
* - dispose() guaranteed to run exactly once via singleshot pattern
|
|
2004
|
-
*
|
|
2005
|
-
* Common use cases:
|
|
2006
|
-
* - State management: Redux/Zustand store integration
|
|
2007
|
-
* - Notifications: Telegram/Discord/Email alerts
|
|
2008
|
-
* - Logging: Custom event tracking and monitoring
|
|
2009
|
-
* - Analytics: Metrics collection and reporting
|
|
2010
|
-
* - External systems: Database writes, API calls, file operations
|
|
2011
|
-
*
|
|
2012
|
-
* @example
|
|
2013
|
-
* ```typescript
|
|
2014
|
-
* class TelegramNotifier implements Partial<IPublicAction> {
|
|
2015
|
-
* private bot: TelegramBot | null = null;
|
|
2016
|
-
*
|
|
2017
|
-
* constructor(
|
|
2018
|
-
* private strategyName: string,
|
|
2019
|
-
* private frameName: string,
|
|
2020
|
-
* private actionName: string
|
|
2021
|
-
* ) {}
|
|
2022
|
-
*
|
|
2023
|
-
* // Called once during initialization
|
|
2024
|
-
* async init() {
|
|
2025
|
-
* this.bot = new TelegramBot(process.env.TELEGRAM_TOKEN);
|
|
2026
|
-
* await this.bot.connect();
|
|
2027
|
-
* }
|
|
2028
|
-
*
|
|
2029
|
-
* // Called on every signal event
|
|
2030
|
-
* async signal(event: IStrategyTickResult) {
|
|
2031
|
-
* if (event.action === 'opened') {
|
|
2032
|
-
* await this.bot.send(
|
|
2033
|
-
* `[${this.strategyName}/${this.frameName}] Signal opened: ${event.signal.side}`
|
|
2034
|
-
* );
|
|
2035
|
-
* }
|
|
2036
|
-
* }
|
|
2037
|
-
*
|
|
2038
|
-
* // Called once during cleanup
|
|
2039
|
-
* async dispose() {
|
|
2040
|
-
* await this.bot?.disconnect();
|
|
2041
|
-
* this.bot = null;
|
|
2042
|
-
* }
|
|
2043
|
-
* }
|
|
2044
|
-
* ```
|
|
2045
|
-
*
|
|
2046
|
-
* @see IAction for all available event methods
|
|
2047
|
-
* @see TActionCtor for constructor signature requirements
|
|
2048
|
-
* @see ClientAction for internal wrapper that manages lifecycle
|
|
2049
|
-
*/
|
|
2050
|
-
type IPublicAction = {
|
|
2051
|
-
[key in keyof IAction]?: IAction[key];
|
|
2052
|
-
} & {
|
|
2053
|
-
init?(): void | Promise<void>;
|
|
2054
|
-
};
|
|
2055
|
-
/**
|
|
2056
|
-
* Action interface for state manager integration.
|
|
2057
|
-
*
|
|
2058
|
-
* Provides methods to handle all events emitted by connection services.
|
|
2059
|
-
* Each method corresponds to a specific event type emitted via .next() calls.
|
|
2060
|
-
*
|
|
2061
|
-
* Use this interface to implement custom state management logic:
|
|
2062
|
-
* - Redux/Zustand action dispatchers
|
|
2063
|
-
* - Event logging systems
|
|
2064
|
-
* - Real-time monitoring dashboards
|
|
2065
|
-
* - Analytics and metrics collection
|
|
2066
|
-
*
|
|
2067
|
-
* @example
|
|
2068
|
-
* ```typescript
|
|
2069
|
-
* class ReduxStateManager implements IAction {
|
|
2070
|
-
* constructor(private store: Store) {}
|
|
2071
|
-
*
|
|
2072
|
-
* signal(event: IStrategyTickResult): void {
|
|
2073
|
-
* this.store.dispatch({ type: 'SIGNAL', payload: event });
|
|
2074
|
-
* }
|
|
2075
|
-
*
|
|
2076
|
-
* breakeven(event: BreakevenContract): void {
|
|
2077
|
-
* this.store.dispatch({ type: 'BREAKEVEN', payload: event });
|
|
2078
|
-
* }
|
|
2079
|
-
*
|
|
2080
|
-
* // ... implement other methods
|
|
2081
|
-
* }
|
|
2082
|
-
* ```
|
|
2083
|
-
*/
|
|
2084
|
-
interface IAction {
|
|
2085
|
-
/**
|
|
2086
|
-
* Handles signal events from all modes (live + backtest).
|
|
2087
|
-
*
|
|
2088
|
-
* Emitted by: StrategyConnectionService via signalEmitter
|
|
2089
|
-
* Source: StrategyConnectionService.tick() and StrategyConnectionService.backtest()
|
|
2090
|
-
* Frequency: Every tick/candle when strategy is evaluated
|
|
2091
|
-
*
|
|
2092
|
-
* @param event - Signal state result (idle, scheduled, opened, active, closed, cancelled)
|
|
2093
|
-
*/
|
|
2094
|
-
signal(event: IStrategyTickResult): void | Promise<void>;
|
|
2095
|
-
/**
|
|
2096
|
-
* Handles signal events from live trading only.
|
|
2097
|
-
*
|
|
2098
|
-
* Emitted by: StrategyConnectionService via signalLiveEmitter
|
|
2099
|
-
* Source: StrategyConnectionService.tick() when backtest=false
|
|
2100
|
-
* Frequency: Every tick in live mode
|
|
2101
|
-
*
|
|
2102
|
-
* @param event - Signal state result from live trading
|
|
2103
|
-
*/
|
|
2104
|
-
signalLive(event: IStrategyTickResult): void | Promise<void>;
|
|
2105
|
-
/**
|
|
2106
|
-
* Handles signal events from backtest only.
|
|
2107
|
-
*
|
|
2108
|
-
* Emitted by: StrategyConnectionService via signalBacktestEmitter
|
|
2109
|
-
* Source: StrategyConnectionService.backtest() when backtest=true
|
|
2110
|
-
* Frequency: Every candle in backtest mode
|
|
2111
|
-
*
|
|
2112
|
-
* @param event - Signal state result from backtest
|
|
2113
|
-
*/
|
|
2114
|
-
signalBacktest(event: IStrategyTickResult): void | Promise<void>;
|
|
2115
|
-
/**
|
|
2116
|
-
* Handles breakeven events when stop-loss is moved to entry price.
|
|
2117
|
-
*
|
|
2118
|
-
* Emitted by: BreakevenConnectionService via breakevenSubject
|
|
2119
|
-
* Source: COMMIT_BREAKEVEN_FN callback in BreakevenConnectionService
|
|
2120
|
-
* Frequency: Once per signal when breakeven threshold is reached
|
|
2121
|
-
*
|
|
2122
|
-
* @param event - Breakeven milestone data
|
|
2123
|
-
*/
|
|
2124
|
-
breakevenAvailable(event: BreakevenContract): void | Promise<void>;
|
|
2125
|
-
/**
|
|
2126
|
-
* Handles partial profit level events (10%, 20%, 30%, etc).
|
|
2127
|
-
*
|
|
2128
|
-
* Emitted by: PartialConnectionService via partialProfitSubject
|
|
2129
|
-
* Source: COMMIT_PROFIT_FN callback in PartialConnectionService
|
|
2130
|
-
* Frequency: Once per profit level per signal (deduplicated)
|
|
2131
|
-
*
|
|
2132
|
-
* @param event - Profit milestone data with level and price
|
|
2133
|
-
*/
|
|
2134
|
-
partialProfitAvailable(event: PartialProfitContract): void | Promise<void>;
|
|
2135
|
-
/**
|
|
2136
|
-
* Handles partial loss level events (-10%, -20%, -30%, etc).
|
|
2137
|
-
*
|
|
2138
|
-
* Emitted by: PartialConnectionService via partialLossSubject
|
|
2139
|
-
* Source: COMMIT_LOSS_FN callback in PartialConnectionService
|
|
2140
|
-
* Frequency: Once per loss level per signal (deduplicated)
|
|
2141
|
-
*
|
|
2142
|
-
* @param event - Loss milestone data with level and price
|
|
2143
|
-
*/
|
|
2144
|
-
partialLossAvailable(event: PartialLossContract): void | Promise<void>;
|
|
2145
|
-
/**
|
|
2146
|
-
* Handles scheduled ping events during scheduled signal monitoring.
|
|
2147
|
-
*
|
|
2148
|
-
* Emitted by: StrategyConnectionService via schedulePingSubject
|
|
2149
|
-
* Source: CREATE_COMMIT_SCHEDULE_PING_FN callback in StrategyConnectionService
|
|
2150
|
-
* Frequency: Every minute while scheduled signal is waiting for activation
|
|
2151
|
-
*
|
|
2152
|
-
* @param event - Scheduled signal monitoring data
|
|
2153
|
-
*/
|
|
2154
|
-
pingScheduled(event: SchedulePingContract): void | Promise<void>;
|
|
2155
|
-
/**
|
|
2156
|
-
* Handles active ping events during active pending signal monitoring.
|
|
2157
|
-
*
|
|
2158
|
-
* Emitted by: StrategyConnectionService via activePingSubject
|
|
2159
|
-
* Source: CREATE_COMMIT_ACTIVE_PING_FN callback in StrategyConnectionService
|
|
2160
|
-
* Frequency: Every minute while pending signal is active
|
|
2161
|
-
*
|
|
2162
|
-
* @param event - Active pending signal monitoring data
|
|
2163
|
-
*/
|
|
2164
|
-
pingActive(event: ActivePingContract): void | Promise<void>;
|
|
2165
|
-
/**
|
|
2166
|
-
* Handles risk rejection events when signals fail risk validation.
|
|
2167
|
-
*
|
|
2168
|
-
* Emitted by: RiskConnectionService via riskSubject
|
|
2169
|
-
* Source: COMMIT_REJECTION_FN callback in RiskConnectionService
|
|
2170
|
-
* Frequency: Only when signal is rejected (not emitted for allowed signals)
|
|
2171
|
-
*
|
|
2172
|
-
* @param event - Risk rejection data with reason and context
|
|
2173
|
-
*/
|
|
2174
|
-
riskRejection(event: RiskContract): void | Promise<void>;
|
|
2175
|
-
/**
|
|
2176
|
-
* Cleans up resources and subscriptions when action handler is no longer needed.
|
|
2177
|
-
*
|
|
2178
|
-
* Called by: Connection services during shutdown
|
|
2179
|
-
* Use for: Unsubscribing from observables, closing connections, flushing buffers
|
|
2180
|
-
*/
|
|
2181
|
-
dispose(): void | Promise<void>;
|
|
2182
|
-
}
|
|
2183
|
-
/**
|
|
2184
|
-
* Unique action identifier.
|
|
2185
|
-
*/
|
|
2186
|
-
type ActionName = string;
|
|
2187
|
-
|
|
2188
|
-
/**
|
|
2189
|
-
* Base fields for all signal commit events.
|
|
2190
|
-
*/
|
|
2191
|
-
interface SignalCommitBase {
|
|
2192
|
-
/** Trading pair symbol (e.g., "BTCUSDT") */
|
|
2193
|
-
symbol: string;
|
|
2194
|
-
/** Strategy name that generated this signal */
|
|
2195
|
-
strategyName: StrategyName;
|
|
2196
|
-
/** Exchange name where signal was executed */
|
|
2197
|
-
exchangeName: ExchangeName;
|
|
2198
|
-
/** Timeframe name (used in backtest mode, empty string in live mode) */
|
|
2199
|
-
frameName: FrameName;
|
|
2200
|
-
/** Whether this event is from backtest mode (true) or live mode (false) */
|
|
2201
|
-
backtest: boolean;
|
|
2202
|
-
/** Unique signal identifier (UUID v4) */
|
|
2203
|
-
signalId: string;
|
|
2204
|
-
/** Timestamp from execution context (tick's when or backtest candle timestamp) */
|
|
2205
|
-
timestamp: number;
|
|
2206
|
-
/**
|
|
2207
|
-
* Total number of DCA entries at the time of this event (_entry.length).
|
|
2208
|
-
* 1 = no averaging done (only initial entry). 2+ = averaged positions.
|
|
2209
|
-
*/
|
|
2210
|
-
totalEntries: number;
|
|
2211
|
-
/** Original entry price at signal creation (unchanged by DCA averaging). */
|
|
2212
|
-
originalPriceOpen: number;
|
|
2213
|
-
}
|
|
2214
|
-
/**
|
|
2215
|
-
* Cancel scheduled signal event.
|
|
1677
|
+
* Cancel scheduled signal event.
|
|
2216
1678
|
*/
|
|
2217
1679
|
interface CancelScheduledCommit extends SignalCommitBase {
|
|
2218
1680
|
/** Discriminator for cancel-scheduled action */
|
|
@@ -3177,305 +2639,847 @@ interface IStrategy {
|
|
|
3177
2639
|
*
|
|
3178
2640
|
* @example
|
|
3179
2641
|
* ```typescript
|
|
3180
|
-
* // Activate scheduled signal without waiting for priceOpen
|
|
3181
|
-
* await strategy.activateScheduled("BTCUSDT", false, "user-activate-123");
|
|
3182
|
-
* // Scheduled signal becomes pending signal immediately
|
|
2642
|
+
* // Activate scheduled signal without waiting for priceOpen
|
|
2643
|
+
* await strategy.activateScheduled("BTCUSDT", false, "user-activate-123");
|
|
2644
|
+
* // Scheduled signal becomes pending signal immediately
|
|
2645
|
+
* ```
|
|
2646
|
+
*/
|
|
2647
|
+
activateScheduled: (symbol: string, backtest: boolean, activateId?: string) => Promise<void>;
|
|
2648
|
+
/**
|
|
2649
|
+
* Closes the pending signal without stopping the strategy.
|
|
2650
|
+
*
|
|
2651
|
+
* Clears the pending signal (active position).
|
|
2652
|
+
* Does NOT affect scheduled signals or strategy operation.
|
|
2653
|
+
* Does NOT set stop flag - strategy can continue generating new signals.
|
|
2654
|
+
*
|
|
2655
|
+
* Use case: Close an active position that is no longer desired without stopping the entire strategy.
|
|
2656
|
+
*
|
|
2657
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
2658
|
+
* @param backtest - Whether running in backtest mode
|
|
2659
|
+
* @param closeId - Optional identifier for this close operation
|
|
2660
|
+
* @returns Promise that resolves when pending signal is cleared
|
|
2661
|
+
*
|
|
2662
|
+
* @example
|
|
2663
|
+
* ```typescript
|
|
2664
|
+
* // Close pending signal without stopping strategy
|
|
2665
|
+
* await strategy.closePending("BTCUSDT", false, "user-close-123");
|
|
2666
|
+
* // Strategy continues, can generate new signals
|
|
2667
|
+
* ```
|
|
2668
|
+
*/
|
|
2669
|
+
closePending: (symbol: string, backtest: boolean, closeId?: string) => Promise<void>;
|
|
2670
|
+
/**
|
|
2671
|
+
* Executes partial close at profit level (moving toward TP).
|
|
2672
|
+
*
|
|
2673
|
+
* Closes specified percentage of position at current price.
|
|
2674
|
+
* Updates _tpClosed, _totalClosed, and _partialHistory state.
|
|
2675
|
+
* Persists updated signal state for crash recovery.
|
|
2676
|
+
*
|
|
2677
|
+
* Validations:
|
|
2678
|
+
* - Throws if no pending signal exists
|
|
2679
|
+
* - Throws if called on scheduled signal (not yet activated)
|
|
2680
|
+
* - Throws if percentToClose <= 0 or > 100
|
|
2681
|
+
* - Returns false if _totalClosed + percentToClose > 100 (prevents over-closing)
|
|
2682
|
+
*
|
|
2683
|
+
* Use case: User-controlled partial close triggered from onPartialProfit callback.
|
|
2684
|
+
*
|
|
2685
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
2686
|
+
* @param percentToClose - Absolute percentage of position to close (0-100)
|
|
2687
|
+
* @param currentPrice - Current market price for partial close
|
|
2688
|
+
* @param backtest - Whether running in backtest mode
|
|
2689
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped
|
|
2690
|
+
*
|
|
2691
|
+
* @example
|
|
2692
|
+
* ```typescript
|
|
2693
|
+
* callbacks: {
|
|
2694
|
+
* onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
|
|
2695
|
+
* if (percentTp >= 50) {
|
|
2696
|
+
* const success = await strategy.partialProfit(symbol, 25, currentPrice, backtest);
|
|
2697
|
+
* if (success) {
|
|
2698
|
+
* console.log('Partial profit executed');
|
|
2699
|
+
* }
|
|
2700
|
+
* }
|
|
2701
|
+
* }
|
|
2702
|
+
* }
|
|
2703
|
+
* ```
|
|
2704
|
+
*/
|
|
2705
|
+
partialProfit: (symbol: string, percentToClose: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
|
|
2706
|
+
/**
|
|
2707
|
+
* Executes partial close at loss level (moving toward SL).
|
|
2708
|
+
*
|
|
2709
|
+
* Closes specified percentage of position at current price.
|
|
2710
|
+
* Updates _slClosed, _totalClosed, and _partialHistory state.
|
|
2711
|
+
* Persists updated signal state for crash recovery.
|
|
2712
|
+
*
|
|
2713
|
+
* Validations:
|
|
2714
|
+
* - Throws if no pending signal exists
|
|
2715
|
+
* - Throws if called on scheduled signal (not yet activated)
|
|
2716
|
+
* - Throws if percentToClose <= 0 or > 100
|
|
2717
|
+
* - Returns false if _totalClosed + percentToClose > 100 (prevents over-closing)
|
|
2718
|
+
*
|
|
2719
|
+
* Use case: User-controlled partial close triggered from onPartialLoss callback.
|
|
2720
|
+
*
|
|
2721
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
2722
|
+
* @param percentToClose - Absolute percentage of position to close (0-100)
|
|
2723
|
+
* @param currentPrice - Current market price for partial close
|
|
2724
|
+
* @param backtest - Whether running in backtest mode
|
|
2725
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped
|
|
2726
|
+
*
|
|
2727
|
+
* @example
|
|
2728
|
+
* ```typescript
|
|
2729
|
+
* callbacks: {
|
|
2730
|
+
* onPartialLoss: async (symbol, signal, currentPrice, percentSl, backtest) => {
|
|
2731
|
+
* if (percentSl >= 80) {
|
|
2732
|
+
* const success = await strategy.partialLoss(symbol, 50, currentPrice, backtest);
|
|
2733
|
+
* if (success) {
|
|
2734
|
+
* console.log('Partial loss executed');
|
|
2735
|
+
* }
|
|
2736
|
+
* }
|
|
2737
|
+
* }
|
|
2738
|
+
* }
|
|
2739
|
+
* ```
|
|
2740
|
+
*/
|
|
2741
|
+
partialLoss: (symbol: string, percentToClose: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
|
|
2742
|
+
/**
|
|
2743
|
+
* Adjusts trailing stop-loss by shifting distance between entry and original SL.
|
|
2744
|
+
*
|
|
2745
|
+
* CRITICAL: Always calculates from ORIGINAL SL, not from current trailing SL.
|
|
2746
|
+
* This prevents error accumulation on repeated calls.
|
|
2747
|
+
* Larger percentShift ABSORBS smaller one (updates only towards better protection).
|
|
2748
|
+
*
|
|
2749
|
+
* Calculates new SL based on percentage shift of the ORIGINAL distance (entry - originalSL):
|
|
2750
|
+
* - Negative %: tightens stop (moves SL closer to entry, reduces risk)
|
|
2751
|
+
* - Positive %: loosens stop (moves SL away from entry, allows more drawdown)
|
|
2752
|
+
*
|
|
2753
|
+
* For LONG position (entry=100, originalSL=90, distance=10%):
|
|
2754
|
+
* - percentShift = -50: newSL = 100 - 10%*(1-0.5) = 95 (5% distance, tighter)
|
|
2755
|
+
* - percentShift = +20: newSL = 100 - 10%*(1+0.2) = 88 (12% distance, looser)
|
|
2756
|
+
*
|
|
2757
|
+
* For SHORT position (entry=100, originalSL=110, distance=10%):
|
|
2758
|
+
* - percentShift = -50: newSL = 100 + 10%*(1-0.5) = 105 (5% distance, tighter)
|
|
2759
|
+
* - percentShift = +20: newSL = 100 + 10%*(1+0.2) = 112 (12% distance, looser)
|
|
2760
|
+
*
|
|
2761
|
+
* Absorption behavior:
|
|
2762
|
+
* - First call: sets trailing SL unconditionally
|
|
2763
|
+
* - Subsequent calls: updates only if new SL is BETTER (protects more profit)
|
|
2764
|
+
* - For LONG: only accepts HIGHER SL (never moves down, closer to entry wins)
|
|
2765
|
+
* - For SHORT: only accepts LOWER SL (never moves up, closer to entry wins)
|
|
2766
|
+
* - Stores in _trailingPriceStopLoss, original priceStopLoss always preserved
|
|
2767
|
+
*
|
|
2768
|
+
* Validations:
|
|
2769
|
+
* - Throws if no pending signal exists
|
|
2770
|
+
* - Throws if percentShift < -100 or > 100
|
|
2771
|
+
* - Throws if percentShift === 0
|
|
2772
|
+
* - Skips if new SL would cross entry price
|
|
2773
|
+
* - Skips if currentPrice already crossed new SL level (price intrusion protection)
|
|
2774
|
+
*
|
|
2775
|
+
* Use case: User-controlled trailing stop triggered from onPartialProfit callback.
|
|
2776
|
+
*
|
|
2777
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
2778
|
+
* @param percentShift - Percentage shift of ORIGINAL SL distance [-100, 100], excluding 0
|
|
2779
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
2780
|
+
* @param backtest - Whether running in backtest mode
|
|
2781
|
+
* @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected
|
|
2782
|
+
*
|
|
2783
|
+
* @example
|
|
2784
|
+
* ```typescript
|
|
2785
|
+
* callbacks: {
|
|
2786
|
+
* onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
|
|
2787
|
+
* if (percentTp >= 50) {
|
|
2788
|
+
* // LONG: entry=100, originalSL=90, distance=10%
|
|
2789
|
+
*
|
|
2790
|
+
* // First call: tighten by 5%
|
|
2791
|
+
* const success1 = await strategy.trailingStop(symbol, -5, currentPrice, backtest);
|
|
2792
|
+
* // success1 = true, newDistance = 10% - 5% = 5%, newSL = 95
|
|
2793
|
+
*
|
|
2794
|
+
* // Second call: try weaker protection
|
|
2795
|
+
* const success2 = await strategy.trailingStop(symbol, -3, currentPrice, backtest);
|
|
2796
|
+
* // success2 = false (SKIPPED: newSL=97 < 95, worse protection, larger % absorbs smaller)
|
|
2797
|
+
*
|
|
2798
|
+
* // Third call: stronger protection
|
|
2799
|
+
* const success3 = await strategy.trailingStop(symbol, -7, currentPrice, backtest);
|
|
2800
|
+
* // success3 = true (ACCEPTED: newDistance = 3%, newSL = 97 > 95, better protection)
|
|
2801
|
+
* }
|
|
2802
|
+
* }
|
|
2803
|
+
* }
|
|
2804
|
+
* ```
|
|
2805
|
+
*/
|
|
2806
|
+
trailingStop: (symbol: string, percentShift: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
|
|
2807
|
+
/**
|
|
2808
|
+
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
2809
|
+
*
|
|
2810
|
+
* CRITICAL: Always calculates from ORIGINAL TP, not from current trailing TP.
|
|
2811
|
+
* This prevents error accumulation on repeated calls.
|
|
2812
|
+
* Larger percentShift ABSORBS smaller one (updates only towards more conservative TP).
|
|
2813
|
+
*
|
|
2814
|
+
* Updates the take-profit distance by a percentage adjustment relative to the ORIGINAL TP distance.
|
|
2815
|
+
* Negative percentShift brings TP closer to entry (more conservative).
|
|
2816
|
+
* Positive percentShift moves TP further from entry (more aggressive).
|
|
2817
|
+
*
|
|
2818
|
+
* Absorption behavior:
|
|
2819
|
+
* - First call: sets trailing TP unconditionally
|
|
2820
|
+
* - Subsequent calls: updates only if new TP is MORE CONSERVATIVE (closer to entry)
|
|
2821
|
+
* - For LONG: only accepts LOWER TP (never moves up, closer to entry wins)
|
|
2822
|
+
* - For SHORT: only accepts HIGHER TP (never moves down, closer to entry wins)
|
|
2823
|
+
* - Stores in _trailingPriceTakeProfit, original priceTakeProfit always preserved
|
|
2824
|
+
*
|
|
2825
|
+
* Price intrusion protection: If current price has already crossed the new TP level,
|
|
2826
|
+
* the update is skipped to prevent immediate TP triggering.
|
|
2827
|
+
*
|
|
2828
|
+
* @param symbol - Trading pair symbol
|
|
2829
|
+
* @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
|
|
2830
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
2831
|
+
* @param backtest - Whether running in backtest mode
|
|
2832
|
+
* @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected
|
|
2833
|
+
*
|
|
2834
|
+
* @example
|
|
2835
|
+
* ```typescript
|
|
2836
|
+
* callbacks: {
|
|
2837
|
+
* onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
|
|
2838
|
+
* // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
|
|
2839
|
+
*
|
|
2840
|
+
* // First call: bring TP closer by 3%
|
|
2841
|
+
* const success1 = await strategy.trailingTake(symbol, -3, currentPrice, backtest);
|
|
2842
|
+
* // success1 = true, newDistance = 10% - 3% = 7%, newTP = 107
|
|
2843
|
+
*
|
|
2844
|
+
* // Second call: try to move TP further (less conservative)
|
|
2845
|
+
* const success2 = await strategy.trailingTake(symbol, 2, currentPrice, backtest);
|
|
2846
|
+
* // success2 = false (SKIPPED: newTP=112 > 107, less conservative, larger % absorbs smaller)
|
|
2847
|
+
*
|
|
2848
|
+
* // Third call: even more conservative
|
|
2849
|
+
* const success3 = await strategy.trailingTake(symbol, -5, currentPrice, backtest);
|
|
2850
|
+
* // success3 = true (ACCEPTED: newDistance = 5%, newTP = 105 < 107, more conservative)
|
|
2851
|
+
* }
|
|
2852
|
+
* }
|
|
2853
|
+
* ```
|
|
2854
|
+
*/
|
|
2855
|
+
trailingTake: (symbol: string, percentShift: number, currentPrice: number, backtest: boolean) => Promise<boolean>;
|
|
2856
|
+
/**
|
|
2857
|
+
* Moves stop-loss to breakeven (entry price) when price reaches threshold.
|
|
2858
|
+
*
|
|
2859
|
+
* Moves SL to entry price (zero-risk position) when current price has moved
|
|
2860
|
+
* far enough in profit direction to cover transaction costs (slippage + fees).
|
|
2861
|
+
* Threshold is calculated as: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2
|
|
2862
|
+
*
|
|
2863
|
+
* Behavior:
|
|
2864
|
+
* - Returns true if SL was moved to breakeven
|
|
2865
|
+
* - Returns false if conditions not met (threshold not reached or already at breakeven)
|
|
2866
|
+
* - Uses _trailingPriceStopLoss to store breakeven SL (preserves original priceStopLoss)
|
|
2867
|
+
* - Only moves SL once per position (idempotent - safe to call multiple times)
|
|
2868
|
+
*
|
|
2869
|
+
* For LONG position (entry=100, slippage=0.1%, fee=0.1%):
|
|
2870
|
+
* - Threshold: (0.1 + 0.1) * 2 = 0.4%
|
|
2871
|
+
* - Breakeven available when price >= 100.4 (entry + 0.4%)
|
|
2872
|
+
* - Moves SL from original (e.g. 95) to 100 (breakeven)
|
|
2873
|
+
* - Returns true on first successful move, false on subsequent calls
|
|
2874
|
+
*
|
|
2875
|
+
* For SHORT position (entry=100, slippage=0.1%, fee=0.1%):
|
|
2876
|
+
* - Threshold: (0.1 + 0.1) * 2 = 0.4%
|
|
2877
|
+
* - Breakeven available when price <= 99.6 (entry - 0.4%)
|
|
2878
|
+
* - Moves SL from original (e.g. 105) to 100 (breakeven)
|
|
2879
|
+
* - Returns true on first successful move, false on subsequent calls
|
|
2880
|
+
*
|
|
2881
|
+
* Validations:
|
|
2882
|
+
* - Throws if no pending signal exists
|
|
2883
|
+
* - Throws if currentPrice is not a positive finite number
|
|
2884
|
+
*
|
|
2885
|
+
* Use case: User-controlled breakeven protection triggered from onPartialProfit callback.
|
|
2886
|
+
*
|
|
2887
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
2888
|
+
* @param currentPrice - Current market price to check threshold
|
|
2889
|
+
* @param backtest - Whether running in backtest mode
|
|
2890
|
+
* @returns Promise<boolean> - true if breakeven was set, false if conditions not met
|
|
2891
|
+
*
|
|
2892
|
+
* @example
|
|
2893
|
+
* ```typescript
|
|
2894
|
+
* callbacks: {
|
|
2895
|
+
* onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
|
|
2896
|
+
* // Try to move SL to breakeven when threshold reached
|
|
2897
|
+
* const movedToBreakeven = await strategy.breakeven(symbol, currentPrice, backtest);
|
|
2898
|
+
* if (movedToBreakeven) {
|
|
2899
|
+
* console.log(`Position moved to breakeven at ${currentPrice}`);
|
|
2900
|
+
* }
|
|
2901
|
+
* }
|
|
2902
|
+
* }
|
|
3183
2903
|
* ```
|
|
3184
2904
|
*/
|
|
3185
|
-
|
|
2905
|
+
breakeven: (symbol: string, currentPrice: number, backtest: boolean) => Promise<boolean>;
|
|
3186
2906
|
/**
|
|
3187
|
-
*
|
|
2907
|
+
* Adds a new averaging entry to an open position (DCA — Dollar Cost Averaging).
|
|
3188
2908
|
*
|
|
3189
|
-
*
|
|
3190
|
-
*
|
|
3191
|
-
*
|
|
2909
|
+
* Appends currentPrice to the _entry array. The effective entry price used in all
|
|
2910
|
+
* distance and PNL calculations becomes the simple arithmetic mean of all _entry prices.
|
|
2911
|
+
* Original priceOpen is preserved unchanged for identity/audit purposes.
|
|
3192
2912
|
*
|
|
3193
|
-
*
|
|
2913
|
+
* Rejection rules (returns false without throwing):
|
|
2914
|
+
* - LONG: currentPrice >= last entry price (must average down, not up or equal)
|
|
2915
|
+
* - SHORT: currentPrice <= last entry price (must average down, not up or equal)
|
|
2916
|
+
*
|
|
2917
|
+
* Validations (throws):
|
|
2918
|
+
* - No pending signal exists
|
|
2919
|
+
* - currentPrice is not a positive finite number
|
|
3194
2920
|
*
|
|
3195
2921
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
2922
|
+
* @param currentPrice - New entry price to add to the averaging history
|
|
3196
2923
|
* @param backtest - Whether running in backtest mode
|
|
3197
|
-
* @
|
|
3198
|
-
|
|
2924
|
+
* @returns Promise<boolean> - true if entry added, false if rejected by direction check
|
|
2925
|
+
*/
|
|
2926
|
+
averageBuy: (symbol: string, currentPrice: number, backtest: boolean) => Promise<boolean>;
|
|
2927
|
+
/**
|
|
2928
|
+
* Disposes the strategy instance and cleans up resources.
|
|
3199
2929
|
*
|
|
3200
|
-
*
|
|
3201
|
-
*
|
|
3202
|
-
*
|
|
3203
|
-
*
|
|
3204
|
-
* // Strategy continues, can generate new signals
|
|
3205
|
-
* ```
|
|
2930
|
+
* Called when the strategy is being removed from cache or shut down.
|
|
2931
|
+
* Invokes the onDispose callback to notify external systems.
|
|
2932
|
+
*
|
|
2933
|
+
* @returns Promise that resolves when disposal is complete
|
|
3206
2934
|
*/
|
|
3207
|
-
|
|
2935
|
+
dispose: () => Promise<void>;
|
|
2936
|
+
}
|
|
2937
|
+
/**
|
|
2938
|
+
* Unique strategy identifier.
|
|
2939
|
+
*/
|
|
2940
|
+
type StrategyName = string;
|
|
2941
|
+
|
|
2942
|
+
/**
|
|
2943
|
+
* Method context containing schema names for operation routing.
|
|
2944
|
+
*
|
|
2945
|
+
* Propagated through MethodContextService to provide implicit context
|
|
2946
|
+
* for retrieving correct strategy/exchange/frame instances.
|
|
2947
|
+
*/
|
|
2948
|
+
interface IMethodContext {
|
|
2949
|
+
/** Name of exchange schema to use */
|
|
2950
|
+
exchangeName: ExchangeName;
|
|
2951
|
+
/** Name of strategy schema to use */
|
|
2952
|
+
strategyName: StrategyName;
|
|
2953
|
+
/** Name of frame schema to use (empty string for live mode) */
|
|
2954
|
+
frameName: FrameName;
|
|
2955
|
+
}
|
|
2956
|
+
/**
|
|
2957
|
+
* Scoped service for method context propagation.
|
|
2958
|
+
*
|
|
2959
|
+
* Uses di-scoped for implicit context passing without explicit parameters.
|
|
2960
|
+
* Context includes strategyName, exchangeName, and frameName.
|
|
2961
|
+
*
|
|
2962
|
+
* Used by PublicServices to inject schema names into ConnectionServices.
|
|
2963
|
+
*
|
|
2964
|
+
* @example
|
|
2965
|
+
* ```typescript
|
|
2966
|
+
* MethodContextService.runAsyncIterator(
|
|
2967
|
+
* backtestGenerator,
|
|
2968
|
+
* {
|
|
2969
|
+
* strategyName: "my-strategy",
|
|
2970
|
+
* exchangeName: "my-exchange",
|
|
2971
|
+
* frameName: "1d-backtest"
|
|
2972
|
+
* }
|
|
2973
|
+
* );
|
|
2974
|
+
* ```
|
|
2975
|
+
*/
|
|
2976
|
+
declare const MethodContextService: (new () => {
|
|
2977
|
+
readonly context: IMethodContext;
|
|
2978
|
+
}) & Omit<{
|
|
2979
|
+
new (context: IMethodContext): {
|
|
2980
|
+
readonly context: IMethodContext;
|
|
2981
|
+
};
|
|
2982
|
+
}, "prototype"> & di_scoped.IScopedClassRun<[context: IMethodContext]>;
|
|
2983
|
+
|
|
2984
|
+
/**
|
|
2985
|
+
* Single log entry stored in the log history.
|
|
2986
|
+
*/
|
|
2987
|
+
interface ILogEntry {
|
|
2988
|
+
/** Unique entry identifier generated via randomString */
|
|
2989
|
+
id: string;
|
|
2990
|
+
/** Log level */
|
|
2991
|
+
type: "log" | "debug" | "info" | "warn";
|
|
2992
|
+
/** Unix timestamp in milliseconds when the entry was created */
|
|
2993
|
+
timestamp: number;
|
|
2994
|
+
/** Date taken from backtest context to improve user experience */
|
|
2995
|
+
createdAt: string;
|
|
2996
|
+
/** Optional method context associated with the log entry, providing additional details about the execution environment or state when the log was recorded */
|
|
2997
|
+
methodContext: IMethodContext | null;
|
|
2998
|
+
/** Optional execution context associated with the log entry, providing additional details about the execution environment or state when the log was recorded */
|
|
2999
|
+
executionContext: IExecutionContext | null;
|
|
3000
|
+
/** Log topic / method name */
|
|
3001
|
+
topic: string;
|
|
3002
|
+
/** Additional arguments passed to the log call */
|
|
3003
|
+
args: unknown[];
|
|
3004
|
+
}
|
|
3005
|
+
/**
|
|
3006
|
+
* Interface representing a logging mechanism for the swarm system.
|
|
3007
|
+
* Provides methods to record messages at different severity levels, used across components like agents, sessions, states, storage, swarms, history, embeddings, completions, and policies.
|
|
3008
|
+
* Logs are utilized to track lifecycle events (e.g., initialization, disposal), operational details (e.g., tool calls, message emissions), validation outcomes (e.g., policy checks), and errors (e.g., persistence failures), aiding in debugging, monitoring, and auditing.
|
|
3009
|
+
*/
|
|
3010
|
+
interface ILogger {
|
|
3011
|
+
/**
|
|
3012
|
+
* Logs a general-purpose message.
|
|
3013
|
+
* Used throughout the swarm system to record significant events or state changes, such as agent execution, session connections, or storage updates.
|
|
3014
|
+
*/
|
|
3015
|
+
log(topic: string, ...args: any[]): void;
|
|
3016
|
+
/**
|
|
3017
|
+
* Logs a debug-level message.
|
|
3018
|
+
* Employed for detailed diagnostic information, such as intermediate states during agent tool calls, swarm navigation changes, or embedding creation processes, typically enabled in development or troubleshooting scenarios.
|
|
3019
|
+
*/
|
|
3020
|
+
debug(topic: string, ...args: any[]): void;
|
|
3021
|
+
/**
|
|
3022
|
+
* Logs an info-level message.
|
|
3023
|
+
* Used to record informational updates, such as successful completions, policy validations, or history commits, providing a high-level overview of system activity without excessive detail.
|
|
3024
|
+
*/
|
|
3025
|
+
info(topic: string, ...args: any[]): void;
|
|
3026
|
+
/**
|
|
3027
|
+
* Logs a warning-level message.
|
|
3028
|
+
* Used to record potentially problematic situations that don't prevent execution but may require attention, such as missing data, unexpected conditions, or deprecated usage.
|
|
3029
|
+
*/
|
|
3030
|
+
warn(topic: string, ...args: any[]): void;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
/**
|
|
3034
|
+
* Candle time interval for fetching historical data.
|
|
3035
|
+
*/
|
|
3036
|
+
type CandleInterval = "1m" | "3m" | "5m" | "15m" | "30m" | "1h" | "2h" | "4h" | "6h" | "8h";
|
|
3037
|
+
/** Numeric type that can be undefined (used for optional numeric values) */
|
|
3038
|
+
type Num = number | undefined;
|
|
3039
|
+
interface IPublicCandleData {
|
|
3040
|
+
/** Unix timestamp in milliseconds when candle opened */
|
|
3041
|
+
timestamp: Num;
|
|
3042
|
+
/** Opening price at candle start */
|
|
3043
|
+
open: Num;
|
|
3044
|
+
/** Highest price during candle period */
|
|
3045
|
+
high: Num;
|
|
3046
|
+
/** Lowest price during candle period */
|
|
3047
|
+
low: Num;
|
|
3048
|
+
/** Closing price at candle end */
|
|
3049
|
+
close: Num;
|
|
3050
|
+
/** Trading volume during candle period */
|
|
3051
|
+
volume: Num;
|
|
3052
|
+
}
|
|
3053
|
+
/**
|
|
3054
|
+
* Single OHLCV candle data point.
|
|
3055
|
+
* Used for VWAP calculation and backtesting.
|
|
3056
|
+
*/
|
|
3057
|
+
interface ICandleData {
|
|
3058
|
+
/** Unix timestamp in milliseconds when candle opened */
|
|
3059
|
+
timestamp: number;
|
|
3060
|
+
/** Opening price at candle start */
|
|
3061
|
+
open: number;
|
|
3062
|
+
/** Highest price during candle period */
|
|
3063
|
+
high: number;
|
|
3064
|
+
/** Lowest price during candle period */
|
|
3065
|
+
low: number;
|
|
3066
|
+
/** Closing price at candle end */
|
|
3067
|
+
close: number;
|
|
3068
|
+
/** Trading volume during candle period */
|
|
3069
|
+
volume: number;
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* Single bid or ask in order book.
|
|
3073
|
+
*/
|
|
3074
|
+
interface IBidData {
|
|
3075
|
+
/** Price level as string */
|
|
3076
|
+
price: string;
|
|
3077
|
+
/** Quantity at this price level as string */
|
|
3078
|
+
quantity: string;
|
|
3079
|
+
}
|
|
3080
|
+
/**
|
|
3081
|
+
* Order book data containing bids and asks.
|
|
3082
|
+
*/
|
|
3083
|
+
interface IOrderBookData {
|
|
3084
|
+
/** Trading pair symbol */
|
|
3085
|
+
symbol: string;
|
|
3086
|
+
/** Array of bid orders (buy orders) */
|
|
3087
|
+
bids: IBidData[];
|
|
3088
|
+
/** Array of ask orders (sell orders) */
|
|
3089
|
+
asks: IBidData[];
|
|
3090
|
+
}
|
|
3091
|
+
/**
|
|
3092
|
+
* Aggregated trade data point.
|
|
3093
|
+
* Represents a single trade that has occurred, used for detailed analysis and backtesting.
|
|
3094
|
+
* Includes price, quantity, timestamp, and whether the buyer is the market maker (which can indicate trade direction).
|
|
3095
|
+
*
|
|
3096
|
+
*/
|
|
3097
|
+
interface IAggregatedTradeData {
|
|
3098
|
+
/** Unique identifier for the aggregated trade */
|
|
3099
|
+
id: string;
|
|
3100
|
+
/** Price at which the trade occurred */
|
|
3101
|
+
price: number;
|
|
3102
|
+
/** Quantity traded */
|
|
3103
|
+
qty: number;
|
|
3104
|
+
/** Unix timestamp in milliseconds when the trade occurred */
|
|
3105
|
+
timestamp: number;
|
|
3106
|
+
/** Whether the buyer is the market maker (true if buyer is maker, false if seller is maker) */
|
|
3107
|
+
isBuyerMaker: boolean;
|
|
3108
|
+
}
|
|
3109
|
+
/**
|
|
3110
|
+
* Exchange parameters passed to ClientExchange constructor.
|
|
3111
|
+
* Combines schema with runtime dependencies.
|
|
3112
|
+
* Note: All exchange methods are required in params (defaults are applied during initialization).
|
|
3113
|
+
*/
|
|
3114
|
+
interface IExchangeParams extends IExchangeSchema {
|
|
3115
|
+
/** Logger service for debug output */
|
|
3116
|
+
logger: ILogger;
|
|
3117
|
+
/** Execution context service (symbol, when, backtest flag) */
|
|
3118
|
+
execution: TExecutionContextService;
|
|
3119
|
+
/** Fetch candles from data source (required, defaults applied) */
|
|
3120
|
+
getCandles: (symbol: string, interval: CandleInterval, since: Date, limit: number, backtest: boolean) => Promise<ICandleData[]>;
|
|
3121
|
+
/** Format quantity according to exchange precision rules (required, defaults applied) */
|
|
3122
|
+
formatQuantity: (symbol: string, quantity: number, backtest: boolean) => Promise<string>;
|
|
3123
|
+
/** Format price according to exchange precision rules (required, defaults applied) */
|
|
3124
|
+
formatPrice: (symbol: string, price: number, backtest: boolean) => Promise<string>;
|
|
3125
|
+
/** Fetch order book for a trading pair (required, defaults applied) */
|
|
3126
|
+
getOrderBook: (symbol: string, depth: number, from: Date, to: Date, backtest: boolean) => Promise<IOrderBookData>;
|
|
3127
|
+
/** Fetch aggregated trades for a trading pair (required, defaults applied) */
|
|
3128
|
+
getAggregatedTrades: (symbol: string, from: Date, to: Date, backtest: boolean) => Promise<IAggregatedTradeData[]>;
|
|
3129
|
+
}
|
|
3130
|
+
/**
|
|
3131
|
+
* Optional callbacks for exchange data events.
|
|
3132
|
+
*/
|
|
3133
|
+
interface IExchangeCallbacks {
|
|
3134
|
+
/** Called when candle data is fetched */
|
|
3135
|
+
onCandleData: (symbol: string, interval: CandleInterval, since: Date, limit: number, data: ICandleData[]) => void | Promise<void>;
|
|
3136
|
+
}
|
|
3137
|
+
/**
|
|
3138
|
+
* Exchange schema registered via addExchange().
|
|
3139
|
+
* Defines candle data source and formatting logic.
|
|
3140
|
+
*/
|
|
3141
|
+
interface IExchangeSchema {
|
|
3142
|
+
/** Unique exchange identifier for registration */
|
|
3143
|
+
exchangeName: ExchangeName;
|
|
3144
|
+
/** Optional developer note for documentation */
|
|
3145
|
+
note?: string;
|
|
3208
3146
|
/**
|
|
3209
|
-
*
|
|
3210
|
-
*
|
|
3211
|
-
* Closes specified percentage of position at current price.
|
|
3212
|
-
* Updates _tpClosed, _totalClosed, and _partialHistory state.
|
|
3213
|
-
* Persists updated signal state for crash recovery.
|
|
3214
|
-
*
|
|
3215
|
-
* Validations:
|
|
3216
|
-
* - Throws if no pending signal exists
|
|
3217
|
-
* - Throws if called on scheduled signal (not yet activated)
|
|
3218
|
-
* - Throws if percentToClose <= 0 or > 100
|
|
3219
|
-
* - Returns false if _totalClosed + percentToClose > 100 (prevents over-closing)
|
|
3220
|
-
*
|
|
3221
|
-
* Use case: User-controlled partial close triggered from onPartialProfit callback.
|
|
3147
|
+
* Fetch candles from data source (API or database).
|
|
3222
3148
|
*
|
|
3223
3149
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3224
|
-
* @param
|
|
3225
|
-
* @param
|
|
3150
|
+
* @param interval - Candle time interval (e.g., "1m", "1h")
|
|
3151
|
+
* @param since - Start date for candle fetching
|
|
3152
|
+
* @param limit - Maximum number of candles to fetch
|
|
3226
3153
|
* @param backtest - Whether running in backtest mode
|
|
3227
|
-
* @returns Promise
|
|
3228
|
-
*
|
|
3229
|
-
* @example
|
|
3230
|
-
* ```typescript
|
|
3231
|
-
* callbacks: {
|
|
3232
|
-
* onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
|
|
3233
|
-
* if (percentTp >= 50) {
|
|
3234
|
-
* const success = await strategy.partialProfit(symbol, 25, currentPrice, backtest);
|
|
3235
|
-
* if (success) {
|
|
3236
|
-
* console.log('Partial profit executed');
|
|
3237
|
-
* }
|
|
3238
|
-
* }
|
|
3239
|
-
* }
|
|
3240
|
-
* }
|
|
3241
|
-
* ```
|
|
3154
|
+
* @returns Promise resolving to array of OHLCV candle data
|
|
3242
3155
|
*/
|
|
3243
|
-
|
|
3156
|
+
getCandles: (symbol: string, interval: CandleInterval, since: Date, limit: number, backtest: boolean) => Promise<IPublicCandleData[]>;
|
|
3244
3157
|
/**
|
|
3245
|
-
*
|
|
3246
|
-
*
|
|
3247
|
-
* Closes specified percentage of position at current price.
|
|
3248
|
-
* Updates _slClosed, _totalClosed, and _partialHistory state.
|
|
3249
|
-
* Persists updated signal state for crash recovery.
|
|
3250
|
-
*
|
|
3251
|
-
* Validations:
|
|
3252
|
-
* - Throws if no pending signal exists
|
|
3253
|
-
* - Throws if called on scheduled signal (not yet activated)
|
|
3254
|
-
* - Throws if percentToClose <= 0 or > 100
|
|
3255
|
-
* - Returns false if _totalClosed + percentToClose > 100 (prevents over-closing)
|
|
3158
|
+
* Format quantity according to exchange precision rules.
|
|
3256
3159
|
*
|
|
3257
|
-
*
|
|
3160
|
+
* Optional. If not provided, defaults to Bitcoin precision on Binance (8 decimal places).
|
|
3258
3161
|
*
|
|
3259
|
-
* @param symbol - Trading pair symbol
|
|
3260
|
-
* @param
|
|
3261
|
-
* @param currentPrice - Current market price for partial close
|
|
3162
|
+
* @param symbol - Trading pair symbol
|
|
3163
|
+
* @param quantity - Raw quantity value
|
|
3262
3164
|
* @param backtest - Whether running in backtest mode
|
|
3263
|
-
* @returns Promise
|
|
3264
|
-
*
|
|
3265
|
-
* @example
|
|
3266
|
-
* ```typescript
|
|
3267
|
-
* callbacks: {
|
|
3268
|
-
* onPartialLoss: async (symbol, signal, currentPrice, percentSl, backtest) => {
|
|
3269
|
-
* if (percentSl >= 80) {
|
|
3270
|
-
* const success = await strategy.partialLoss(symbol, 50, currentPrice, backtest);
|
|
3271
|
-
* if (success) {
|
|
3272
|
-
* console.log('Partial loss executed');
|
|
3273
|
-
* }
|
|
3274
|
-
* }
|
|
3275
|
-
* }
|
|
3276
|
-
* }
|
|
3277
|
-
* ```
|
|
3165
|
+
* @returns Promise resolving to formatted quantity string
|
|
3278
3166
|
*/
|
|
3279
|
-
|
|
3167
|
+
formatQuantity?: (symbol: string, quantity: number, backtest: boolean) => Promise<string>;
|
|
3280
3168
|
/**
|
|
3281
|
-
*
|
|
3282
|
-
*
|
|
3283
|
-
* CRITICAL: Always calculates from ORIGINAL SL, not from current trailing SL.
|
|
3284
|
-
* This prevents error accumulation on repeated calls.
|
|
3285
|
-
* Larger percentShift ABSORBS smaller one (updates only towards better protection).
|
|
3286
|
-
*
|
|
3287
|
-
* Calculates new SL based on percentage shift of the ORIGINAL distance (entry - originalSL):
|
|
3288
|
-
* - Negative %: tightens stop (moves SL closer to entry, reduces risk)
|
|
3289
|
-
* - Positive %: loosens stop (moves SL away from entry, allows more drawdown)
|
|
3290
|
-
*
|
|
3291
|
-
* For LONG position (entry=100, originalSL=90, distance=10%):
|
|
3292
|
-
* - percentShift = -50: newSL = 100 - 10%*(1-0.5) = 95 (5% distance, tighter)
|
|
3293
|
-
* - percentShift = +20: newSL = 100 - 10%*(1+0.2) = 88 (12% distance, looser)
|
|
3294
|
-
*
|
|
3295
|
-
* For SHORT position (entry=100, originalSL=110, distance=10%):
|
|
3296
|
-
* - percentShift = -50: newSL = 100 + 10%*(1-0.5) = 105 (5% distance, tighter)
|
|
3297
|
-
* - percentShift = +20: newSL = 100 + 10%*(1+0.2) = 112 (12% distance, looser)
|
|
3169
|
+
* Format price according to exchange precision rules.
|
|
3298
3170
|
*
|
|
3299
|
-
*
|
|
3300
|
-
* - First call: sets trailing SL unconditionally
|
|
3301
|
-
* - Subsequent calls: updates only if new SL is BETTER (protects more profit)
|
|
3302
|
-
* - For LONG: only accepts HIGHER SL (never moves down, closer to entry wins)
|
|
3303
|
-
* - For SHORT: only accepts LOWER SL (never moves up, closer to entry wins)
|
|
3304
|
-
* - Stores in _trailingPriceStopLoss, original priceStopLoss always preserved
|
|
3171
|
+
* Optional. If not provided, defaults to Bitcoin precision on Binance (2 decimal places).
|
|
3305
3172
|
*
|
|
3306
|
-
*
|
|
3307
|
-
*
|
|
3308
|
-
*
|
|
3309
|
-
*
|
|
3310
|
-
|
|
3311
|
-
|
|
3173
|
+
* @param symbol - Trading pair symbol
|
|
3174
|
+
* @param price - Raw price value
|
|
3175
|
+
* @param backtest - Whether running in backtest mode
|
|
3176
|
+
* @returns Promise resolving to formatted price string
|
|
3177
|
+
*/
|
|
3178
|
+
formatPrice?: (symbol: string, price: number, backtest: boolean) => Promise<string>;
|
|
3179
|
+
/**
|
|
3180
|
+
* Fetch order book for a trading pair.
|
|
3312
3181
|
*
|
|
3313
|
-
*
|
|
3182
|
+
* Optional. If not provided, throws an error when called.
|
|
3314
3183
|
*
|
|
3315
3184
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3316
|
-
* @param
|
|
3317
|
-
* @param
|
|
3185
|
+
* @param depth - Maximum depth levels for both bids and asks (default: CC_ORDER_BOOK_MAX_DEPTH_LEVELS)
|
|
3186
|
+
* @param from - Start of time range (used in backtest for historical data, can be ignored in live)
|
|
3187
|
+
* @param to - End of time range (used in backtest for historical data, can be ignored in live)
|
|
3318
3188
|
* @param backtest - Whether running in backtest mode
|
|
3319
|
-
* @returns Promise
|
|
3189
|
+
* @returns Promise resolving to order book data
|
|
3320
3190
|
*
|
|
3321
3191
|
* @example
|
|
3322
3192
|
* ```typescript
|
|
3323
|
-
*
|
|
3324
|
-
*
|
|
3325
|
-
*
|
|
3326
|
-
*
|
|
3327
|
-
*
|
|
3328
|
-
* // First call: tighten by 5%
|
|
3329
|
-
* const success1 = await strategy.trailingStop(symbol, -5, currentPrice, backtest);
|
|
3330
|
-
* // success1 = true, newDistance = 10% - 5% = 5%, newSL = 95
|
|
3331
|
-
*
|
|
3332
|
-
* // Second call: try weaker protection
|
|
3333
|
-
* const success2 = await strategy.trailingStop(symbol, -3, currentPrice, backtest);
|
|
3334
|
-
* // success2 = false (SKIPPED: newSL=97 < 95, worse protection, larger % absorbs smaller)
|
|
3335
|
-
*
|
|
3336
|
-
* // Third call: stronger protection
|
|
3337
|
-
* const success3 = await strategy.trailingStop(symbol, -7, currentPrice, backtest);
|
|
3338
|
-
* // success3 = true (ACCEPTED: newDistance = 3%, newSL = 97 > 95, better protection)
|
|
3339
|
-
* }
|
|
3193
|
+
* // Backtest implementation: returns historical order book for the time range
|
|
3194
|
+
* const backtestOrderBook = async (symbol: string, depth: number, from: Date, to: Date, backtest: boolean) => {
|
|
3195
|
+
* if (backtest) {
|
|
3196
|
+
* return await database.getOrderBookSnapshot(symbol, depth, from, to);
|
|
3340
3197
|
* }
|
|
3341
|
-
*
|
|
3198
|
+
* return await exchange.fetchOrderBook(symbol, depth);
|
|
3199
|
+
* };
|
|
3200
|
+
*
|
|
3201
|
+
* // Live implementation: ignores from/to when not in backtest mode
|
|
3202
|
+
* const liveOrderBook = async (symbol: string, depth: number, _from: Date, _to: Date, backtest: boolean) => {
|
|
3203
|
+
* return await exchange.fetchOrderBook(symbol, depth);
|
|
3204
|
+
* };
|
|
3342
3205
|
* ```
|
|
3343
3206
|
*/
|
|
3344
|
-
|
|
3207
|
+
getOrderBook?: (symbol: string, depth: number, from: Date, to: Date, backtest: boolean) => Promise<IOrderBookData>;
|
|
3345
3208
|
/**
|
|
3346
|
-
*
|
|
3347
|
-
*
|
|
3348
|
-
*
|
|
3349
|
-
*
|
|
3350
|
-
*
|
|
3351
|
-
*
|
|
3352
|
-
* Updates the take-profit distance by a percentage adjustment relative to the ORIGINAL TP distance.
|
|
3353
|
-
* Negative percentShift brings TP closer to entry (more conservative).
|
|
3354
|
-
* Positive percentShift moves TP further from entry (more aggressive).
|
|
3355
|
-
*
|
|
3356
|
-
* Absorption behavior:
|
|
3357
|
-
* - First call: sets trailing TP unconditionally
|
|
3358
|
-
* - Subsequent calls: updates only if new TP is MORE CONSERVATIVE (closer to entry)
|
|
3359
|
-
* - For LONG: only accepts LOWER TP (never moves up, closer to entry wins)
|
|
3360
|
-
* - For SHORT: only accepts HIGHER TP (never moves down, closer to entry wins)
|
|
3361
|
-
* - Stores in _trailingPriceTakeProfit, original priceTakeProfit always preserved
|
|
3362
|
-
*
|
|
3363
|
-
* Price intrusion protection: If current price has already crossed the new TP level,
|
|
3364
|
-
* the update is skipped to prevent immediate TP triggering.
|
|
3365
|
-
*
|
|
3366
|
-
* @param symbol - Trading pair symbol
|
|
3367
|
-
* @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
|
|
3368
|
-
* @param currentPrice - Current market price to check for intrusion
|
|
3209
|
+
* Fetch aggregated trades for a trading pair.
|
|
3210
|
+
* Optional. If not provided, throws an error when called.
|
|
3211
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3212
|
+
* @param from - Start of time range (used in backtest for historical data, can be ignored in live)
|
|
3213
|
+
* @param to - End of time range (used in backtest for historical data, can be ignored in live)
|
|
3369
3214
|
* @param backtest - Whether running in backtest mode
|
|
3370
|
-
* @
|
|
3371
|
-
*
|
|
3215
|
+
* @return Promise resolving to array of aggregated trade data
|
|
3372
3216
|
* @example
|
|
3373
3217
|
* ```typescript
|
|
3374
|
-
*
|
|
3375
|
-
*
|
|
3376
|
-
*
|
|
3377
|
-
*
|
|
3378
|
-
* // First call: bring TP closer by 3%
|
|
3379
|
-
* const success1 = await strategy.trailingTake(symbol, -3, currentPrice, backtest);
|
|
3380
|
-
* // success1 = true, newDistance = 10% - 3% = 7%, newTP = 107
|
|
3381
|
-
*
|
|
3382
|
-
* // Second call: try to move TP further (less conservative)
|
|
3383
|
-
* const success2 = await strategy.trailingTake(symbol, 2, currentPrice, backtest);
|
|
3384
|
-
* // success2 = false (SKIPPED: newTP=112 > 107, less conservative, larger % absorbs smaller)
|
|
3385
|
-
*
|
|
3386
|
-
* // Third call: even more conservative
|
|
3387
|
-
* const success3 = await strategy.trailingTake(symbol, -5, currentPrice, backtest);
|
|
3388
|
-
* // success3 = true (ACCEPTED: newDistance = 5%, newTP = 105 < 107, more conservative)
|
|
3218
|
+
* // Backtest implementation: returns historical aggregated trades for the time range
|
|
3219
|
+
* const backtestAggregatedTrades = async (symbol: string, from: Date, to: Date, backtest: boolean) => {
|
|
3220
|
+
* if (backtest) {
|
|
3221
|
+
* return await database.getAggregatedTrades(symbol, from, to);
|
|
3389
3222
|
* }
|
|
3390
|
-
*
|
|
3223
|
+
* return await exchange.fetchAggregatedTrades(symbol);
|
|
3224
|
+
* };
|
|
3225
|
+
*
|
|
3226
|
+
* // Live implementation: ignores from/to when not in backtest mode
|
|
3227
|
+
* const liveAggregatedTrades = async (symbol: string, _from: Date, _to: Date, backtest: boolean) => {
|
|
3228
|
+
* return await exchange.fetchAggregatedTrades(symbol);
|
|
3229
|
+
* };
|
|
3391
3230
|
* ```
|
|
3392
3231
|
*/
|
|
3393
|
-
|
|
3232
|
+
getAggregatedTrades?: (symbol: string, from: Date, to: Date, backtest: boolean) => Promise<IAggregatedTradeData[]>;
|
|
3233
|
+
/** Optional lifecycle event callbacks (onCandleData) */
|
|
3234
|
+
callbacks?: Partial<IExchangeCallbacks>;
|
|
3235
|
+
}
|
|
3236
|
+
/**
|
|
3237
|
+
* Exchange interface implemented by ClientExchange.
|
|
3238
|
+
* Provides candle data access and VWAP calculation.
|
|
3239
|
+
*/
|
|
3240
|
+
interface IExchange {
|
|
3394
3241
|
/**
|
|
3395
|
-
*
|
|
3396
|
-
*
|
|
3397
|
-
* Moves SL to entry price (zero-risk position) when current price has moved
|
|
3398
|
-
* far enough in profit direction to cover transaction costs (slippage + fees).
|
|
3399
|
-
* Threshold is calculated as: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2
|
|
3400
|
-
*
|
|
3401
|
-
* Behavior:
|
|
3402
|
-
* - Returns true if SL was moved to breakeven
|
|
3403
|
-
* - Returns false if conditions not met (threshold not reached or already at breakeven)
|
|
3404
|
-
* - Uses _trailingPriceStopLoss to store breakeven SL (preserves original priceStopLoss)
|
|
3405
|
-
* - Only moves SL once per position (idempotent - safe to call multiple times)
|
|
3406
|
-
*
|
|
3407
|
-
* For LONG position (entry=100, slippage=0.1%, fee=0.1%):
|
|
3408
|
-
* - Threshold: (0.1 + 0.1) * 2 = 0.4%
|
|
3409
|
-
* - Breakeven available when price >= 100.4 (entry + 0.4%)
|
|
3410
|
-
* - Moves SL from original (e.g. 95) to 100 (breakeven)
|
|
3411
|
-
* - Returns true on first successful move, false on subsequent calls
|
|
3412
|
-
*
|
|
3413
|
-
* For SHORT position (entry=100, slippage=0.1%, fee=0.1%):
|
|
3414
|
-
* - Threshold: (0.1 + 0.1) * 2 = 0.4%
|
|
3415
|
-
* - Breakeven available when price <= 99.6 (entry - 0.4%)
|
|
3416
|
-
* - Moves SL from original (e.g. 105) to 100 (breakeven)
|
|
3417
|
-
* - Returns true on first successful move, false on subsequent calls
|
|
3418
|
-
*
|
|
3419
|
-
* Validations:
|
|
3420
|
-
* - Throws if no pending signal exists
|
|
3421
|
-
* - Throws if currentPrice is not a positive finite number
|
|
3242
|
+
* Fetch historical candles backwards from execution context time.
|
|
3422
3243
|
*
|
|
3423
|
-
*
|
|
3244
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3245
|
+
* @param interval - Candle time interval (e.g., "1m", "1h")
|
|
3246
|
+
* @param limit - Maximum number of candles to fetch
|
|
3247
|
+
* @returns Promise resolving to array of candle data
|
|
3248
|
+
*/
|
|
3249
|
+
getCandles: (symbol: string, interval: CandleInterval, limit: number) => Promise<ICandleData[]>;
|
|
3250
|
+
/**
|
|
3251
|
+
* Fetch future candles forward from execution context time (for backtest).
|
|
3424
3252
|
*
|
|
3425
3253
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3426
|
-
* @param
|
|
3427
|
-
* @param
|
|
3428
|
-
* @returns Promise
|
|
3254
|
+
* @param interval - Candle time interval (e.g., "1m", "1h")
|
|
3255
|
+
* @param limit - Maximum number of candles to fetch
|
|
3256
|
+
* @returns Promise resolving to array of candle data
|
|
3257
|
+
*/
|
|
3258
|
+
getNextCandles: (symbol: string, interval: CandleInterval, limit: number) => Promise<ICandleData[]>;
|
|
3259
|
+
/**
|
|
3260
|
+
* Format quantity for exchange precision.
|
|
3429
3261
|
*
|
|
3430
|
-
* @
|
|
3431
|
-
*
|
|
3432
|
-
*
|
|
3433
|
-
* onPartialProfit: async (symbol, signal, currentPrice, percentTp, backtest) => {
|
|
3434
|
-
* // Try to move SL to breakeven when threshold reached
|
|
3435
|
-
* const movedToBreakeven = await strategy.breakeven(symbol, currentPrice, backtest);
|
|
3436
|
-
* if (movedToBreakeven) {
|
|
3437
|
-
* console.log(`Position moved to breakeven at ${currentPrice}`);
|
|
3438
|
-
* }
|
|
3439
|
-
* }
|
|
3440
|
-
* }
|
|
3441
|
-
* ```
|
|
3262
|
+
* @param symbol - Trading pair symbol
|
|
3263
|
+
* @param quantity - Raw quantity value
|
|
3264
|
+
* @returns Promise resolving to formatted quantity string
|
|
3442
3265
|
*/
|
|
3443
|
-
|
|
3266
|
+
formatQuantity: (symbol: string, quantity: number) => Promise<string>;
|
|
3444
3267
|
/**
|
|
3445
|
-
*
|
|
3268
|
+
* Format price for exchange precision.
|
|
3446
3269
|
*
|
|
3447
|
-
*
|
|
3448
|
-
*
|
|
3449
|
-
*
|
|
3270
|
+
* @param symbol - Trading pair symbol
|
|
3271
|
+
* @param price - Raw price value
|
|
3272
|
+
* @returns Promise resolving to formatted price string
|
|
3273
|
+
*/
|
|
3274
|
+
formatPrice: (symbol: string, price: number) => Promise<string>;
|
|
3275
|
+
/**
|
|
3276
|
+
* Calculate VWAP from last 5 1-minute candles.
|
|
3450
3277
|
*
|
|
3451
|
-
*
|
|
3452
|
-
*
|
|
3453
|
-
* - SHORT: currentPrice <= last entry price (must average down, not up or equal)
|
|
3278
|
+
* Formula: VWAP = Σ(Typical Price × Volume) / Σ(Volume)
|
|
3279
|
+
* where Typical Price = (High + Low + Close) / 3
|
|
3454
3280
|
*
|
|
3455
|
-
*
|
|
3456
|
-
*
|
|
3457
|
-
|
|
3281
|
+
* @param symbol - Trading pair symbol
|
|
3282
|
+
* @returns Promise resolving to volume-weighted average price
|
|
3283
|
+
*/
|
|
3284
|
+
getAveragePrice: (symbol: string) => Promise<number>;
|
|
3285
|
+
/**
|
|
3286
|
+
* Fetch order book for a trading pair.
|
|
3458
3287
|
*
|
|
3459
3288
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3460
|
-
* @param
|
|
3461
|
-
* @
|
|
3462
|
-
* @returns Promise<boolean> - true if entry added, false if rejected by direction check
|
|
3289
|
+
* @param depth - Maximum depth levels (default: CC_ORDER_BOOK_MAX_DEPTH_LEVELS)
|
|
3290
|
+
* @returns Promise resolving to order book data
|
|
3463
3291
|
*/
|
|
3464
|
-
|
|
3292
|
+
getOrderBook: (symbol: string, depth?: number) => Promise<IOrderBookData>;
|
|
3465
3293
|
/**
|
|
3466
|
-
*
|
|
3294
|
+
* Fetch aggregated trades for a trading pair.
|
|
3467
3295
|
*
|
|
3468
|
-
*
|
|
3469
|
-
*
|
|
3296
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3297
|
+
* @param limit - Optional maximum number of aggregated trades to fetch. If empty returns one hour of data.
|
|
3298
|
+
* @returns Promise resolving to array of aggregated trade data
|
|
3299
|
+
*/
|
|
3300
|
+
getAggregatedTrades: (symbol: string, limit?: number) => Promise<IAggregatedTradeData[]>;
|
|
3301
|
+
/**
|
|
3302
|
+
* Fetch raw candles with flexible date/limit parameters.
|
|
3470
3303
|
*
|
|
3471
|
-
*
|
|
3304
|
+
* All modes respect execution context and prevent look-ahead bias.
|
|
3305
|
+
*
|
|
3306
|
+
* Parameter combinations:
|
|
3307
|
+
* 1. sDate + eDate + limit: fetches with explicit parameters, validates eDate <= when
|
|
3308
|
+
* 2. sDate + eDate: calculates limit from date range, validates eDate <= when
|
|
3309
|
+
* 3. eDate + limit: calculates sDate backward, validates eDate <= when
|
|
3310
|
+
* 4. sDate + limit: fetches forward, validates calculated endTimestamp <= when
|
|
3311
|
+
* 5. Only limit: uses execution.context.when as reference (backward)
|
|
3312
|
+
*
|
|
3313
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3314
|
+
* @param interval - Candle interval (e.g., "1m", "1h")
|
|
3315
|
+
* @param limit - Optional number of candles to fetch
|
|
3316
|
+
* @param sDate - Optional start date in milliseconds
|
|
3317
|
+
* @param eDate - Optional end date in milliseconds
|
|
3318
|
+
* @returns Promise resolving to array of candles
|
|
3472
3319
|
*/
|
|
3473
|
-
|
|
3320
|
+
getRawCandles: (symbol: string, interval: CandleInterval, limit?: number, sDate?: number, eDate?: number) => Promise<ICandleData[]>;
|
|
3474
3321
|
}
|
|
3475
3322
|
/**
|
|
3476
|
-
* Unique
|
|
3323
|
+
* Unique exchange identifier.
|
|
3477
3324
|
*/
|
|
3478
|
-
type
|
|
3325
|
+
type ExchangeName = string;
|
|
3326
|
+
|
|
3327
|
+
/**
|
|
3328
|
+
* Parameters for pre-caching candles into persist storage.
|
|
3329
|
+
* Used to download historical candle data before running a backtest.
|
|
3330
|
+
*/
|
|
3331
|
+
interface ICacheCandlesParams {
|
|
3332
|
+
/** Trading pair symbol (e.g., "BTCUSDT") */
|
|
3333
|
+
symbol: string;
|
|
3334
|
+
/** Name of the registered exchange schema */
|
|
3335
|
+
exchangeName: ExchangeName;
|
|
3336
|
+
/** Candle time interval (e.g., "1m", "4h") */
|
|
3337
|
+
interval: CandleInterval;
|
|
3338
|
+
/** Start date of the caching range (inclusive) */
|
|
3339
|
+
from: Date;
|
|
3340
|
+
/** End date of the caching range (inclusive) */
|
|
3341
|
+
to: Date;
|
|
3342
|
+
}
|
|
3343
|
+
/**
|
|
3344
|
+
* Parameters for validating cached candle timestamps.
|
|
3345
|
+
* Reads JSON files directly from persist storage directory.
|
|
3346
|
+
*/
|
|
3347
|
+
interface ICheckCandlesParams {
|
|
3348
|
+
/** Trading pair symbol (e.g., "BTCUSDT") */
|
|
3349
|
+
symbol: string;
|
|
3350
|
+
/** Name of the registered exchange schema */
|
|
3351
|
+
exchangeName: ExchangeName;
|
|
3352
|
+
/** Candle time interval (e.g., "1m", "4h") */
|
|
3353
|
+
interval: CandleInterval;
|
|
3354
|
+
/** Start date of the validation range (inclusive) */
|
|
3355
|
+
from: Date;
|
|
3356
|
+
/** End date of the validation range (inclusive) */
|
|
3357
|
+
to: Date;
|
|
3358
|
+
/** Base directory of candle persist storage (default: "./dump/data/candle") */
|
|
3359
|
+
baseDir?: string;
|
|
3360
|
+
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Checks cached candle timestamps for correct interval alignment.
|
|
3363
|
+
* Reads JSON files directly from persist storage without using abstractions.
|
|
3364
|
+
*
|
|
3365
|
+
* @param params - Validation parameters
|
|
3366
|
+
*/
|
|
3367
|
+
declare function checkCandles(params: ICheckCandlesParams): Promise<void>;
|
|
3368
|
+
/**
|
|
3369
|
+
* Pre-caches candles for a date range into persist storage.
|
|
3370
|
+
* Downloads all candles matching the interval from `from` to `to`.
|
|
3371
|
+
*
|
|
3372
|
+
* @param params - Cache parameters
|
|
3373
|
+
*/
|
|
3374
|
+
declare function warmCandles(params: ICacheCandlesParams): Promise<void>;
|
|
3375
|
+
|
|
3376
|
+
/**
|
|
3377
|
+
* Type alias for enum objects with string key-value pairs
|
|
3378
|
+
*/
|
|
3379
|
+
type Enum = Record<string, string>;
|
|
3380
|
+
/**
|
|
3381
|
+
* Type alias for ValidateArgs with any enum type
|
|
3382
|
+
*/
|
|
3383
|
+
type Args = ValidateArgs<any>;
|
|
3384
|
+
/**
|
|
3385
|
+
* Interface defining validation arguments for all entity types.
|
|
3386
|
+
*
|
|
3387
|
+
* Each property accepts an enum object where values will be validated
|
|
3388
|
+
* against registered entities in their respective validation services.
|
|
3389
|
+
*
|
|
3390
|
+
* @template T - Enum type extending Record<string, string>
|
|
3391
|
+
*/
|
|
3392
|
+
interface ValidateArgs<T = Enum> {
|
|
3393
|
+
/**
|
|
3394
|
+
* Exchange name enum to validate
|
|
3395
|
+
* @example { BINANCE: "binance", BYBIT: "bybit" }
|
|
3396
|
+
*/
|
|
3397
|
+
ExchangeName?: T;
|
|
3398
|
+
/**
|
|
3399
|
+
* Frame (timeframe) name enum to validate
|
|
3400
|
+
* @example { Q1_2024: "2024-Q1", Q2_2024: "2024-Q2" }
|
|
3401
|
+
*/
|
|
3402
|
+
FrameName?: T;
|
|
3403
|
+
/**
|
|
3404
|
+
* Strategy name enum to validate
|
|
3405
|
+
* @example { MOMENTUM_BTC: "momentum-btc" }
|
|
3406
|
+
*/
|
|
3407
|
+
StrategyName?: T;
|
|
3408
|
+
/**
|
|
3409
|
+
* Risk profile name enum to validate
|
|
3410
|
+
* @example { CONSERVATIVE: "conservative", AGGRESSIVE: "aggressive" }
|
|
3411
|
+
*/
|
|
3412
|
+
RiskName?: T;
|
|
3413
|
+
/**
|
|
3414
|
+
* Action handler name enum to validate
|
|
3415
|
+
* @example { TELEGRAM_NOTIFIER: "telegram-notifier" }
|
|
3416
|
+
*/
|
|
3417
|
+
ActionName?: T;
|
|
3418
|
+
/**
|
|
3419
|
+
* Sizing strategy name enum to validate
|
|
3420
|
+
* @example { FIXED_1000: "fixed-1000" }
|
|
3421
|
+
*/
|
|
3422
|
+
SizingName?: T;
|
|
3423
|
+
/**
|
|
3424
|
+
* Walker (parameter sweep) name enum to validate
|
|
3425
|
+
* @example { RSI_SWEEP: "rsi-sweep" }
|
|
3426
|
+
*/
|
|
3427
|
+
WalkerName?: T;
|
|
3428
|
+
}
|
|
3429
|
+
/**
|
|
3430
|
+
* Validates the existence of all provided entity names across validation services.
|
|
3431
|
+
*
|
|
3432
|
+
* This function accepts enum objects for various entity types (exchanges, frames,
|
|
3433
|
+
* strategies, risks, sizings, walkers) and validates that each entity
|
|
3434
|
+
* name exists in its respective registry. Validation results are memoized for performance.
|
|
3435
|
+
*
|
|
3436
|
+
* If no arguments are provided (or specific entity types are omitted), the function
|
|
3437
|
+
* automatically fetches and validates ALL registered entities from their respective
|
|
3438
|
+
* validation services. This is useful for comprehensive validation of the entire setup.
|
|
3439
|
+
*
|
|
3440
|
+
* Use this before running backtests or optimizations to ensure all referenced
|
|
3441
|
+
* entities are properly registered and configured.
|
|
3442
|
+
*
|
|
3443
|
+
* @public
|
|
3444
|
+
* @param args - Partial validation arguments containing entity name enums to validate.
|
|
3445
|
+
* If empty or omitted, validates all registered entities.
|
|
3446
|
+
* @throws {Error} If any entity name is not found in its validation service
|
|
3447
|
+
*
|
|
3448
|
+
* @example
|
|
3449
|
+
* ```typescript
|
|
3450
|
+
* // Validate ALL registered entities (exchanges, frames, strategies, etc.)
|
|
3451
|
+
* await validate({});
|
|
3452
|
+
* ```
|
|
3453
|
+
*
|
|
3454
|
+
* @example
|
|
3455
|
+
* ```typescript
|
|
3456
|
+
* // Define your entity name enums
|
|
3457
|
+
* enum ExchangeName {
|
|
3458
|
+
* BINANCE = "binance",
|
|
3459
|
+
* BYBIT = "bybit"
|
|
3460
|
+
* }
|
|
3461
|
+
*
|
|
3462
|
+
* enum StrategyName {
|
|
3463
|
+
* MOMENTUM_BTC = "momentum-btc"
|
|
3464
|
+
* }
|
|
3465
|
+
*
|
|
3466
|
+
* // Validate specific entities before running backtest
|
|
3467
|
+
* await validate({
|
|
3468
|
+
* ExchangeName,
|
|
3469
|
+
* StrategyName,
|
|
3470
|
+
* });
|
|
3471
|
+
* ```
|
|
3472
|
+
*
|
|
3473
|
+
* @example
|
|
3474
|
+
* ```typescript
|
|
3475
|
+
* // Validate specific entity types
|
|
3476
|
+
* await validate({
|
|
3477
|
+
* RiskName: { CONSERVATIVE: "conservative" },
|
|
3478
|
+
* SizingName: { FIXED_1000: "fixed-1000" },
|
|
3479
|
+
* });
|
|
3480
|
+
* ```
|
|
3481
|
+
*/
|
|
3482
|
+
declare function validate(args?: Partial<Args>): Promise<void>;
|
|
3479
3483
|
|
|
3480
3484
|
/**
|
|
3481
3485
|
* Statistical data calculated from backtest results.
|
|
@@ -9995,6 +9999,15 @@ declare class LogAdapter implements ILog {
|
|
|
9995
9999
|
* All future log writes will be no-ops.
|
|
9996
10000
|
*/
|
|
9997
10001
|
useDummy: () => void;
|
|
10002
|
+
/**
|
|
10003
|
+
* Switches to JSONL file log adapter.
|
|
10004
|
+
* Log entries will be appended to {dirName}/{fileName}.jsonl.
|
|
10005
|
+
* Reads are performed by parsing all lines from the file.
|
|
10006
|
+
*
|
|
10007
|
+
* @param fileName - Base file name without extension (default: "log")
|
|
10008
|
+
* @param dirName - Directory for the JSONL file (default: ./dump/log)
|
|
10009
|
+
*/
|
|
10010
|
+
useJsonl: (fileName?: string, dirName?: string) => void;
|
|
9998
10011
|
}
|
|
9999
10012
|
/**
|
|
10000
10013
|
* Global singleton instance of LogAdapter.
|