tradelab 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/data.cjs CHANGED
@@ -215,56 +215,87 @@ function buildMetrics({
215
215
  estBarMs,
216
216
  eqSeries
217
217
  }) {
218
- const completedTrades = closed.filter((trade) => trade.exit.reason !== "SCALE");
219
- const winningTrades = completedTrades.filter((trade) => trade.exit.pnl > 0);
220
- const losingTrades = completedTrades.filter((trade) => trade.exit.pnl < 0);
221
- const tradeRs = completedTrades.map(tradeRMultiple);
222
- const totalR = sum(tradeRs);
223
- const avgR = mean(tradeRs);
224
- const labels = completedTrades.map(
225
- (trade) => trade.exit.pnl > 0 ? "win" : trade.exit.pnl < 0 ? "loss" : "flat"
226
- );
227
- const { maxWin, maxLoss } = streaks(labels);
228
- const tradePnls = completedTrades.map((trade) => trade.exit.pnl);
229
- const expectancy = mean(tradePnls);
230
- const tradeReturns = completedTrades.map(
231
- (trade) => trade.exit.pnl / Math.max(1e-12, equityStart)
232
- );
233
- const tradeReturnStd = stddev(tradeReturns);
234
- const sharpePerTrade = tradeReturnStd === 0 ? tradeReturns.length ? Infinity : 0 : mean(tradeReturns) / tradeReturnStd;
235
- const sortinoPerTrade = sortino(tradeReturns);
236
- const grossProfitPositions = sum(winningTrades.map((trade) => trade.exit.pnl));
237
- const grossLossPositions = Math.abs(
238
- sum(losingTrades.map((trade) => trade.exit.pnl))
239
- );
240
- const profitFactorPositions = grossLossPositions === 0 ? grossProfitPositions > 0 ? Infinity : 0 : grossProfitPositions / grossLossPositions;
241
218
  const legs = [...closed].sort((left, right) => left.exit.time - right.exit.time);
242
- const winningLegs = legs.filter((trade) => trade.exit.pnl > 0);
243
- const losingLegs = legs.filter((trade) => trade.exit.pnl < 0);
244
- const grossProfitLegs = sum(winningLegs.map((trade) => trade.exit.pnl));
245
- const grossLossLegs = Math.abs(sum(losingLegs.map((trade) => trade.exit.pnl)));
246
- const profitFactorLegs = grossLossLegs === 0 ? grossProfitLegs > 0 ? Infinity : 0 : grossProfitLegs / grossLossLegs;
219
+ const completedTrades = [];
220
+ const tradeRs = [];
221
+ const tradePnls = [];
222
+ const tradeReturns = [];
223
+ const holdDurationsMinutes = [];
224
+ const labels = [];
225
+ const longRs = [];
226
+ const shortRs = [];
227
+ let totalR = 0;
228
+ let realizedPnL = 0;
229
+ let winningTradeCount = 0;
230
+ let grossProfitPositions = 0;
231
+ let grossLossPositions = 0;
232
+ let grossProfitLegs = 0;
233
+ let grossLossLegs = 0;
234
+ let winningLegCount = 0;
235
+ let openBars = 0;
236
+ let longTradesCount = 0;
237
+ let longTradeWins = 0;
238
+ let longPnLSum = 0;
239
+ let shortTradesCount = 0;
240
+ let shortTradeWins = 0;
241
+ let shortPnLSum = 0;
247
242
  let peakEquity = equityStart;
248
243
  let currentEquity = equityStart;
249
244
  let maxDrawdown = 0;
250
- for (const leg of legs) {
251
- currentEquity += leg.exit.pnl;
245
+ for (const trade of legs) {
246
+ const pnl = trade.exit.pnl;
247
+ realizedPnL += pnl;
248
+ if (pnl > 0) {
249
+ grossProfitLegs += pnl;
250
+ winningLegCount += 1;
251
+ } else if (pnl < 0) {
252
+ grossLossLegs += Math.abs(pnl);
253
+ }
254
+ currentEquity += pnl;
252
255
  if (currentEquity > peakEquity) peakEquity = currentEquity;
253
256
  const drawdown = (peakEquity - currentEquity) / Math.max(1e-12, peakEquity);
254
257
  if (drawdown > maxDrawdown) maxDrawdown = drawdown;
258
+ if (trade.exit.reason === "SCALE") continue;
259
+ completedTrades.push(trade);
260
+ tradePnls.push(pnl);
261
+ tradeReturns.push(pnl / Math.max(1e-12, equityStart));
262
+ const tradeR = tradeRMultiple(trade);
263
+ tradeRs.push(tradeR);
264
+ totalR += tradeR;
265
+ labels.push(pnl > 0 ? "win" : pnl < 0 ? "loss" : "flat");
266
+ const holdMinutes = (trade.exit.time - trade.openTime) / (1e3 * 60);
267
+ holdDurationsMinutes.push(holdMinutes);
268
+ openBars += Math.max(1, Math.round((trade.exit.time - trade.openTime) / estBarMs));
269
+ if (pnl > 0) {
270
+ winningTradeCount += 1;
271
+ grossProfitPositions += pnl;
272
+ } else if (pnl < 0) {
273
+ grossLossPositions += Math.abs(pnl);
274
+ }
275
+ if (trade.side === "long") {
276
+ longTradesCount += 1;
277
+ longPnLSum += pnl;
278
+ longRs.push(tradeR);
279
+ if (pnl > 0) longTradeWins += 1;
280
+ } else if (trade.side === "short") {
281
+ shortTradesCount += 1;
282
+ shortPnLSum += pnl;
283
+ shortRs.push(tradeR);
284
+ if (pnl > 0) shortTradeWins += 1;
285
+ }
255
286
  }
256
- const realizedPnL = sum(closed.map((trade) => trade.exit.pnl));
287
+ const avgR = mean(tradeRs);
288
+ const { maxWin, maxLoss } = streaks(labels);
289
+ const expectancy = mean(tradePnls);
290
+ const tradeReturnStd = stddev(tradeReturns);
291
+ const sharpePerTrade = tradeReturnStd === 0 ? tradeReturns.length ? Infinity : 0 : mean(tradeReturns) / tradeReturnStd;
292
+ const sortinoPerTrade = sortino(tradeReturns);
293
+ const profitFactorPositions = grossLossPositions === 0 ? grossProfitPositions > 0 ? Infinity : 0 : grossProfitPositions / grossLossPositions;
294
+ const profitFactorLegs = grossLossLegs === 0 ? grossProfitLegs > 0 ? Infinity : 0 : grossProfitLegs / grossLossLegs;
257
295
  const returnPct = (equityFinal - equityStart) / Math.max(1e-12, equityStart);
258
296
  const calmar = maxDrawdown === 0 ? returnPct > 0 ? Infinity : 0 : returnPct / maxDrawdown;
259
297
  const totalBars = Math.max(1, candles.length);
260
- const openBars = completedTrades.reduce((total, trade) => {
261
- const barsHeld = Math.max(1, Math.round((trade.exit.time - trade.openTime) / estBarMs));
262
- return total + barsHeld;
263
- }, 0);
264
298
  const exposurePct = openBars / totalBars;
265
- const holdDurationsMinutes = completedTrades.map(
266
- (trade) => (trade.exit.time - trade.openTime) / (1e3 * 60)
267
- );
268
299
  const avgHoldMin = mean(holdDurationsMinutes);
269
300
  const equitySeries = eqSeries && eqSeries.length ? eqSeries : buildEquitySeriesFromLegs({ legs, equityStart });
270
301
  const dailyReturnsSeries = dailyReturns(equitySeries);
@@ -272,12 +303,6 @@ function buildMetrics({
272
303
  const sharpeDaily = dailyStd === 0 ? dailyReturnsSeries.length ? Infinity : 0 : mean(dailyReturnsSeries) / dailyStd;
273
304
  const sortinoDaily = sortino(dailyReturnsSeries);
274
305
  const dailyWinRate = dailyReturnsSeries.length ? dailyReturnsSeries.filter((value) => value > 0).length / dailyReturnsSeries.length : 0;
275
- const longTrades = completedTrades.filter((trade) => trade.side === "long");
276
- const shortTrades = completedTrades.filter((trade) => trade.side === "short");
277
- const longRs = longTrades.map(tradeRMultiple);
278
- const shortRs = shortTrades.map(tradeRMultiple);
279
- const longPnls = longTrades.map((trade) => trade.exit.pnl);
280
- const shortPnls = shortTrades.map((trade) => trade.exit.pnl);
281
306
  const rDistribution = {
282
307
  p10: percentile(tradeRs, 0.1),
283
308
  p25: percentile(tradeRs, 0.25),
@@ -294,21 +319,21 @@ function buildMetrics({
294
319
  };
295
320
  const sideBreakdown = {
296
321
  long: {
297
- trades: longTrades.length,
298
- winRate: longTrades.length ? longTrades.filter((trade) => trade.exit.pnl > 0).length / longTrades.length : 0,
299
- avgPnL: mean(longPnls),
322
+ trades: longTradesCount,
323
+ winRate: longTradesCount ? longTradeWins / longTradesCount : 0,
324
+ avgPnL: longTradesCount ? longPnLSum / longTradesCount : 0,
300
325
  avgR: mean(longRs)
301
326
  },
302
327
  short: {
303
- trades: shortTrades.length,
304
- winRate: shortTrades.length ? shortTrades.filter((trade) => trade.exit.pnl > 0).length / shortTrades.length : 0,
305
- avgPnL: mean(shortPnls),
328
+ trades: shortTradesCount,
329
+ winRate: shortTradesCount ? shortTradeWins / shortTradesCount : 0,
330
+ avgPnL: shortTradesCount ? shortPnLSum / shortTradesCount : 0,
306
331
  avgR: mean(shortRs)
307
332
  }
308
333
  };
309
334
  return {
310
335
  trades: completedTrades.length,
311
- winRate: completedTrades.length ? winningTrades.length / completedTrades.length : 0,
336
+ winRate: completedTrades.length ? winningTradeCount / completedTrades.length : 0,
312
337
  profitFactor: profitFactorPositions,
313
338
  expectancy,
314
339
  totalR,
@@ -330,8 +355,8 @@ function buildMetrics({
330
355
  startEquity: equityStart,
331
356
  profitFactor_pos: profitFactorPositions,
332
357
  profitFactor_leg: profitFactorLegs,
333
- winRate_pos: completedTrades.length ? winningTrades.length / completedTrades.length : 0,
334
- winRate_leg: legs.length ? winningLegs.length / legs.length : 0,
358
+ winRate_pos: completedTrades.length ? winningTradeCount / completedTrades.length : 0,
359
+ winRate_leg: legs.length ? winningLegCount / legs.length : 0,
335
360
  sharpeDaily,
336
361
  sortinoDaily,
337
362
  sideBreakdown,