@adaptic/utils 0.0.989 → 0.0.990

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.cjs CHANGED
@@ -7804,6 +7804,81 @@ const fetchTickerNews = async (ticker, options = {
7804
7804
  });
7805
7805
  };
7806
7806
 
7807
+ /**
7808
+ * Wilder ATR + EWMA-smoothed + multi-timespan variants.
7809
+ *
7810
+ * All functions are pure: same inputs → same output, no I/O, no time-dependence.
7811
+ */
7812
+ /**
7813
+ * Classic Wilder ATR (single value).
7814
+ * @returns ATR for the most recent `period` bars, or null when insufficient bars.
7815
+ */
7816
+ function calculateATR(highs, lows, closes, period) {
7817
+ if (period < 1 || !Number.isInteger(period)) {
7818
+ throw new Error("ATR: period must be a positive integer");
7819
+ }
7820
+ if (highs.length !== lows.length || highs.length !== closes.length) {
7821
+ throw new Error("ATR: highs, lows, closes must have equal length");
7822
+ }
7823
+ if (highs.length < period + 1)
7824
+ return null;
7825
+ const trs = [];
7826
+ for (let i = 1; i < highs.length; i++) {
7827
+ const tr = Math.max(highs[i] - lows[i], Math.abs(highs[i] - closes[i - 1]), Math.abs(lows[i] - closes[i - 1]));
7828
+ trs.push(tr);
7829
+ }
7830
+ // Wilder smoothing: first ATR = simple mean of first `period` TRs
7831
+ let atr = trs.slice(0, period).reduce((a, b) => a + b, 0) / period;
7832
+ for (let i = period; i < trs.length; i++) {
7833
+ atr = (atr * (period - 1) + trs[i]) / period;
7834
+ }
7835
+ return atr;
7836
+ }
7837
+ /**
7838
+ * Returns the EWMA-smoothed ATR series (one ATR per bar, leading nulls until `period` bars).
7839
+ */
7840
+ function calculateATREMA(highs, lows, closes, period) {
7841
+ if (period < 1 || !Number.isInteger(period)) {
7842
+ throw new Error("ATR: period must be a positive integer");
7843
+ }
7844
+ if (highs.length !== lows.length || highs.length !== closes.length) {
7845
+ throw new Error("ATREMA: highs, lows, closes must have equal length");
7846
+ }
7847
+ const out = highs.map(() => null);
7848
+ if (highs.length < period + 1)
7849
+ return out;
7850
+ // Index 0 is a placeholder so trs[i] aligns with bar i (i >= 1); TR is undefined for the first bar (no prior close).
7851
+ const trs = [0];
7852
+ for (let i = 1; i < highs.length; i++) {
7853
+ trs.push(Math.max(highs[i] - lows[i], Math.abs(highs[i] - closes[i - 1]), Math.abs(lows[i] - closes[i - 1])));
7854
+ }
7855
+ let atr = trs.slice(1, period + 1).reduce((a, b) => a + b, 0) / period;
7856
+ out[period] = atr;
7857
+ for (let i = period + 1; i < trs.length; i++) {
7858
+ atr = (atr * (period - 1) + trs[i]) / period;
7859
+ out[i] = atr;
7860
+ }
7861
+ return out;
7862
+ }
7863
+ /**
7864
+ * Wrapper that accepts AtrBar[] (timespan-tagged); identical math, the timespan tag is
7865
+ * carried through for consumer-side logic only.
7866
+ *
7867
+ * @param bars - All bars must share the same timespan; the tag is metadata only
7868
+ * and is not validated. Mixed-timespan input produces a meaningless ATR.
7869
+ * @param period - Wilder lookback period (positive integer).
7870
+ */
7871
+ function calculateATRMultiTimespan(bars, period) {
7872
+ return calculateATR(bars.map((b) => b.high), bars.map((b) => b.low), bars.map((b) => b.close), period);
7873
+ }
7874
+
7875
+ var atrNs = /*#__PURE__*/Object.freeze({
7876
+ __proto__: null,
7877
+ calculateATR: calculateATR,
7878
+ calculateATREMA: calculateATREMA,
7879
+ calculateATRMultiTimespan: calculateATRMultiTimespan
7880
+ });
7881
+
7807
7882
  const ALPACA_API_BASE = MARKET_DATA_API.CRYPTO;
7808
7883
  /**
7809
7884
  * Fetches cryptocurrency bars for the specified parameters.
@@ -10853,6 +10928,335 @@ async function fetchPerformanceMetrics({ params, client, accountId, alpacaAccoun
10853
10928
  }
10854
10929
  }
10855
10930
 
10931
+ /**
10932
+ * VaR, Expected Shortfall (CVaR), conditional drawdown, rolling drawdown, Sortino, Calmar.
10933
+ *
10934
+ * Convention: VaR and ES are returned as the actual quantile value (typically negative
10935
+ * for losses). Drawdowns from `calculateConditionalDrawdown` are returned as non-negative
10936
+ * magnitudes (e.g., 0.05 = 5% drawdown). Drawdowns from `calculateRollingDrawdown` are
10937
+ * non-positive (e.g., -0.05 = 5% below rolling peak, 0 = at or above peak).
10938
+ *
10939
+ * All public functions reject non-finite inputs (NaN, Infinity) by throwing. Callers
10940
+ * must pre-validate or filter their inputs.
10941
+ */
10942
+ function assertAlpha(alpha) {
10943
+ if (!(alpha > 0 && alpha < 1)) {
10944
+ throw new Error(`alpha must be in (0,1), got ${alpha}`);
10945
+ }
10946
+ }
10947
+ function assertFiniteArray$1(name, arr) {
10948
+ for (let i = 0; i < arr.length; i++) {
10949
+ if (!Number.isFinite(arr[i])) {
10950
+ throw new Error(`${name}: input contains non-finite value at index ${i}: ${arr[i]}`);
10951
+ }
10952
+ }
10953
+ }
10954
+ /**
10955
+ * Historical-bootstrap VaR at confidence `alpha`.
10956
+ * E.g., alpha=0.95 returns the 5%-quantile of returns (the loss at the 5th percentile).
10957
+ *
10958
+ * @returns The quantile value (typically negative), or null on empty input.
10959
+ */
10960
+ function calculateVaRHistorical(returns, alpha) {
10961
+ assertAlpha(alpha);
10962
+ if (returns.length === 0)
10963
+ return null;
10964
+ assertFiniteArray$1("calculateVaRHistorical", returns);
10965
+ const sorted = [...returns].sort((a, b) => a - b);
10966
+ const idx = Math.max(0, Math.floor((1 - alpha) * sorted.length) - 1);
10967
+ return sorted[idx];
10968
+ }
10969
+ /**
10970
+ * Gaussian parametric VaR: μ + zα·σ where zα is the (1-alpha) standard-normal quantile.
10971
+ *
10972
+ * @returns The Gaussian quantile, or null when fewer than 2 samples.
10973
+ */
10974
+ function calculateVaRParametric(returns, alpha) {
10975
+ assertAlpha(alpha);
10976
+ if (returns.length < 2)
10977
+ return null;
10978
+ assertFiniteArray$1("calculateVaRParametric", returns);
10979
+ const mean = returns.reduce((a, b) => a + b, 0) / returns.length;
10980
+ const variance = returns.reduce((a, b) => a + (b - mean) ** 2, 0) / (returns.length - 1);
10981
+ const sigma = Math.sqrt(variance);
10982
+ const z = inverseStdNormal(1 - alpha);
10983
+ return mean + z * sigma;
10984
+ }
10985
+ /**
10986
+ * Expected Shortfall (Conditional VaR): average of returns below the (1-alpha) quantile.
10987
+ *
10988
+ * @returns The mean tail return (typically negative), or null on empty input.
10989
+ */
10990
+ function calculateExpectedShortfall(returns, alpha) {
10991
+ assertAlpha(alpha);
10992
+ if (returns.length === 0)
10993
+ return null;
10994
+ assertFiniteArray$1("calculateExpectedShortfall", returns);
10995
+ const sorted = [...returns].sort((a, b) => a - b);
10996
+ const cutoff = Math.max(1, Math.floor((1 - alpha) * sorted.length));
10997
+ const tail = sorted.slice(0, cutoff);
10998
+ return tail.reduce((a, b) => a + b, 0) / tail.length;
10999
+ }
11000
+ /**
11001
+ * Conditional Drawdown at Risk (CDaR): average of drawdowns in the worst (1-alpha) tail.
11002
+ * Drawdowns are computed as (peak - equity) / peak so they are non-negative.
11003
+ *
11004
+ * @returns A non-negative magnitude (0 = no drawdowns), or null for fewer than 2 samples.
11005
+ */
11006
+ function calculateConditionalDrawdown(equity, alpha) {
11007
+ assertAlpha(alpha);
11008
+ if (equity.length < 2)
11009
+ return null;
11010
+ assertFiniteArray$1("calculateConditionalDrawdown", equity);
11011
+ let peak = equity[0];
11012
+ const drawdowns = [];
11013
+ for (const e of equity) {
11014
+ if (e > peak)
11015
+ peak = e;
11016
+ drawdowns.push(peak > 0 ? (peak - e) / peak : 0);
11017
+ }
11018
+ if (drawdowns.every((d) => d === 0))
11019
+ return 0;
11020
+ const sorted = [...drawdowns].sort((a, b) => b - a); // descending (worst first)
11021
+ const cutoff = Math.max(1, Math.floor((1 - alpha) * sorted.length));
11022
+ const tail = sorted.slice(0, cutoff);
11023
+ return tail.reduce((a, b) => a + b, 0) / tail.length;
11024
+ }
11025
+ /**
11026
+ * Rolling-window drawdown series: for each index, drawdown = (current - rollingPeak) / rollingPeak.
11027
+ * Non-positive values; 0 when at or above the rolling peak. Window measured in samples.
11028
+ *
11029
+ * @returns An array the same length as `equity`.
11030
+ */
11031
+ function calculateRollingDrawdown(equity, windowSize) {
11032
+ if (windowSize < 1 || !Number.isInteger(windowSize)) {
11033
+ throw new Error("calculateRollingDrawdown: windowSize must be a positive integer");
11034
+ }
11035
+ assertFiniteArray$1("calculateRollingDrawdown", equity);
11036
+ return equity.map((_, i) => {
11037
+ const start = Math.max(0, i - windowSize + 1);
11038
+ const slice = equity.slice(start, i + 1);
11039
+ const peak = Math.max(...slice);
11040
+ return peak > 0 ? (equity[i] - peak) / peak : 0;
11041
+ });
11042
+ }
11043
+ /**
11044
+ * Sortino ratio: (mean excess return) / downside deviation.
11045
+ * Returns +Infinity when there are no downside returns.
11046
+ * Returns null when fewer than 2 samples.
11047
+ */
11048
+ function calculateSortino(returns, riskFreeRate) {
11049
+ if (returns.length < 2)
11050
+ return null;
11051
+ assertFiniteArray$1("calculateSortino", returns);
11052
+ const excess = returns.map((r) => r - riskFreeRate);
11053
+ const meanExcess = excess.reduce((a, b) => a + b, 0) / excess.length;
11054
+ const downside = excess.filter((r) => r < 0);
11055
+ if (downside.length === 0)
11056
+ return Number.POSITIVE_INFINITY;
11057
+ const dd = Math.sqrt(downside.reduce((a, b) => a + b * b, 0) / downside.length);
11058
+ return meanExcess / dd;
11059
+ }
11060
+ /**
11061
+ * Calmar ratio: CAGR / |max drawdown|.
11062
+ *
11063
+ * @returns null when there is no drawdown (division by zero), fewer than 2 samples,
11064
+ * or `equity[0] <= 0` (CAGR undefined).
11065
+ */
11066
+ function calculateCalmar(equity, periodsPerYear) {
11067
+ if (equity.length < 2)
11068
+ return null;
11069
+ if (equity[0] <= 0)
11070
+ return null;
11071
+ if (periodsPerYear <= 0) {
11072
+ throw new Error("calculateCalmar: periodsPerYear must be > 0");
11073
+ }
11074
+ assertFiniteArray$1("calculateCalmar", equity);
11075
+ const total = equity[equity.length - 1] / equity[0];
11076
+ const years = (equity.length - 1) / periodsPerYear;
11077
+ const cagr = years > 0 ? Math.pow(total, 1 / years) - 1 : 0;
11078
+ let peak = equity[0];
11079
+ let maxDd = 0;
11080
+ for (const e of equity) {
11081
+ if (e > peak)
11082
+ peak = e;
11083
+ const dd = peak > 0 ? (peak - e) / peak : 0;
11084
+ if (dd > maxDd)
11085
+ maxDd = dd;
11086
+ }
11087
+ return maxDd === 0 ? null : cagr / maxDd;
11088
+ }
11089
+ /**
11090
+ * Beasley-Springer-Moro approximation of the inverse standard normal CDF.
11091
+ * Accurate to ~1e-9 across the full domain; sufficient for VaR work.
11092
+ */
11093
+ function inverseStdNormal(p) {
11094
+ if (p <= 0 || p >= 1)
11095
+ throw new Error("p must be in (0,1)");
11096
+ const a = [-39.69683028665376, 2.209460984245205e2, -275.9285104469687,
11097
+ 1.38357751867269e2, -30.66479806614716, 2.506628277459239];
11098
+ const b = [-54.47609879822406, 1.615858368580409e2, -155.6989798598866,
11099
+ 6.680131188771972e1, -13.28068155288572];
11100
+ const c = [-0.007784894002430293, -0.3223964580411365, -2.400758277161838,
11101
+ -2.549732539343734, 4.374664141464968, 2.938163982698783];
11102
+ const d = [7.784695709041462e-3, 3.224671290700398e-1, 2.445134137142996,
11103
+ 3.754408661907416];
11104
+ const pLow = 0.02425;
11105
+ const pHigh = 1 - pLow;
11106
+ let q, r;
11107
+ if (p < pLow) {
11108
+ q = Math.sqrt(-2 * Math.log(p));
11109
+ return (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /
11110
+ ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
11111
+ }
11112
+ if (p <= pHigh) {
11113
+ q = p - 0.5;
11114
+ r = q * q;
11115
+ return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q /
11116
+ (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);
11117
+ }
11118
+ q = Math.sqrt(-2 * Math.log(1 - p));
11119
+ return -(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /
11120
+ ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
11121
+ }
11122
+
11123
+ var riskNs = /*#__PURE__*/Object.freeze({
11124
+ __proto__: null,
11125
+ calculateCalmar: calculateCalmar,
11126
+ calculateConditionalDrawdown: calculateConditionalDrawdown,
11127
+ calculateExpectedShortfall: calculateExpectedShortfall,
11128
+ calculateRollingDrawdown: calculateRollingDrawdown,
11129
+ calculateSortino: calculateSortino,
11130
+ calculateVaRHistorical: calculateVaRHistorical,
11131
+ calculateVaRParametric: calculateVaRParametric
11132
+ });
11133
+
11134
+ /**
11135
+ * Per-strategy rolling metrics and backtest-divergence z-score.
11136
+ *
11137
+ * Conventions:
11138
+ * - tradePnls / tradeReturns is an array of per-trade realised P&L or return
11139
+ * (positive = win, negative = loss, zero = breakeven).
11140
+ * - All "rolling*" functions return null when fewer than `windowSize` trades exist.
11141
+ * - All public functions reject non-finite inputs (NaN, Infinity) by throwing.
11142
+ * Callers must pre-validate or filter their inputs.
11143
+ */
11144
+ function assertWindowSize(name, windowSize) {
11145
+ if (windowSize < 1 || !Number.isInteger(windowSize)) {
11146
+ throw new Error(`${name}: windowSize must be a positive integer`);
11147
+ }
11148
+ }
11149
+ function assertFiniteArray(name, arr) {
11150
+ for (let i = 0; i < arr.length; i++) {
11151
+ if (!Number.isFinite(arr[i])) {
11152
+ throw new Error(`${name}: input contains non-finite value at index ${i}: ${arr[i]}`);
11153
+ }
11154
+ }
11155
+ }
11156
+ /**
11157
+ * Rolling expectancy: mean P&L over the most-recent `windowSize` trades.
11158
+ *
11159
+ * @param tradePnls - Array of per-trade realised P&L values.
11160
+ * @param windowSize - Number of most-recent trades to include. Must be a positive integer.
11161
+ * @returns Mean P&L of the last `windowSize` trades, or null when fewer than `windowSize` exist.
11162
+ * @throws When `windowSize` is not a positive integer or any input is non-finite.
11163
+ */
11164
+ function calculateRollingExpectancy(tradePnls, windowSize) {
11165
+ assertWindowSize("calculateRollingExpectancy", windowSize);
11166
+ if (tradePnls.length < windowSize)
11167
+ return null;
11168
+ assertFiniteArray("calculateRollingExpectancy", tradePnls);
11169
+ const slice = tradePnls.slice(-windowSize);
11170
+ return slice.reduce((a, b) => a + b, 0) / windowSize;
11171
+ }
11172
+ /**
11173
+ * Rolling hit-rate: fraction of strictly-positive P&L trades in the most-recent
11174
+ * `windowSize` trades. Zero P&L counts as non-win.
11175
+ *
11176
+ * @param tradePnls - Array of per-trade realised P&L values.
11177
+ * @param windowSize - Number of most-recent trades to include. Must be a positive integer.
11178
+ * @returns Fraction of winning trades in the window, or null when fewer than `windowSize` exist.
11179
+ * @throws When `windowSize` is not a positive integer or any input is non-finite.
11180
+ */
11181
+ function calculateRollingHitRate(tradePnls, windowSize) {
11182
+ assertWindowSize("calculateRollingHitRate", windowSize);
11183
+ if (tradePnls.length < windowSize)
11184
+ return null;
11185
+ assertFiniteArray("calculateRollingHitRate", tradePnls);
11186
+ const slice = tradePnls.slice(-windowSize);
11187
+ const wins = slice.filter((p) => p > 0).length;
11188
+ return wins / windowSize;
11189
+ }
11190
+ /**
11191
+ * Rolling profit factor: sum(wins) / |sum(losses)| over the most-recent `windowSize` trades.
11192
+ *
11193
+ * Edge cases:
11194
+ * - no losses and at least one win → +Infinity
11195
+ * - no wins and no losses (all zeros) → 0
11196
+ * - fewer than windowSize trades → null
11197
+ *
11198
+ * @param tradePnls - Array of per-trade realised P&L values.
11199
+ * @param windowSize - Number of most-recent trades to include. Must be a positive integer.
11200
+ * @returns Profit factor for the rolling window, or null when fewer than `windowSize` exist.
11201
+ * @throws When `windowSize` is not a positive integer or any input is non-finite.
11202
+ */
11203
+ function calculateRollingProfitFactor(tradePnls, windowSize) {
11204
+ assertWindowSize("calculateRollingProfitFactor", windowSize);
11205
+ if (tradePnls.length < windowSize)
11206
+ return null;
11207
+ assertFiniteArray("calculateRollingProfitFactor", tradePnls);
11208
+ const slice = tradePnls.slice(-windowSize);
11209
+ const wins = slice.filter((p) => p > 0).reduce((a, b) => a + b, 0);
11210
+ const losses = slice.filter((p) => p < 0).reduce((a, b) => a + Math.abs(b), 0);
11211
+ if (losses === 0)
11212
+ return wins > 0 ? Number.POSITIVE_INFINITY : 0;
11213
+ return wins / losses;
11214
+ }
11215
+ /**
11216
+ * Rolling Sortino: delegate to `calculateSortino` over the most-recent `windowSize` returns.
11217
+ *
11218
+ * @param tradeReturns - Array of per-trade return values.
11219
+ * @param windowSize - Number of most-recent trades to include. Must be a positive integer.
11220
+ * @param riskFreeRate - Risk-free rate to subtract from returns (default 0).
11221
+ * @returns Sortino ratio for the rolling window, or null when fewer than `windowSize` exist.
11222
+ * @throws When `windowSize` is not a positive integer or any input is non-finite.
11223
+ */
11224
+ function calculateRollingSortino(tradeReturns, windowSize, riskFreeRate = 0) {
11225
+ assertWindowSize("calculateRollingSortino", windowSize);
11226
+ if (tradeReturns.length < windowSize)
11227
+ return null;
11228
+ assertFiniteArray("calculateRollingSortino", tradeReturns);
11229
+ return calculateSortino(tradeReturns.slice(-windowSize), riskFreeRate);
11230
+ }
11231
+ /**
11232
+ * Z-score of live-expectancy vs backtest-expectancy, scaled by the backtest stddev.
11233
+ * Positive Z = live outperforming; negative Z = live underperforming.
11234
+ *
11235
+ * @param liveExpectancy - Mean P&L per trade in the live window.
11236
+ * @param backtestExpectancy - Mean P&L per trade from the calibration backtest.
11237
+ * @param backtestStddev - Stddev of per-trade P&L in the backtest. Must be > 0.
11238
+ * @returns Z-score measuring divergence between live and backtest performance.
11239
+ * @throws When any input is non-finite or `backtestStddev` is not positive.
11240
+ */
11241
+ function calculateBacktestDivergenceZ(liveExpectancy, backtestExpectancy, backtestStddev) {
11242
+ if (!Number.isFinite(liveExpectancy) || !Number.isFinite(backtestExpectancy) || !Number.isFinite(backtestStddev)) {
11243
+ throw new Error("calculateBacktestDivergenceZ: inputs must be finite numbers");
11244
+ }
11245
+ if (backtestStddev <= 0) {
11246
+ throw new Error("calculateBacktestDivergenceZ: stddev must be > 0");
11247
+ }
11248
+ return (liveExpectancy - backtestExpectancy) / backtestStddev;
11249
+ }
11250
+
11251
+ var strategyNs = /*#__PURE__*/Object.freeze({
11252
+ __proto__: null,
11253
+ calculateBacktestDivergenceZ: calculateBacktestDivergenceZ,
11254
+ calculateRollingExpectancy: calculateRollingExpectancy,
11255
+ calculateRollingHitRate: calculateRollingHitRate,
11256
+ calculateRollingProfitFactor: calculateRollingProfitFactor,
11257
+ calculateRollingSortino: calculateRollingSortino
11258
+ });
11259
+
10856
11260
  /**
10857
11261
  * Calculates Bollinger Bands for a given set of price data.
10858
11262
  * Bollinger Bands consist of a middle band (SMA) and two outer bands
@@ -11479,6 +11883,98 @@ var Types = /*#__PURE__*/Object.freeze({
11479
11883
  __proto__: null
11480
11884
  });
11481
11885
 
11886
+ /**
11887
+ * Realized and EWMA volatility + regime classifier + annualisation helper.
11888
+ * All functions pure.
11889
+ */
11890
+ /**
11891
+ * Sample standard deviation (Bessel-corrected) of returns over the most recent
11892
+ * `window` samples.
11893
+ * @returns null when fewer than `window` samples.
11894
+ */
11895
+ function calculateRealizedVolatility(returns, window) {
11896
+ if (window < 2 || !Number.isInteger(window)) {
11897
+ throw new Error("calculateRealizedVolatility: window must be an integer >= 2");
11898
+ }
11899
+ if (returns.length < window)
11900
+ return null;
11901
+ const slice = returns.slice(-window);
11902
+ const mean = slice.reduce((a, b) => a + b, 0) / window;
11903
+ const variance = slice.reduce((a, b) => a + (b - mean) ** 2, 0) / (window - 1);
11904
+ return Math.sqrt(variance);
11905
+ }
11906
+ /**
11907
+ * EWMA volatility (RiskMetrics-style). λ ∈ (0,1); higher = longer memory.
11908
+ * Default usage: λ = 0.94 for daily returns.
11909
+ *
11910
+ * For a single-element input, the function returns `|returns[0]|` (the seed)
11911
+ * since no smoothing iterations are possible.
11912
+ *
11913
+ * @param returns - Period returns (e.g., log returns or simple returns).
11914
+ * @param lambda - Decay factor in (0,1).
11915
+ * @returns EWMA standard deviation, or null on empty input.
11916
+ * @throws when `lambda` is outside (0,1).
11917
+ */
11918
+ function calculateEWMAVolatility(returns, lambda) {
11919
+ if (lambda <= 0 || lambda >= 1) {
11920
+ throw new Error("calculateEWMAVolatility: lambda must be in (0,1)");
11921
+ }
11922
+ if (returns.length === 0)
11923
+ return null;
11924
+ let variance = returns[0] ** 2;
11925
+ for (let i = 1; i < returns.length; i++) {
11926
+ variance = lambda * variance + (1 - lambda) * returns[i] ** 2;
11927
+ }
11928
+ return Math.sqrt(variance);
11929
+ }
11930
+ /**
11931
+ * Classify a volatility value into one of four regimes.
11932
+ *
11933
+ * Bands are checked in the order: crisis (≥crisisMin) → elevated (≥elevatedMax)
11934
+ * → calm (≤calmMax) → normal (otherwise).
11935
+ *
11936
+ * @throws when bands are not strictly ordered (calmMax < elevatedMax < crisisMin).
11937
+ */
11938
+ function detectVolatilityRegime(volatility, bands) {
11939
+ if (!(bands.calmMax < bands.elevatedMax && bands.elevatedMax < bands.crisisMin)) {
11940
+ throw new Error(`detectVolatilityRegime: bands must satisfy calmMax < elevatedMax < crisisMin (got ${JSON.stringify(bands)})`);
11941
+ }
11942
+ if (volatility >= bands.crisisMin)
11943
+ return "crisis";
11944
+ if (volatility >= bands.elevatedMax)
11945
+ return "elevated";
11946
+ if (volatility <= bands.calmMax)
11947
+ return "calm";
11948
+ return "normal";
11949
+ }
11950
+ /**
11951
+ * Annualise a volatility computed at the given cadence by multiplying by
11952
+ * the square root of the periods per year.
11953
+ *
11954
+ * - daily → sqrt(252) (252 trading days per year)
11955
+ * - hourly → sqrt(252 × 6.5) (6.5 RTH hours per trading day)
11956
+ * - minute → sqrt(252 × 6.5 × 60) (60 minutes per RTH hour)
11957
+ *
11958
+ * @param volatility - Per-period volatility (stddev).
11959
+ * @param cadence - The cadence at which `volatility` was sampled.
11960
+ * @returns The annualised volatility.
11961
+ */
11962
+ function annualiseVolatility(volatility, cadence) {
11963
+ switch (cadence) {
11964
+ case "daily": return volatility * Math.sqrt(252);
11965
+ case "hourly": return volatility * Math.sqrt(252 * 6.5);
11966
+ case "minute": return volatility * Math.sqrt(252 * 6.5 * 60);
11967
+ }
11968
+ }
11969
+
11970
+ var volatilityNs = /*#__PURE__*/Object.freeze({
11971
+ __proto__: null,
11972
+ annualiseVolatility: annualiseVolatility,
11973
+ calculateEWMAVolatility: calculateEWMAVolatility,
11974
+ calculateRealizedVolatility: calculateRealizedVolatility,
11975
+ detectVolatilityRegime: detectVolatilityRegime
11976
+ });
11977
+
11482
11978
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
11483
11979
 
11484
11980
  function getDefaultExportFromCjs (x) {
@@ -68697,6 +69193,10 @@ const createAlpacaMarketDataAPI = () => {
68697
69193
  };
68698
69194
  const adaptic = {
68699
69195
  types: Types,
69196
+ atr: atrNs,
69197
+ risk: riskNs,
69198
+ strategy: strategyNs,
69199
+ volatility: volatilityNs,
68700
69200
  backend: {
68701
69201
  fetchAssetOverview: fetchAssetOverview,
68702
69202
  getApolloClient: getSharedApolloClient,
@@ -68965,6 +69465,7 @@ exports.adptc = adptc;
68965
69465
  exports.alpaca = alpaca;
68966
69466
  exports.analyzeBars = analyzeBars;
68967
69467
  exports.approximateImpliedVolatility = approximateImpliedVolatility;
69468
+ exports.atr = atrNs;
68968
69469
  exports.bracketOrders = bracketOrders;
68969
69470
  exports.buildOCCSymbol = buildOCCSymbol;
68970
69471
  exports.buildOptionSymbol = buildOptionSymbol;
@@ -69147,6 +69648,7 @@ exports.protectShortPosition = protectShortPosition;
69147
69648
  exports.rateLimiters = rateLimiters;
69148
69649
  exports.resetLogger = resetLogger;
69149
69650
  exports.resetRiskFreeRateCache = resetRiskFreeRateCache;
69651
+ exports.risk = riskNs;
69150
69652
  exports.rollOptionPosition = rollOptionPosition;
69151
69653
  exports.roundPriceForAlpaca = roundPriceForAlpaca$3;
69152
69654
  exports.roundPriceForAlpacaNumber = roundPriceForAlpacaNumber;
@@ -69160,6 +69662,7 @@ exports.setLogger = setLogger;
69160
69662
  exports.setRiskFreeRate = setRiskFreeRate;
69161
69663
  exports.shortWithStopLoss = shortWithStopLoss;
69162
69664
  exports.sortOrdersByDate = sortOrdersByDate;
69665
+ exports.strategy = strategyNs;
69163
69666
  exports.tradingPolicy = index;
69164
69667
  exports.trailingStops = trailingStops;
69165
69668
  exports.updateAccountConfiguration = updateAccountConfiguration;
@@ -69170,6 +69673,7 @@ exports.validateMassiveApiKey = validateMassiveApiKey$1;
69170
69673
  exports.validateMultiLegOrder = validateMultiLegOrder;
69171
69674
  exports.validateResponse = validateResponse;
69172
69675
  exports.verifyFetchKeepAlive = verifyFetchKeepAlive;
69676
+ exports.volatility = volatilityNs;
69173
69677
  exports.waitForOrderFill = waitForOrderFill;
69174
69678
  exports.withRetry = withRetry;
69175
69679
  exports.withTimeout = withTimeout;