@tradecanvas/analytics 0.8.2 → 0.9.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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- class A {
1
+ class $ {
2
2
  constructor(t) {
3
3
  this.perTrade = t;
4
4
  }
@@ -6,50 +6,50 @@ class A {
6
6
  return this.perTrade;
7
7
  }
8
8
  }
9
- class Y {
9
+ class G {
10
10
  /** rate = 0.001 → 10 bps per trade notional. */
11
11
  constructor(t) {
12
12
  this.rate = t;
13
13
  }
14
- calculate(t, i) {
15
- return Math.abs(t) * i * this.rate;
14
+ calculate(t, e) {
15
+ return Math.abs(t) * e * this.rate;
16
16
  }
17
17
  }
18
- class V {
18
+ class Q {
19
19
  /** Minimum total commission per trade (optional). */
20
- constructor(t, i = 0) {
21
- this.perShare = t, this.minimum = i;
20
+ constructor(t, e = 0) {
21
+ this.perShare = t, this.minimum = e;
22
22
  }
23
23
  calculate(t) {
24
24
  return Math.max(this.minimum, Math.abs(t) * this.perShare);
25
25
  }
26
26
  }
27
- const F = {
27
+ const A = {
28
28
  calculate: () => 0
29
- }, E = {
30
- apply: (s) => s
29
+ }, L = {
30
+ apply: (i) => i
31
31
  };
32
- class _ {
32
+ class U {
33
33
  /** rate = 0.0005 → 5bps adverse */
34
34
  constructor(t) {
35
35
  this.rate = t;
36
36
  }
37
- apply(t, i) {
38
- const e = i === "long" ? 1 + this.rate : 1 - this.rate;
39
- return t * e;
37
+ apply(t, e) {
38
+ const n = e === "long" ? 1 + this.rate : 1 - this.rate;
39
+ return t * n;
40
40
  }
41
41
  }
42
- class B {
42
+ class Z {
43
43
  /** factor = 0.1 → 10% of the bar's range pushes against the order */
44
44
  constructor(t) {
45
45
  this.factor = t;
46
46
  }
47
- apply(t, i, e) {
48
- const o = (e.high - e.low) * this.factor;
49
- return i === "long" ? t + o : t - o;
47
+ apply(t, e, n) {
48
+ const o = (n.high - n.low) * this.factor;
49
+ return e === "long" ? t + o : t - o;
50
50
  }
51
51
  }
52
- class k {
52
+ class I {
53
53
  cash;
54
54
  initialCash;
55
55
  position = null;
@@ -84,22 +84,22 @@ class k {
84
84
  }
85
85
  unrealizedPnl(t) {
86
86
  if (!this.position) return 0;
87
- const i = this.position.side === "long" ? 1 : -1;
88
- return (t - this.position.averagePrice) * this.position.quantity * i;
87
+ const e = this.position.side === "long" ? 1 : -1;
88
+ return (t - this.position.averagePrice) * this.position.quantity * e;
89
89
  }
90
90
  equity(t) {
91
91
  return this.cash + this.positionValue(t);
92
92
  }
93
93
  positionValue(t) {
94
94
  if (!this.position) return 0;
95
- const i = this.position.side === "long" ? 1 : -1;
96
- return this.position.quantity * t * i;
95
+ const e = this.position.side === "long" ? 1 : -1;
96
+ return this.position.quantity * t * e;
97
97
  }
98
98
  /** Apply a fill: cash flow + position update + realized PnL. */
99
99
  applyFill(t) {
100
100
  this.fills.push(t);
101
- const i = t.side === "long" ? t.quantity : -t.quantity;
102
- if (this.cash -= i * t.price, this.cash -= t.commission, !this.position) {
101
+ const e = t.side === "long" ? t.quantity : -t.quantity;
102
+ if (this.cash -= e * t.price, this.cash -= t.commission, !this.position) {
103
103
  this.position = {
104
104
  side: t.side,
105
105
  quantity: t.quantity,
@@ -110,45 +110,45 @@ class k {
110
110
  return;
111
111
  }
112
112
  if (this.position.side === t.side) {
113
- const r = this.position.quantity + t.quantity;
113
+ const a = this.position.quantity + t.quantity;
114
114
  this.position = {
115
115
  ...this.position,
116
- quantity: r,
117
- averagePrice: (this.position.averagePrice * this.position.quantity + t.price * t.quantity) / r
116
+ quantity: a,
117
+ averagePrice: (this.position.averagePrice * this.position.quantity + t.price * t.quantity) / a
118
118
  };
119
119
  return;
120
120
  }
121
- const e = Math.min(this.position.quantity, t.quantity), n = this.position.side === "long" ? 1 : -1, o = (t.price - this.position.averagePrice) * e * n;
121
+ const n = Math.min(this.position.quantity, t.quantity), s = this.position.side === "long" ? 1 : -1, o = (t.price - this.position.averagePrice) * n * s;
122
122
  this.realizedPnl += o, this.trades.push({
123
123
  entryTime: this.position.openedAt,
124
124
  exitTime: t.time,
125
125
  side: this.position.side,
126
- quantity: e,
126
+ quantity: n,
127
127
  entryPrice: this.position.averagePrice,
128
128
  exitPrice: t.price,
129
129
  pnl: o,
130
- pnlPct: (t.price / this.position.averagePrice - 1) * n,
130
+ pnlPct: (t.price / this.position.averagePrice - 1) * s,
131
131
  commission: t.commission,
132
132
  tag: t.tag ?? this.position.tag
133
133
  });
134
- const a = this.position.quantity - t.quantity;
135
- a > 0 ? this.position = { ...this.position, quantity: a } : a < 0 ? this.position = {
134
+ const r = this.position.quantity - t.quantity;
135
+ r > 0 ? this.position = { ...this.position, quantity: r } : r < 0 ? this.position = {
136
136
  side: t.side,
137
- quantity: -a,
137
+ quantity: -r,
138
138
  averagePrice: t.price,
139
139
  openedAt: t.time,
140
140
  tag: t.tag
141
141
  } : this.position = null;
142
142
  }
143
143
  /** Snapshot equity at the current bar close. */
144
- mark(t, i) {
145
- const e = this.positionValue(i), n = this.unrealizedPnl(i);
144
+ mark(t, e) {
145
+ const n = this.positionValue(e), s = this.unrealizedPnl(e);
146
146
  this.equityCurve.push({
147
147
  time: t,
148
- equity: this.cash + e,
148
+ equity: this.cash + n,
149
149
  cash: this.cash,
150
- positionValue: e,
151
- unrealizedPnl: n,
150
+ positionValue: n,
151
+ unrealizedPnl: s,
152
152
  realizedPnl: this.realizedPnl
153
153
  });
154
154
  }
@@ -156,81 +156,81 @@ class k {
156
156
  return t === "long" ? "short" : "long";
157
157
  }
158
158
  }
159
- const P = 365 * 24 * 60 * 60 * 1e3;
160
- function D(s, t, i, e = {}) {
159
+ const E = 365 * 24 * 60 * 60 * 1e3;
160
+ function T(i, t, e, n = {}) {
161
161
  if (t.length < 2)
162
- return v(s, t, i);
163
- const n = t[t.length - 1].equity, o = n - s, a = o / s, r = e.periodsPerYear ?? L(t), l = (e.riskFreeRate ?? 0) / r, c = I(t), u = q(c), d = z(c, u), g = C(c, l), x = d === 0 ? 0 : (u - l) / d * Math.sqrt(r), M = g === 0 ? 0 : (u - l) / g * Math.sqrt(r), m = t[t.length - 1].time - t[0].time, f = m > 0 ? m / P : 0, y = f > 0 ? Math.pow(n / s, 1 / f) - 1 : 0, { maxDrawdown: O, maxDrawdownPct: p } = T(t), R = p > 0 ? y / p : 0, S = w(i);
162
+ return B(i, t, e);
163
+ const s = t[t.length - 1].equity, o = s - i, r = o / i, a = n.periodsPerYear ?? _(t), u = (n.riskFreeRate ?? 0) / a, h = Y(t), d = z(h), m = N(h, d), q = C(h, u), f = m === 0 ? 0 : (d - u) / m * Math.sqrt(a), O = q === 0 ? 0 : (d - u) / q * Math.sqrt(a), M = t[t.length - 1].time - t[0].time, l = M > 0 ? M / E : 0, p = l > 0 ? Math.pow(s / i, 1 / l) - 1 : 0, { maxDrawdown: y, maxDrawdownPct: g } = V(t), v = g > 0 ? p / g : 0, P = b(e);
164
164
  return {
165
165
  totalReturn: o,
166
- totalReturnPct: a,
167
- cagr: y,
168
- sharpe: x,
169
- sortino: M,
170
- calmar: R,
171
- maxDrawdown: O,
172
- maxDrawdownPct: p,
173
- ...S
166
+ totalReturnPct: r,
167
+ cagr: p,
168
+ sharpe: f,
169
+ sortino: O,
170
+ calmar: v,
171
+ maxDrawdown: y,
172
+ maxDrawdownPct: g,
173
+ ...P
174
174
  };
175
175
  }
176
- function v(s, t, i) {
177
- const e = t.length > 0 ? t[t.length - 1].equity : s;
176
+ function B(i, t, e) {
177
+ const n = t.length > 0 ? t[t.length - 1].equity : i;
178
178
  return {
179
- totalReturn: e - s,
180
- totalReturnPct: (e - s) / s,
179
+ totalReturn: n - i,
180
+ totalReturnPct: (n - i) / i,
181
181
  cagr: 0,
182
182
  sharpe: 0,
183
183
  sortino: 0,
184
184
  calmar: 0,
185
185
  maxDrawdown: 0,
186
186
  maxDrawdownPct: 0,
187
- ...w(i)
187
+ ...b(e)
188
188
  };
189
189
  }
190
- function I(s) {
190
+ function Y(i) {
191
191
  const t = [];
192
- for (let i = 1; i < s.length; i++) {
193
- const e = s[i - 1].equity;
194
- if (e <= 0) {
192
+ for (let e = 1; e < i.length; e++) {
193
+ const n = i[e - 1].equity;
194
+ if (n <= 0) {
195
195
  t.push(0);
196
196
  continue;
197
197
  }
198
- t.push(s[i].equity / e - 1);
198
+ t.push(i[e].equity / n - 1);
199
199
  }
200
200
  return t;
201
201
  }
202
- function q(s) {
203
- if (s.length === 0) return 0;
202
+ function z(i) {
203
+ if (i.length === 0) return 0;
204
204
  let t = 0;
205
- for (const i of s) t += i;
206
- return t / s.length;
205
+ for (const e of i) t += e;
206
+ return t / i.length;
207
207
  }
208
- function z(s, t) {
209
- if (s.length < 2) return 0;
210
- let i = 0;
211
- for (const e of s) i += (e - t) ** 2;
212
- return Math.sqrt(i / (s.length - 1));
208
+ function N(i, t) {
209
+ if (i.length < 2) return 0;
210
+ let e = 0;
211
+ for (const n of i) e += (n - t) ** 2;
212
+ return Math.sqrt(e / (i.length - 1));
213
213
  }
214
- function C(s, t) {
215
- if (s.length < 2) return 0;
216
- let i = 0, e = 0;
217
- for (const n of s) {
218
- const o = n - t;
219
- o < 0 && (i += o ** 2, e++);
220
- }
221
- return e === 0 ? 0 : Math.sqrt(i / e);
214
+ function C(i, t) {
215
+ if (i.length < 2) return 0;
216
+ let e = 0, n = 0;
217
+ for (const s of i) {
218
+ const o = s - t;
219
+ o < 0 && (e += o ** 2, n++);
220
+ }
221
+ return n === 0 ? 0 : Math.sqrt(e / n);
222
222
  }
223
- function T(s) {
224
- let t = s[0].equity, i = 0, e = 0;
225
- for (const n of s) {
226
- n.equity > t && (t = n.equity);
227
- const o = t - n.equity;
228
- o > i && (i = o, e = t > 0 ? o / t : 0);
229
- }
230
- return { maxDrawdown: i, maxDrawdownPct: e };
223
+ function V(i) {
224
+ let t = i[0].equity, e = 0, n = 0;
225
+ for (const s of i) {
226
+ s.equity > t && (t = s.equity);
227
+ const o = t - s.equity;
228
+ o > e && (e = o, n = t > 0 ? o / t : 0);
229
+ }
230
+ return { maxDrawdown: e, maxDrawdownPct: n };
231
231
  }
232
- function w(s) {
233
- if (s.length === 0)
232
+ function b(i) {
233
+ if (i.length === 0)
234
234
  return {
235
235
  winRate: 0,
236
236
  profitFactor: 0,
@@ -239,30 +239,30 @@ function w(s) {
239
239
  averageLoss: 0,
240
240
  trades: 0
241
241
  };
242
- let t = 0, i = 0, e = 0, n = 0;
243
- for (const c of s)
244
- c.pnl > 0 ? (t++, e += c.pnl) : c.pnl < 0 && (i++, n += -c.pnl);
245
- const o = t / s.length, a = t > 0 ? e / t : 0, r = i > 0 ? n / i : 0, h = n > 0 ? e / n : e > 0 ? 1 / 0 : 0, l = o * a - (1 - o) * r;
242
+ let t = 0, e = 0, n = 0, s = 0;
243
+ for (const h of i)
244
+ h.pnl > 0 ? (t++, n += h.pnl) : h.pnl < 0 && (e++, s += -h.pnl);
245
+ const o = t / i.length, r = t > 0 ? n / t : 0, a = e > 0 ? s / e : 0, c = s > 0 ? n / s : n > 0 ? 1 / 0 : 0, u = o * r - (1 - o) * a;
246
246
  return {
247
247
  winRate: o,
248
- profitFactor: h,
249
- expectancy: l,
250
- averageWin: a,
251
- averageLoss: r,
252
- trades: s.length
248
+ profitFactor: c,
249
+ expectancy: u,
250
+ averageWin: r,
251
+ averageLoss: a,
252
+ trades: i.length
253
253
  };
254
254
  }
255
- function L(s) {
256
- if (s.length < 2) return 252;
255
+ function _(i) {
256
+ if (i.length < 2) return 252;
257
257
  const t = [];
258
- for (let n = 1; n < s.length && n < 50; n++)
259
- t.push(s[n].time - s[n - 1].time);
260
- const i = q(t);
261
- if (i <= 0) return 252;
262
- const e = P / i;
263
- return e > 2e5 ? 365 * 24 * 60 : e > 5e4 ? 365 * 24 * 4 : e > 5e3 ? 365 * 24 : e > 200 ? 252 : e > 40 ? 52 : 12;
258
+ for (let s = 1; s < i.length && s < 50; s++)
259
+ t.push(i[s].time - i[s - 1].time);
260
+ const e = z(t);
261
+ if (e <= 0) return 252;
262
+ const n = E / e;
263
+ return n > 2e5 ? 365 * 24 * 60 : n > 5e4 ? 365 * 24 * 4 : n > 5e3 ? 365 * 24 : n > 200 ? 252 : n > 40 ? 52 : 12;
264
264
  }
265
- class W {
265
+ class x {
266
266
  commission;
267
267
  slippage;
268
268
  allowShort;
@@ -270,89 +270,89 @@ class W {
270
270
  pendingOrders = [];
271
271
  orderSeq = 0;
272
272
  constructor(t) {
273
- this.commission = t.commission ?? F, this.slippage = t.slippage ?? E, this.allowShort = t.allowShort ?? !0, this.portfolio = new k({ initialCash: t.initialCash });
273
+ this.commission = t.commission ?? A, this.slippage = t.slippage ?? L, this.allowShort = t.allowShort ?? !0, this.portfolio = new I({ initialCash: t.initialCash });
274
274
  }
275
- run(t, i) {
275
+ run(t, e) {
276
276
  if (t.length < 2)
277
277
  throw new Error("Backtester requires at least 2 bars");
278
- for (let r = 0; r < t.length; r++) {
279
- const h = t[r];
280
- if (this.fillPendingOrders(h), this.portfolio.mark(h.time, h.close), r < t.length - 1) {
281
- const l = this.makeContext(h, r, t.slice(0, r + 1));
282
- i(l);
278
+ for (let a = 0; a < t.length; a++) {
279
+ const c = t[a];
280
+ if (this.fillPendingOrders(c), this.portfolio.mark(c.time, c.close), a < t.length - 1) {
281
+ const u = this.makeContext(c, a, t.slice(0, a + 1));
282
+ e(u);
283
283
  }
284
284
  }
285
- for (const r of this.pendingOrders)
286
- r.status === "pending" && (r.status = "cancelled");
287
- const e = this.portfolio.getEquityCurve(), n = this.portfolio.getInitialCash(), o = this.portfolio.getTrades(), a = e.length > 0 ? e[e.length - 1].equity : n;
285
+ for (const a of this.pendingOrders)
286
+ a.status === "pending" && (a.status = "cancelled");
287
+ const n = this.portfolio.getEquityCurve(), s = this.portfolio.getInitialCash(), o = this.portfolio.getTrades(), r = n.length > 0 ? n[n.length - 1].equity : s;
288
288
  return {
289
289
  fills: this.portfolio.getFills(),
290
290
  trades: o,
291
- equityCurve: e,
292
- initialCash: n,
293
- finalEquity: a,
294
- metrics: D(n, e, o)
291
+ equityCurve: n,
292
+ initialCash: s,
293
+ finalEquity: r,
294
+ metrics: T(s, n, o)
295
295
  };
296
296
  }
297
297
  fillPendingOrders(t) {
298
- for (const i of this.pendingOrders) {
299
- if (i.status !== "pending") continue;
300
- const e = this.resolveFillPrice(i, t);
301
- if (e === null) {
302
- (i.timeInForce === "day" || i.timeInForce === "ioc") && (i.status = "cancelled");
298
+ for (const e of this.pendingOrders) {
299
+ if (e.status !== "pending") continue;
300
+ const n = this.resolveFillPrice(e, t);
301
+ if (n === null) {
302
+ (e.timeInForce === "day" || e.timeInForce === "ioc") && (e.status = "cancelled");
303
303
  continue;
304
304
  }
305
- const n = this.slippage.apply(e, i.side, t), o = this.commission.calculate(i.quantity, n), a = {
306
- orderId: i.id,
305
+ const s = this.slippage.apply(n, e.side, t), o = this.commission.calculate(e.quantity, s), r = {
306
+ orderId: e.id,
307
307
  time: t.time,
308
- price: n,
309
- quantity: i.quantity,
310
- side: i.side,
308
+ price: s,
309
+ quantity: e.quantity,
310
+ side: e.side,
311
311
  commission: o,
312
- slippage: Math.abs(n - e),
313
- tag: i.tag
312
+ slippage: Math.abs(s - n),
313
+ tag: e.tag
314
314
  };
315
- this.portfolio.applyFill(a), i.status = "filled";
315
+ this.portfolio.applyFill(r), e.status = "filled";
316
316
  }
317
317
  this.pendingOrders = this.pendingOrders.filter(
318
- (i) => i.status === "pending"
318
+ (e) => e.status === "pending"
319
319
  );
320
320
  }
321
- resolveFillPrice(t, i) {
321
+ resolveFillPrice(t, e) {
322
322
  switch (t.type) {
323
323
  case "market":
324
- return i.open;
324
+ return e.open;
325
325
  case "limit":
326
- return t.price === void 0 ? null : t.side === "long" && i.low <= t.price ? Math.min(t.price, i.open) : t.side === "short" && i.high >= t.price ? Math.max(t.price, i.open) : null;
326
+ return t.price === void 0 ? null : t.side === "long" && e.low <= t.price ? Math.min(t.price, e.open) : t.side === "short" && e.high >= t.price ? Math.max(t.price, e.open) : null;
327
327
  case "stop":
328
- return t.price === void 0 ? null : t.side === "long" && i.high >= t.price ? Math.max(t.price, i.open) : t.side === "short" && i.low <= t.price ? Math.min(t.price, i.open) : null;
328
+ return t.price === void 0 ? null : t.side === "long" && e.high >= t.price ? Math.max(t.price, e.open) : t.side === "short" && e.low <= t.price ? Math.min(t.price, e.open) : null;
329
329
  }
330
330
  }
331
- makeContext(t, i, e) {
332
- const n = this.portfolio;
331
+ makeContext(t, e, n) {
332
+ const s = this.portfolio;
333
333
  return {
334
334
  bar: t,
335
- index: i,
336
- history: e,
337
- position: n.getPosition(),
338
- cash: n.getCash(),
339
- equity: n.equity(t.close),
335
+ index: e,
336
+ history: n,
337
+ position: s.getPosition(),
338
+ cash: s.getCash(),
339
+ equity: s.equity(t.close),
340
340
  placeOrder: (o) => this.placeOrder(o, t.time),
341
341
  close: (o) => this.closePosition(t.time, o),
342
342
  cancel: (o) => this.cancelOrder(o)
343
343
  };
344
344
  }
345
- placeOrder(t, i) {
345
+ placeOrder(t, e) {
346
346
  if (t.quantity <= 0)
347
347
  throw new Error("order quantity must be > 0");
348
348
  if (!this.allowShort && t.side === "short") {
349
- const n = this.portfolio.getPosition();
350
- if (!(n !== null && n.side === "long" && t.quantity <= n.quantity))
349
+ const s = this.portfolio.getPosition();
350
+ if (!(s !== null && s.side === "long" && t.quantity <= s.quantity))
351
351
  throw new Error("shorting is disabled");
352
352
  }
353
- const e = t.id ?? `o-${++this.orderSeq}`;
353
+ const n = t.id ?? `o-${++this.orderSeq}`;
354
354
  return this.pendingOrders.push({
355
- id: e,
355
+ id: n,
356
356
  side: t.side,
357
357
  type: t.type,
358
358
  quantity: t.quantity,
@@ -360,33 +360,182 @@ class W {
360
360
  tag: t.tag,
361
361
  timeInForce: t.timeInForce ?? "gtc",
362
362
  status: "pending",
363
- placedAt: i
364
- }), e;
363
+ placedAt: e
364
+ }), n;
365
365
  }
366
- closePosition(t, i) {
367
- const e = this.portfolio.getPosition();
368
- if (!e) return null;
369
- const n = e.side === "long" ? "short" : "long";
366
+ closePosition(t, e) {
367
+ const n = this.portfolio.getPosition();
368
+ if (!n) return null;
369
+ const s = n.side === "long" ? "short" : "long";
370
370
  return this.placeOrder(
371
- { side: n, type: "market", quantity: e.quantity, tag: i },
371
+ { side: s, type: "market", quantity: n.quantity, tag: e },
372
372
  t
373
373
  );
374
374
  }
375
375
  cancelOrder(t) {
376
- const i = this.pendingOrders.find((e) => e.id === t);
377
- return !i || i.status !== "pending" ? !1 : (i.status = "cancelled", !0);
376
+ const e = this.pendingOrders.find((n) => n.id === t);
377
+ return !e || e.status !== "pending" ? !1 : (e.status = "cancelled", !0);
378
378
  }
379
379
  }
380
+ function J(i, t, e = {}) {
381
+ const n = e.simulations ?? 1e3, s = (e.percentiles ?? [5, 25, 50, 75, 95]).slice().sort((l, p) => l - p), o = H(e.seed ?? Date.now() ^ 23100);
382
+ if (t.length === 0 || n <= 0)
383
+ return {
384
+ simulations: n,
385
+ initialCash: i,
386
+ equityBands: [{ step: 0, p5: i, p25: i, p50: i, p75: i, p95: i }],
387
+ finalEquityPercentiles: Object.fromEntries(s.map((l) => [`p${l}`, i])),
388
+ probabilityProfitable: 0,
389
+ worstMaxDrawdownPct: 0
390
+ };
391
+ const r = t.map((l) => l.pnl), a = t.length + 1, c = r.slice(), u = new Float64Array(n), h = new Float64Array(n * a);
392
+ let d = 0, m = 0;
393
+ for (let l = 0; l < n; l++) {
394
+ j(c, o);
395
+ let p = i, y = i, g = 0;
396
+ const v = l * a;
397
+ h[v] = p;
398
+ for (let P = 0; P < c.length; P++) {
399
+ p += c[P], h[v + P + 1] = p, p > y && (y = p);
400
+ const S = y > 0 ? (y - p) / y : 0;
401
+ S > g && (g = S);
402
+ }
403
+ u[l] = p, p > i && m++, g > d && (d = g);
404
+ }
405
+ const q = [], f = new Float64Array(n);
406
+ for (let l = 0; l < a; l++) {
407
+ for (let p = 0; p < n; p++)
408
+ f[p] = h[p * a + l];
409
+ f.sort(), q.push({
410
+ step: l,
411
+ p5: w(f, 5),
412
+ p25: w(f, 25),
413
+ p50: w(f, 50),
414
+ p75: w(f, 75),
415
+ p95: w(f, 95)
416
+ });
417
+ }
418
+ const O = new Float64Array(u);
419
+ O.sort();
420
+ const M = {};
421
+ for (const l of s)
422
+ M[`p${l}`] = w(O, l);
423
+ return {
424
+ simulations: n,
425
+ initialCash: i,
426
+ equityBands: q,
427
+ finalEquityPercentiles: M,
428
+ probabilityProfitable: m / n,
429
+ worstMaxDrawdownPct: d * 100
430
+ };
431
+ }
432
+ function j(i, t) {
433
+ for (let e = i.length - 1; e > 0; e--) {
434
+ const n = Math.floor(t() * (e + 1)), s = i[e];
435
+ i[e] = i[n], i[n] = s;
436
+ }
437
+ }
438
+ function w(i, t) {
439
+ if (i.length === 0) return 0;
440
+ if (i.length === 1) return i[0];
441
+ const e = t / 100 * (i.length - 1), n = Math.floor(e), s = Math.ceil(e);
442
+ return n === s ? i[n] : i[n] * (s - e) + i[s] * (e - n);
443
+ }
444
+ function H(i) {
445
+ let t = i >>> 0;
446
+ return () => {
447
+ t = t + 1831565813 >>> 0;
448
+ let e = t;
449
+ return e = Math.imul(e ^ e >>> 15, e | 1), e ^= e + Math.imul(e ^ e >>> 7, e | 61), ((e ^ e >>> 14) >>> 0) / 4294967296;
450
+ };
451
+ }
452
+ function K(i = {}) {
453
+ const t = i.fastPeriod ?? 10, e = i.slowPeriod ?? 30, n = i.size ?? 1, s = i.tag ?? "sma-cross";
454
+ if (t >= e)
455
+ throw new Error("smaCrossStrategy: fastPeriod must be less than slowPeriod");
456
+ return (o) => {
457
+ if (o.index < e) return;
458
+ const r = o.history, a = F(r, o.index, t), c = F(r, o.index, e), u = F(r, o.index - 1, t), h = F(r, o.index - 1, e), d = u <= h && a > c, m = u >= h && a < c;
459
+ d ? (o.position?.side === "short" && o.close(s), (!o.position || o.position.side === "short") && o.placeOrder({ side: "long", type: "market", quantity: n, tag: s })) : m && o.position?.side === "long" && o.close(s);
460
+ };
461
+ }
462
+ function F(i, t, e) {
463
+ let n = 0;
464
+ for (let s = t - e + 1; s <= t; s++)
465
+ n += i[s].close;
466
+ return n / e;
467
+ }
468
+ function X(i = {}) {
469
+ const t = i.period ?? 14, e = i.oversold ?? 30, n = i.overbought ?? 70, s = i.size ?? 1, o = i.tag ?? "rsi-reversion";
470
+ return (r) => {
471
+ if (r.index < t + 1) return;
472
+ const a = k(r.history, r.index, t), c = k(r.history, r.index - 1, t), u = c < e && a >= e, h = c < n && a >= n;
473
+ u && !r.position ? r.placeOrder({ side: "long", type: "market", quantity: s, tag: o }) : h && r.position?.side === "long" && r.close(o);
474
+ };
475
+ }
476
+ function k(i, t, e) {
477
+ let n = 0, s = 0;
478
+ for (let r = t - e + 1; r <= t; r++) {
479
+ const a = i[r].close - i[r - 1].close;
480
+ a >= 0 ? n += a : s -= a;
481
+ }
482
+ return s === 0 ? 100 : 100 - 100 / (1 + n / e / (s / e));
483
+ }
484
+ function tt(i = {}) {
485
+ const t = i.entryPeriod ?? 20, e = i.exitPeriod ?? 10, n = i.size ?? 1, s = i.tag ?? "donchian-breakout";
486
+ return (o) => {
487
+ if (o.index < t) return;
488
+ const r = o.bar.close, a = D(o.history, o.index - t, o.index - 1), c = R(o.history, o.index - t, o.index - 1), u = D(o.history, o.index - e, o.index - 1), h = R(o.history, o.index - e, o.index - 1);
489
+ o.position ? (o.position.side === "long" && r < h || o.position.side === "short" && r > u) && o.close(s) : r > a ? o.placeOrder({ side: "long", type: "market", quantity: n, tag: s }) : r < c && o.placeOrder({ side: "short", type: "market", quantity: n, tag: s });
490
+ };
491
+ }
492
+ function D(i, t, e) {
493
+ let n = -1 / 0;
494
+ for (let s = Math.max(0, t); s <= e; s++)
495
+ i[s].high > n && (n = i[s].high);
496
+ return n;
497
+ }
498
+ function R(i, t, e) {
499
+ let n = 1 / 0;
500
+ for (let s = Math.max(0, t); s <= e; s++)
501
+ i[s].low < n && (n = i[s].low);
502
+ return n;
503
+ }
504
+ function et(i = {}) {
505
+ const t = i.period ?? 20, e = i.stdDev ?? 2, n = i.size ?? 1, s = i.tag ?? "bollinger-reversion";
506
+ return (o) => {
507
+ if (o.index < t) return;
508
+ const { mid: r, lower: a } = W(o.history, o.index, t, e), c = o.bar.close;
509
+ !o.position && c <= a ? o.placeOrder({ side: "long", type: "market", quantity: n, tag: s }) : o.position?.side === "long" && c >= r && o.close(s);
510
+ };
511
+ }
512
+ function W(i, t, e, n) {
513
+ let s = 0;
514
+ for (let c = t - e + 1; c <= t; c++) s += i[c].close;
515
+ const o = s / e;
516
+ let r = 0;
517
+ for (let c = t - e + 1; c <= t; c++) {
518
+ const u = i[c].close - o;
519
+ r += u * u;
520
+ }
521
+ const a = Math.sqrt(r / e);
522
+ return { mid: o, upper: o + n * a, lower: o - n * a };
523
+ }
380
524
  export {
381
- W as Backtester,
382
- A as FixedCommission,
383
- E as NO_SLIPPAGE,
384
- V as PerShareCommission,
385
- Y as PercentCommission,
386
- _ as PercentSlippage,
387
- k as Portfolio,
388
- B as RangeBasedSlippage,
389
- F as ZERO_COMMISSION,
390
- D as computeRiskMetrics
525
+ x as Backtester,
526
+ $ as FixedCommission,
527
+ L as NO_SLIPPAGE,
528
+ Q as PerShareCommission,
529
+ G as PercentCommission,
530
+ U as PercentSlippage,
531
+ I as Portfolio,
532
+ Z as RangeBasedSlippage,
533
+ A as ZERO_COMMISSION,
534
+ et as bollingerReversionStrategy,
535
+ T as computeRiskMetrics,
536
+ tt as donchianBreakoutStrategy,
537
+ X as rsiReversionStrategy,
538
+ J as runMonteCarlo,
539
+ K as smaCrossStrategy
391
540
  };
392
541
  //# sourceMappingURL=index.js.map