@adaptic/utils 0.0.1031 → 0.0.1033
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 +631 -208
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +627 -209
- package/dist/index.mjs.map +1 -1
- package/dist/types/__tests__/indicator-parity/generate.d.ts +64 -0
- package/dist/types/__tests__/indicator-parity/generate.d.ts.map +1 -0
- package/dist/types/__tests__/indicator-parity/record.d.ts +144 -0
- package/dist/types/__tests__/indicator-parity/record.d.ts.map +1 -0
- package/dist/types/__tests__/indicator-parity/reference.d.ts +92 -0
- package/dist/types/__tests__/indicator-parity/reference.d.ts.map +1 -0
- package/dist/types/__tests__/indicator-parity/series.d.ts +64 -0
- package/dist/types/__tests__/indicator-parity/series.d.ts.map +1 -0
- package/dist/types/__tests__/indicator-parity/subjects.d.ts +55 -0
- package/dist/types/__tests__/indicator-parity/subjects.d.ts.map +1 -0
- package/dist/types/__tests__/support/statistic.d.ts +18 -0
- package/dist/types/__tests__/support/statistic.d.ts.map +1 -0
- package/dist/types/alpaca/trading/order-utils.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/llm/circuit-breaker.d.ts +18 -1
- package/dist/types/llm/circuit-breaker.d.ts.map +1 -1
- package/dist/types/llm/fallback-chain.d.ts.map +1 -1
- package/dist/types/llm/index.d.ts +3 -1
- package/dist/types/llm/index.d.ts.map +1 -1
- package/dist/types/llm/rate-guard.d.ts +82 -9
- package/dist/types/llm/rate-guard.d.ts.map +1 -1
- package/dist/types/llm/structured-content.d.ts +73 -0
- package/dist/types/llm/structured-content.d.ts.map +1 -0
- package/dist/types/llm/transports/gateway.d.ts.map +1 -1
- package/dist/types/metrics-calcs.d.ts +25 -0
- package/dist/types/metrics-calcs.d.ts.map +1 -1
- package/dist/types/performance-metrics.d.ts +16 -4
- package/dist/types/performance-metrics.d.ts.map +1 -1
- package/dist/types/sample-statistic.d.ts +123 -0
- package/dist/types/sample-statistic.d.ts.map +1 -0
- package/dist/types/schemas/alpaca-schemas.d.ts.map +1 -1
- package/dist/types/strategy-metrics.d.ts +38 -16
- package/dist/types/strategy-metrics.d.ts.map +1 -1
- package/dist/types/trading-policy/schemas/effective-policy.schema.d.ts +18 -18
- package/dist/types/trading-policy/schemas/model-prefs.schema.d.ts +24 -24
- package/dist/types/trading-policy/schemas/policy-mutation.schema.d.ts +36 -36
- package/dist/types/types/alpaca-types.d.ts +36 -5
- package/dist/types/types/alpaca-types.d.ts.map +1 -1
- package/package.json +3 -1
package/dist/index.mjs
CHANGED
|
@@ -20221,6 +20221,93 @@ function getEquityValues(equityData, portfolioHistory, marketTimeUtil, period) {
|
|
|
20221
20221
|
};
|
|
20222
20222
|
}
|
|
20223
20223
|
|
|
20224
|
+
/**
|
|
20225
|
+
* A measured statistic and the cohort it was measured on, carried as one
|
|
20226
|
+
* inseparable value.
|
|
20227
|
+
*
|
|
20228
|
+
* A ratio is meaningless without the population it was taken over: the same
|
|
20229
|
+
* `0.42` is a strong result on 2,000 trades and noise on five, and a `0.0`
|
|
20230
|
+
* returned because nothing could be computed is indistinguishable from a `0.0`
|
|
20231
|
+
* that was genuinely measured. Both confusions are the same error — a number
|
|
20232
|
+
* read apart from its unit and its cohort — and both have produced wrong
|
|
20233
|
+
* conclusions from correct arithmetic.
|
|
20234
|
+
*
|
|
20235
|
+
* This type removes the option. Every statistic shaped by a population carries
|
|
20236
|
+
* `sampleCount` (how many observations actually entered the computation) and
|
|
20237
|
+
* `coverage` (what fraction of the observations the caller offered were usable),
|
|
20238
|
+
* on BOTH branches: an unavailable statistic still reports how much data it
|
|
20239
|
+
* saw, because "we had nothing" and "we had 900 rows and still could not
|
|
20240
|
+
* compute it" are different facts with different responses.
|
|
20241
|
+
*
|
|
20242
|
+
* Absence is a branch of the union rather than a sentinel value. There is no
|
|
20243
|
+
* number a caller can read without first proving the statistic exists, which is
|
|
20244
|
+
* what keeps an unknown from silently becoming a zero on its way to a decision.
|
|
20245
|
+
*
|
|
20246
|
+
* @module sample-statistic
|
|
20247
|
+
*/
|
|
20248
|
+
/**
|
|
20249
|
+
* Build the cohort descriptor for a computation.
|
|
20250
|
+
*
|
|
20251
|
+
* `coverage` is derived here rather than supplied, so it cannot drift from the
|
|
20252
|
+
* counts it claims to summarise. A zero request yields zero coverage: no
|
|
20253
|
+
* observations were asked for, so none were covered, and the alternative (`1`)
|
|
20254
|
+
* would report a vacuous computation as fully covered.
|
|
20255
|
+
*
|
|
20256
|
+
* @param requestedCount - Observations offered, or the window width requested.
|
|
20257
|
+
* @param sampleCount - Observations that entered the computation.
|
|
20258
|
+
* @returns The cohort descriptor with `coverage` derived from the two counts.
|
|
20259
|
+
* @throws When either count is negative or non-finite, which is a programming
|
|
20260
|
+
* error rather than a data condition.
|
|
20261
|
+
*/
|
|
20262
|
+
function sampleCohort(requestedCount, sampleCount) {
|
|
20263
|
+
if (!Number.isFinite(requestedCount) || requestedCount < 0) {
|
|
20264
|
+
throw new Error(`sampleCohort: requestedCount must be a non-negative finite number (got ${requestedCount})`);
|
|
20265
|
+
}
|
|
20266
|
+
if (!Number.isFinite(sampleCount) || sampleCount < 0) {
|
|
20267
|
+
throw new Error(`sampleCohort: sampleCount must be a non-negative finite number (got ${sampleCount})`);
|
|
20268
|
+
}
|
|
20269
|
+
const NOTHING_REQUESTED_COVERAGE = 0;
|
|
20270
|
+
const FULL_COVERAGE = 1;
|
|
20271
|
+
const coverage = requestedCount === 0
|
|
20272
|
+
? NOTHING_REQUESTED_COVERAGE
|
|
20273
|
+
: Math.min(FULL_COVERAGE, sampleCount / requestedCount);
|
|
20274
|
+
return { sampleCount, requestedCount, coverage };
|
|
20275
|
+
}
|
|
20276
|
+
/**
|
|
20277
|
+
* Wrap a computed value with its cohort.
|
|
20278
|
+
*
|
|
20279
|
+
* @param value - The measured statistic.
|
|
20280
|
+
* @param cohort - The cohort it was measured on.
|
|
20281
|
+
* @returns The available branch of {@link SampleStatistic}.
|
|
20282
|
+
*/
|
|
20283
|
+
function availableStatistic(value, cohort) {
|
|
20284
|
+
return { available: true, value, ...cohort };
|
|
20285
|
+
}
|
|
20286
|
+
/**
|
|
20287
|
+
* Record that a statistic could not be computed, and what was seen instead.
|
|
20288
|
+
*
|
|
20289
|
+
* @param reason - Which class of failure prevented the computation.
|
|
20290
|
+
* @param detail - Specifics for logs; never machine-parsed.
|
|
20291
|
+
* @param cohort - What data was available when the attempt was abandoned.
|
|
20292
|
+
* @returns The unavailable branch of {@link SampleStatistic}.
|
|
20293
|
+
*/
|
|
20294
|
+
function unavailableStatistic(reason, detail, cohort) {
|
|
20295
|
+
return { available: false, reason, detail, ...cohort };
|
|
20296
|
+
}
|
|
20297
|
+
/**
|
|
20298
|
+
* Narrow a statistic to its available branch.
|
|
20299
|
+
*
|
|
20300
|
+
* Exists so consumers in other packages can discriminate without restating the
|
|
20301
|
+
* predicate, and so the discriminant stays a single named concept if the shape
|
|
20302
|
+
* ever grows a third branch.
|
|
20303
|
+
*
|
|
20304
|
+
* @param statistic - The statistic to test.
|
|
20305
|
+
* @returns Whether the statistic carries a value.
|
|
20306
|
+
*/
|
|
20307
|
+
function isAvailable(statistic) {
|
|
20308
|
+
return statistic.available;
|
|
20309
|
+
}
|
|
20310
|
+
|
|
20224
20311
|
// risk-free-rate.ts
|
|
20225
20312
|
/**
|
|
20226
20313
|
* Conservative fallback annual risk-free rate used when no live rate has been
|
|
@@ -20579,57 +20666,60 @@ function alignReturns(tradeBars, benchmarkBars) {
|
|
|
20579
20666
|
});
|
|
20580
20667
|
return { alignedTradeReturns, alignedBenchmarkReturns, alignedDates };
|
|
20581
20668
|
}
|
|
20582
|
-
|
|
20583
|
-
*
|
|
20584
|
-
*
|
|
20585
|
-
*
|
|
20586
|
-
*
|
|
20669
|
+
/**
|
|
20670
|
+
* Beta of a portfolio against a benchmark, from paired period returns.
|
|
20671
|
+
*
|
|
20672
|
+
* Non-finite rows are dropped pairwise — a return that is `NaN` on either leg
|
|
20673
|
+
* cannot contribute to a covariance — and the count that survives is reported
|
|
20674
|
+
* as the cohort rather than discarded. That reporting is the point: silently
|
|
20675
|
+
* computing a beta on the 12 rows that happened to be clean, and returning it
|
|
20676
|
+
* with the same shape as a beta over all 900, is how a statistic measured on
|
|
20677
|
+
* one population gets applied to another.
|
|
20678
|
+
*
|
|
20679
|
+
* When beta cannot be computed the result is the unavailable branch, never a
|
|
20680
|
+
* numeric stand-in. A beta of `0` asserts that the portfolio does not move with
|
|
20681
|
+
* the market, which is a strong and consequential claim; emitting it to mean
|
|
20682
|
+
* "we could not tell" makes every alpha derived from it wrong by the whole
|
|
20683
|
+
* benchmark term.
|
|
20684
|
+
*
|
|
20685
|
+
* @param portfolioReturns - Portfolio period returns.
|
|
20686
|
+
* @param benchmarkReturns - Benchmark period returns, index-aligned to the portfolio.
|
|
20687
|
+
* @returns The beta components with their cohort, or a typed unavailable result.
|
|
20587
20688
|
* @example
|
|
20588
|
-
* const
|
|
20589
|
-
*
|
|
20590
|
-
*
|
|
20591
|
-
*
|
|
20592
|
-
* @throws Will log warnings if input data is invalid or insufficient
|
|
20593
|
-
* @throws Will log warnings if benchmark variance is effectively zero
|
|
20594
|
-
* @throws Will log warnings if beta calculation results in a non-finite value
|
|
20595
|
-
* @throws Will log warnings if there are not enough valid data points for calculation
|
|
20596
|
-
* @throws Will log warnings if benchmark variance is zero or non-finite
|
|
20689
|
+
* const result = calculateBetaFromReturns([0.05, -0.02, 0.03], [0.03, -0.01, 0.02]);
|
|
20690
|
+
* if (result.available) {
|
|
20691
|
+
* // result.value.beta, alongside result.sampleCount and result.coverage
|
|
20692
|
+
* }
|
|
20597
20693
|
*/
|
|
20598
20694
|
function calculateBetaFromReturns$1(portfolioReturns, benchmarkReturns) {
|
|
20599
|
-
//
|
|
20600
|
-
|
|
20601
|
-
|
|
20602
|
-
|
|
20603
|
-
|
|
20604
|
-
|
|
20605
|
-
|
|
20606
|
-
|
|
20607
|
-
|
|
20608
|
-
|
|
20609
|
-
|
|
20610
|
-
|
|
20611
|
-
|
|
20612
|
-
|
|
20613
|
-
|
|
20614
|
-
const
|
|
20615
|
-
|
|
20616
|
-
|
|
20617
|
-
|
|
20618
|
-
|
|
20619
|
-
|
|
20620
|
-
|
|
20621
|
-
averagePortfolioReturn: 0,
|
|
20622
|
-
averageBenchmarkReturn: 0,
|
|
20623
|
-
};
|
|
20695
|
+
// A covariance is defined over PAIRS, so the offered cohort is the number of
|
|
20696
|
+
// index positions both series can supply. Ragged input is a caller defect
|
|
20697
|
+
// rather than a data condition, and it is reported as such instead of being
|
|
20698
|
+
// silently truncated to the shorter series.
|
|
20699
|
+
if (!Array.isArray(portfolioReturns) || !Array.isArray(benchmarkReturns)) {
|
|
20700
|
+
return unavailableStatistic("invalid_input", "portfolioReturns and benchmarkReturns must both be arrays", sampleCohort(0, 0));
|
|
20701
|
+
}
|
|
20702
|
+
const requestedCount = portfolioReturns.length;
|
|
20703
|
+
if (portfolioReturns.length !== benchmarkReturns.length) {
|
|
20704
|
+
return unavailableStatistic("invalid_input", `series lengths differ: portfolio ${portfolioReturns.length}, benchmark ${benchmarkReturns.length}`, sampleCohort(requestedCount, 0));
|
|
20705
|
+
}
|
|
20706
|
+
// Pairwise finiteness filter. Both legs must be usable for the pair to
|
|
20707
|
+
// contribute; keeping a pair on the strength of one leg would mix a real
|
|
20708
|
+
// observation with a fabricated one.
|
|
20709
|
+
const validIndices = [...Array(requestedCount).keys()].filter((i) => isFinite(portfolioReturns[i]) && isFinite(benchmarkReturns[i]));
|
|
20710
|
+
const cohort = sampleCohort(requestedCount, validIndices.length);
|
|
20711
|
+
// Bessel-corrected estimators need at least one degree of freedom, so two
|
|
20712
|
+
// usable pairs is the floor below which no sample variance exists.
|
|
20713
|
+
const MIN_PAIRS_FOR_SAMPLE_VARIANCE = 2;
|
|
20714
|
+
if (validIndices.length < MIN_PAIRS_FOR_SAMPLE_VARIANCE) {
|
|
20715
|
+
getLogger().warn(`Beta unavailable: ${validIndices.length} usable pairs of ${requestedCount} offered.`);
|
|
20716
|
+
return unavailableStatistic(validIndices.length === 0 ? "no_usable_samples" : "insufficient_samples", `beta needs at least ${MIN_PAIRS_FOR_SAMPLE_VARIANCE} finite pairs; ${validIndices.length} of ${requestedCount} were usable`, cohort);
|
|
20624
20717
|
}
|
|
20625
|
-
// Use validated indices only
|
|
20626
20718
|
const validPortfolioReturns = validIndices.map((i) => portfolioReturns[i]);
|
|
20627
20719
|
const validBenchmarkReturns = validIndices.map((i) => benchmarkReturns[i]);
|
|
20628
|
-
// Calculate means
|
|
20629
20720
|
const n = validIndices.length;
|
|
20630
20721
|
const averagePortfolioReturn = validPortfolioReturns.reduce((sum, ret) => sum + ret, 0) / n;
|
|
20631
20722
|
const averageBenchmarkReturn = validBenchmarkReturns.reduce((sum, ret) => sum + ret, 0) / n;
|
|
20632
|
-
// Calculate covariance and variance with Welford's online algorithm for numerical stability
|
|
20633
20723
|
let covariance = 0;
|
|
20634
20724
|
let variance = 0;
|
|
20635
20725
|
for (let i = 0; i < n; i++) {
|
|
@@ -20638,30 +20728,26 @@ function calculateBetaFromReturns$1(portfolioReturns, benchmarkReturns) {
|
|
|
20638
20728
|
covariance += portfolioDiff * benchmarkDiff;
|
|
20639
20729
|
variance += benchmarkDiff * benchmarkDiff;
|
|
20640
20730
|
}
|
|
20641
|
-
//
|
|
20642
|
-
//
|
|
20643
|
-
// already ensures n >= 2, so (n - 1) is always safe.
|
|
20731
|
+
// Sample (Bessel-corrected) estimators — divide by (n - 1), not n. The guard
|
|
20732
|
+
// above ensures n >= 2, so (n - 1) is always safe.
|
|
20644
20733
|
covariance /= n - 1;
|
|
20645
20734
|
variance /= n - 1;
|
|
20646
|
-
//
|
|
20647
|
-
|
|
20648
|
-
|
|
20649
|
-
|
|
20650
|
-
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
20655
|
-
|
|
20656
|
-
|
|
20657
|
-
const beta = covariance / variance;
|
|
20658
|
-
return {
|
|
20659
|
-
beta,
|
|
20735
|
+
// A benchmark that never moved has no variance to regress against, so beta is
|
|
20736
|
+
// undefined rather than zero. VARIANCE_NOISE_FLOOR absorbs the case where a
|
|
20737
|
+
// constant series still produces a tiny positive variance because the computed
|
|
20738
|
+
// mean differs from the constant by a rounding unit.
|
|
20739
|
+
const VARIANCE_NOISE_FLOOR = 1e-10;
|
|
20740
|
+
if (Math.abs(variance) < VARIANCE_NOISE_FLOOR) {
|
|
20741
|
+
getLogger().warn("Beta unavailable: benchmark variance is effectively zero.");
|
|
20742
|
+
return unavailableStatistic("degenerate_population", `benchmark variance ${variance} is below the noise floor ${VARIANCE_NOISE_FLOOR}; beta is undefined`, cohort);
|
|
20743
|
+
}
|
|
20744
|
+
return availableStatistic({
|
|
20745
|
+
beta: covariance / variance,
|
|
20660
20746
|
covariance,
|
|
20661
20747
|
variance,
|
|
20662
20748
|
averagePortfolioReturn,
|
|
20663
20749
|
averageBenchmarkReturn,
|
|
20664
|
-
};
|
|
20750
|
+
}, cohort);
|
|
20665
20751
|
}
|
|
20666
20752
|
/**
|
|
20667
20753
|
* Calculates the total return for a position, respecting position direction
|
|
@@ -20745,7 +20831,18 @@ async function calculateAlphaAndBeta$1(tradeBars, benchmarkBars, isShort) {
|
|
|
20745
20831
|
: rawTradeReturns;
|
|
20746
20832
|
// Calculate beta with position-adjusted returns
|
|
20747
20833
|
const beta = calculateBetaFromReturns$1(alignedTradeReturns, alignedBenchmarkReturns);
|
|
20748
|
-
|
|
20834
|
+
// Alpha is the return left over after the benchmark term, so an unknown beta
|
|
20835
|
+
// makes alpha unknown too. Substituting any number here — zero most of all —
|
|
20836
|
+
// would credit the whole benchmark move to the strategy.
|
|
20837
|
+
if (!beta.available) {
|
|
20838
|
+
getLogger().warn(`Alpha unavailable: beta could not be computed (${beta.reason}: ${beta.detail}).`);
|
|
20839
|
+
return {
|
|
20840
|
+
alpha: "N/A",
|
|
20841
|
+
alphaAnnualized: "N/A",
|
|
20842
|
+
beta: "N/A",
|
|
20843
|
+
};
|
|
20844
|
+
}
|
|
20845
|
+
if (!isFinite(beta.value.beta)) {
|
|
20749
20846
|
getLogger().warn("Beta calculation resulted in a non-finite value.");
|
|
20750
20847
|
return {
|
|
20751
20848
|
alpha: "N/A",
|
|
@@ -20756,7 +20853,7 @@ async function calculateAlphaAndBeta$1(tradeBars, benchmarkBars, isShort) {
|
|
|
20756
20853
|
// For short positions, the interpretation of beta changes
|
|
20757
20854
|
// A positive beta on a short means the position moves with the market,
|
|
20758
20855
|
// which is bad for a short. We invert it for consistency.
|
|
20759
|
-
const positionAwareBeta = isShort ? -beta.beta : beta.beta;
|
|
20856
|
+
const positionAwareBeta = isShort ? -beta.value.beta : beta.value.beta;
|
|
20760
20857
|
const avgTradeReturn = alignedTradeReturns.reduce((sum, ret) => sum + ret, 0) /
|
|
20761
20858
|
alignedTradeReturns.length;
|
|
20762
20859
|
const avgBenchmarkReturn = alignedBenchmarkReturns.reduce((sum, ret) => sum + ret, 0) /
|
|
@@ -21375,7 +21472,18 @@ async function calculateAlphaAndBeta(portfolioHistory, benchmarkBars) {
|
|
|
21375
21472
|
const benchmarkAvgReturn = alignedBenchmarkReturns.reduce((sum, ret) => sum + ret, 0) / n;
|
|
21376
21473
|
// **Calculate beta**
|
|
21377
21474
|
const beta = calculateBetaFromReturns(alignedPortfolioReturns, alignedBenchmarkReturns);
|
|
21378
|
-
|
|
21475
|
+
// Alpha is what remains after subtracting the benchmark term, so an unknown
|
|
21476
|
+
// beta leaves alpha unknown. Any numeric stand-in — zero above all — would
|
|
21477
|
+
// attribute the entire benchmark move to the strategy.
|
|
21478
|
+
if (!beta.available) {
|
|
21479
|
+
getLogger().warn(`Alpha unavailable: beta could not be computed (${beta.reason}: ${beta.detail}).`);
|
|
21480
|
+
return {
|
|
21481
|
+
alpha: "N/A",
|
|
21482
|
+
alphaAnnualized: "N/A",
|
|
21483
|
+
beta: "N/A",
|
|
21484
|
+
};
|
|
21485
|
+
}
|
|
21486
|
+
if (!isFinite(beta.value.beta)) {
|
|
21379
21487
|
getLogger().warn("Beta calculation resulted in a non-finite value.");
|
|
21380
21488
|
return {
|
|
21381
21489
|
alpha: "N/A",
|
|
@@ -21390,7 +21498,7 @@ async function calculateAlphaAndBeta(portfolioHistory, benchmarkBars) {
|
|
|
21390
21498
|
const tradingDaysPerYear = 252;
|
|
21391
21499
|
const riskFreeRateDaily = riskFreeRateAnnual / tradingDaysPerYear;
|
|
21392
21500
|
const alpha = portfolioAvgReturn -
|
|
21393
|
-
(riskFreeRateDaily + beta.beta * (benchmarkAvgReturn - riskFreeRateDaily));
|
|
21501
|
+
(riskFreeRateDaily + beta.value.beta * (benchmarkAvgReturn - riskFreeRateDaily));
|
|
21394
21502
|
const alphaAnnualized = alpha * tradingDaysPerYear;
|
|
21395
21503
|
if (!isFinite(alphaAnnualized)) {
|
|
21396
21504
|
getLogger().warn("Alpha calculation resulted in a non-finite value.");
|
|
@@ -21403,7 +21511,7 @@ async function calculateAlphaAndBeta(portfolioHistory, benchmarkBars) {
|
|
|
21403
21511
|
return {
|
|
21404
21512
|
alpha: `${(alpha * 100).toFixed(2)}`,
|
|
21405
21513
|
alphaAnnualized: `${(alphaAnnualized * 100).toFixed(2)}`,
|
|
21406
|
-
beta: `${(beta.beta * 100).toFixed(2)}`,
|
|
21514
|
+
beta: `${(beta.value.beta * 100).toFixed(2)}`,
|
|
21407
21515
|
};
|
|
21408
21516
|
}
|
|
21409
21517
|
// **Helper function to calculate daily returns with Unix millisecond timestamps**
|
|
@@ -21626,27 +21734,40 @@ function alignReturnsByDate(portfolioHistory, benchmarkBars) {
|
|
|
21626
21734
|
return { alignedPortfolioReturns, alignedBenchmarkReturns };
|
|
21627
21735
|
}
|
|
21628
21736
|
/**
|
|
21629
|
-
*
|
|
21630
|
-
*
|
|
21631
|
-
*
|
|
21632
|
-
*
|
|
21737
|
+
* Beta of a portfolio against a benchmark, from paired period returns.
|
|
21738
|
+
*
|
|
21739
|
+
* The two series are index-aligned pairs by contract: every mean, covariance
|
|
21740
|
+
* and variance below is taken over the SAME row set. A length mismatch is
|
|
21741
|
+
* therefore reported as invalid input rather than absorbed, because dividing
|
|
21742
|
+
* one series' sum by the other series' length produces a mean of a population
|
|
21743
|
+
* that does not exist — a number with no cohort, which is the failure this
|
|
21744
|
+
* return type exists to make impossible.
|
|
21745
|
+
*
|
|
21746
|
+
* An uncomputable beta is returned as the unavailable branch, never as `0`.
|
|
21747
|
+
* Zero beta is a claim of no market exposure, and downstream alpha attributes
|
|
21748
|
+
* the entire benchmark move to the strategy when it believes that claim.
|
|
21749
|
+
*
|
|
21750
|
+
* @param portfolioReturns - Portfolio period returns.
|
|
21751
|
+
* @param benchmarkReturns - Benchmark period returns, index-aligned to the portfolio.
|
|
21752
|
+
* @returns The beta components with their cohort, or a typed unavailable result.
|
|
21633
21753
|
*/
|
|
21634
21754
|
function calculateBetaFromReturns(portfolioReturns, benchmarkReturns) {
|
|
21635
|
-
const
|
|
21636
|
-
if (
|
|
21637
|
-
getLogger().warn(
|
|
21638
|
-
return {
|
|
21639
|
-
|
|
21640
|
-
|
|
21641
|
-
|
|
21642
|
-
|
|
21643
|
-
|
|
21644
|
-
|
|
21645
|
-
|
|
21646
|
-
|
|
21755
|
+
const requestedCount = portfolioReturns.length;
|
|
21756
|
+
if (portfolioReturns.length !== benchmarkReturns.length) {
|
|
21757
|
+
getLogger().warn(`Beta unavailable: series lengths differ (portfolio ${portfolioReturns.length}, benchmark ${benchmarkReturns.length}).`);
|
|
21758
|
+
return unavailableStatistic("invalid_input", `series lengths differ: portfolio ${portfolioReturns.length}, benchmark ${benchmarkReturns.length}`, sampleCohort(requestedCount, 0));
|
|
21759
|
+
}
|
|
21760
|
+
// Bessel-corrected estimators need one degree of freedom, so two paired
|
|
21761
|
+
// observations is the floor below which no sample variance exists.
|
|
21762
|
+
const MIN_PAIRS_FOR_SAMPLE_VARIANCE = 2;
|
|
21763
|
+
const n = requestedCount;
|
|
21764
|
+
if (n < MIN_PAIRS_FOR_SAMPLE_VARIANCE) {
|
|
21765
|
+
getLogger().warn(`Beta unavailable: ${n} paired returns offered.`);
|
|
21766
|
+
return unavailableStatistic(n === 0 ? "no_usable_samples" : "insufficient_samples", `beta needs at least ${MIN_PAIRS_FOR_SAMPLE_VARIANCE} paired returns; ${n} were offered`, sampleCohort(requestedCount, n));
|
|
21767
|
+
}
|
|
21768
|
+
const cohort = sampleCohort(requestedCount, n);
|
|
21647
21769
|
const averagePortfolioReturn = portfolioReturns.reduce((sum, ret) => sum + ret, 0) / n;
|
|
21648
21770
|
const averageBenchmarkReturn = benchmarkReturns.reduce((sum, ret) => sum + ret, 0) / n;
|
|
21649
|
-
// Calculate covariance and variance
|
|
21650
21771
|
let covariance = 0;
|
|
21651
21772
|
let variance = 0;
|
|
21652
21773
|
for (let i = 0; i < n; i++) {
|
|
@@ -21656,37 +21777,26 @@ function calculateBetaFromReturns(portfolioReturns, benchmarkReturns) {
|
|
|
21656
21777
|
variance += benchmarkDiff ** 2;
|
|
21657
21778
|
}
|
|
21658
21779
|
// Use sample (Bessel-corrected) estimators — divide by (n - 1), not n.
|
|
21659
|
-
|
|
21660
|
-
|
|
21661
|
-
|
|
21662
|
-
|
|
21663
|
-
|
|
21664
|
-
//
|
|
21665
|
-
//
|
|
21666
|
-
// mean
|
|
21667
|
-
// rounding noise yields a meaningless beta. Treat any variance at or
|
|
21668
|
-
// below the summation noise floor — (n * eps * |mean|)^2, the square of
|
|
21669
|
-
// the worst-case naive-summation error — as zero. When the mean is
|
|
21670
|
-
// exactly 0 this reduces to the exact zero check.
|
|
21780
|
+
covariance /= n - 1;
|
|
21781
|
+
variance /= n - 1;
|
|
21782
|
+
// A constant benchmark series can still produce a tiny nonzero variance
|
|
21783
|
+
// because the computed mean differs from the constant by an ulp; dividing
|
|
21784
|
+
// covariance by that rounding noise yields a meaningless beta. Treat any
|
|
21785
|
+
// variance at or below the summation noise floor — (n * eps * |mean|)^2, the
|
|
21786
|
+
// square of the worst-case naive-summation error — as no variance at all.
|
|
21787
|
+
// When the mean is exactly 0 this reduces to the exact zero check.
|
|
21671
21788
|
const varianceNoiseFloor = (n * Number.EPSILON * Math.abs(averageBenchmarkReturn)) ** 2;
|
|
21672
21789
|
if (variance <= varianceNoiseFloor) {
|
|
21673
|
-
getLogger().warn("
|
|
21674
|
-
return {
|
|
21675
|
-
beta: 0,
|
|
21676
|
-
covariance,
|
|
21677
|
-
variance,
|
|
21678
|
-
averagePortfolioReturn,
|
|
21679
|
-
averageBenchmarkReturn,
|
|
21680
|
-
};
|
|
21790
|
+
getLogger().warn("Beta unavailable: benchmark variance is zero or below the floating-point noise floor.");
|
|
21791
|
+
return unavailableStatistic("degenerate_population", `benchmark variance ${variance} is at or below the summation noise floor ${varianceNoiseFloor}; beta is undefined`, cohort);
|
|
21681
21792
|
}
|
|
21682
|
-
|
|
21683
|
-
|
|
21684
|
-
beta,
|
|
21793
|
+
return availableStatistic({
|
|
21794
|
+
beta: covariance / variance,
|
|
21685
21795
|
covariance,
|
|
21686
21796
|
variance,
|
|
21687
21797
|
averagePortfolioReturn,
|
|
21688
21798
|
averageBenchmarkReturn,
|
|
21689
|
-
};
|
|
21799
|
+
}, cohort);
|
|
21690
21800
|
}
|
|
21691
21801
|
/**
|
|
21692
21802
|
* Calculates the information ratio of the portfolio compared to a benchmark.
|
|
@@ -22165,7 +22275,14 @@ var riskNs = /*#__PURE__*/Object.freeze({
|
|
|
22165
22275
|
* Conventions:
|
|
22166
22276
|
* - tradePnls / tradeReturns is an array of per-trade realised P&L or return
|
|
22167
22277
|
* (positive = win, negative = loss, zero = breakeven).
|
|
22168
|
-
* -
|
|
22278
|
+
* - Every statistic here is a ratio or a mean over a WINDOW, so every one is
|
|
22279
|
+
* returned as a {@link SampleStatistic}: the value cannot be read without the
|
|
22280
|
+
* `sampleCount` it was taken over and the `coverage` of the window that was
|
|
22281
|
+
* asked for. A hit-rate is a different claim on 5 trades than on 500, and a
|
|
22282
|
+
* window that could only be half-filled is a different cohort from a full
|
|
22283
|
+
* one — a caller holding a bare number can tell neither apart.
|
|
22284
|
+
* - A window that cannot support the statistic returns the unavailable branch
|
|
22285
|
+
* with a reason, never a numeric stand-in. Zero is a measurement.
|
|
22169
22286
|
* - All public functions reject non-finite inputs (NaN, Infinity) by throwing.
|
|
22170
22287
|
* Callers must pre-validate or filter their inputs.
|
|
22171
22288
|
*/
|
|
@@ -22181,21 +22298,38 @@ function assertFiniteArray(name, arr) {
|
|
|
22181
22298
|
}
|
|
22182
22299
|
}
|
|
22183
22300
|
}
|
|
22301
|
+
/**
|
|
22302
|
+
* Report a window that holds fewer trades than it asked for.
|
|
22303
|
+
*
|
|
22304
|
+
* Shared so every rolling function describes a short window the same way — the
|
|
22305
|
+
* cohort is `(requested = windowSize, sampled = what exists)`, which is the
|
|
22306
|
+
* pair a caller needs to distinguish a warm-up from a data gap.
|
|
22307
|
+
*
|
|
22308
|
+
* @param name - The calling function, for the detail string.
|
|
22309
|
+
* @param available - Trades actually present.
|
|
22310
|
+
* @param windowSize - Trades the window asked for.
|
|
22311
|
+
* @returns The unavailable branch describing the short window.
|
|
22312
|
+
*/
|
|
22313
|
+
function insufficientWindow(name, available, windowSize) {
|
|
22314
|
+
return unavailableStatistic("insufficient_samples", `${name}: window of ${windowSize} requested, only ${available} trades available`, sampleCohort(windowSize, available));
|
|
22315
|
+
}
|
|
22184
22316
|
/**
|
|
22185
22317
|
* Rolling expectancy: mean P&L over the most-recent `windowSize` trades.
|
|
22186
22318
|
*
|
|
22187
22319
|
* @param tradePnls - Array of per-trade realised P&L values.
|
|
22188
22320
|
* @param windowSize - Number of most-recent trades to include. Must be a positive integer.
|
|
22189
|
-
* @returns Mean P&L of the last `windowSize` trades
|
|
22321
|
+
* @returns Mean P&L of the last `windowSize` trades with its cohort, or a typed
|
|
22322
|
+
* unavailable result when fewer than `windowSize` trades exist.
|
|
22190
22323
|
* @throws When `windowSize` is not a positive integer or any input is non-finite.
|
|
22191
22324
|
*/
|
|
22192
22325
|
function calculateRollingExpectancy(tradePnls, windowSize) {
|
|
22193
22326
|
assertWindowSize("calculateRollingExpectancy", windowSize);
|
|
22194
|
-
if (tradePnls.length < windowSize)
|
|
22195
|
-
return
|
|
22327
|
+
if (tradePnls.length < windowSize) {
|
|
22328
|
+
return insufficientWindow("calculateRollingExpectancy", tradePnls.length, windowSize);
|
|
22329
|
+
}
|
|
22196
22330
|
assertFiniteArray("calculateRollingExpectancy", tradePnls);
|
|
22197
22331
|
const slice = tradePnls.slice(-windowSize);
|
|
22198
|
-
return slice.reduce((a, b) => a + b, 0) / windowSize;
|
|
22332
|
+
return availableStatistic(slice.reduce((a, b) => a + b, 0) / windowSize, sampleCohort(windowSize, windowSize));
|
|
22199
22333
|
}
|
|
22200
22334
|
/**
|
|
22201
22335
|
* Rolling hit-rate: fraction of strictly-positive P&L trades in the most-recent
|
|
@@ -22203,42 +22337,53 @@ function calculateRollingExpectancy(tradePnls, windowSize) {
|
|
|
22203
22337
|
*
|
|
22204
22338
|
* @param tradePnls - Array of per-trade realised P&L values.
|
|
22205
22339
|
* @param windowSize - Number of most-recent trades to include. Must be a positive integer.
|
|
22206
|
-
* @returns Fraction of winning trades in the window
|
|
22340
|
+
* @returns Fraction of winning trades in the window with its cohort, or a typed
|
|
22341
|
+
* unavailable result when fewer than `windowSize` trades exist.
|
|
22207
22342
|
* @throws When `windowSize` is not a positive integer or any input is non-finite.
|
|
22208
22343
|
*/
|
|
22209
22344
|
function calculateRollingHitRate(tradePnls, windowSize) {
|
|
22210
22345
|
assertWindowSize("calculateRollingHitRate", windowSize);
|
|
22211
|
-
if (tradePnls.length < windowSize)
|
|
22212
|
-
return
|
|
22346
|
+
if (tradePnls.length < windowSize) {
|
|
22347
|
+
return insufficientWindow("calculateRollingHitRate", tradePnls.length, windowSize);
|
|
22348
|
+
}
|
|
22213
22349
|
assertFiniteArray("calculateRollingHitRate", tradePnls);
|
|
22214
22350
|
const slice = tradePnls.slice(-windowSize);
|
|
22215
22351
|
const wins = slice.filter((p) => p > 0).length;
|
|
22216
|
-
return wins / windowSize;
|
|
22352
|
+
return availableStatistic(wins / windowSize, sampleCohort(windowSize, windowSize));
|
|
22217
22353
|
}
|
|
22218
22354
|
/**
|
|
22219
22355
|
* Rolling profit factor: sum(wins) / |sum(losses)| over the most-recent `windowSize` trades.
|
|
22220
22356
|
*
|
|
22221
22357
|
* Edge cases:
|
|
22222
|
-
* - no losses and at least one win → +Infinity
|
|
22223
|
-
* - no wins and no losses (all zeros) → 0
|
|
22224
|
-
*
|
|
22358
|
+
* - no losses and at least one win → +Infinity (an unbounded but real ratio)
|
|
22359
|
+
* - no wins and no losses (all zeros) → unavailable: `0 / 0` is undefined, and a
|
|
22360
|
+
* window of breakeven trades has no profit factor rather than a profit factor
|
|
22361
|
+
* of zero
|
|
22362
|
+
* - fewer than windowSize trades → unavailable
|
|
22225
22363
|
*
|
|
22226
22364
|
* @param tradePnls - Array of per-trade realised P&L values.
|
|
22227
22365
|
* @param windowSize - Number of most-recent trades to include. Must be a positive integer.
|
|
22228
|
-
* @returns Profit factor for the rolling window
|
|
22366
|
+
* @returns Profit factor for the rolling window with its cohort, or a typed
|
|
22367
|
+
* unavailable result.
|
|
22229
22368
|
* @throws When `windowSize` is not a positive integer or any input is non-finite.
|
|
22230
22369
|
*/
|
|
22231
22370
|
function calculateRollingProfitFactor(tradePnls, windowSize) {
|
|
22232
22371
|
assertWindowSize("calculateRollingProfitFactor", windowSize);
|
|
22233
|
-
if (tradePnls.length < windowSize)
|
|
22234
|
-
return
|
|
22372
|
+
if (tradePnls.length < windowSize) {
|
|
22373
|
+
return insufficientWindow("calculateRollingProfitFactor", tradePnls.length, windowSize);
|
|
22374
|
+
}
|
|
22235
22375
|
assertFiniteArray("calculateRollingProfitFactor", tradePnls);
|
|
22376
|
+
const cohort = sampleCohort(windowSize, windowSize);
|
|
22236
22377
|
const slice = tradePnls.slice(-windowSize);
|
|
22237
22378
|
const wins = slice.filter((p) => p > 0).reduce((a, b) => a + b, 0);
|
|
22238
22379
|
const losses = slice.filter((p) => p < 0).reduce((a, b) => a + Math.abs(b), 0);
|
|
22239
|
-
if (losses === 0)
|
|
22240
|
-
|
|
22241
|
-
|
|
22380
|
+
if (losses === 0) {
|
|
22381
|
+
if (wins > 0) {
|
|
22382
|
+
return availableStatistic(Number.POSITIVE_INFINITY, cohort);
|
|
22383
|
+
}
|
|
22384
|
+
return unavailableStatistic("degenerate_population", `calculateRollingProfitFactor: window of ${windowSize} contains neither wins nor losses; the ratio is undefined`, cohort);
|
|
22385
|
+
}
|
|
22386
|
+
return availableStatistic(wins / losses, cohort);
|
|
22242
22387
|
}
|
|
22243
22388
|
/**
|
|
22244
22389
|
* Rolling Sortino: delegate to `calculateSortino` over the most-recent `windowSize` returns.
|
|
@@ -22246,34 +22391,58 @@ function calculateRollingProfitFactor(tradePnls, windowSize) {
|
|
|
22246
22391
|
* @param tradeReturns - Array of per-trade return values.
|
|
22247
22392
|
* @param windowSize - Number of most-recent trades to include. Must be a positive integer.
|
|
22248
22393
|
* @param riskFreeRate - Risk-free rate to subtract from returns (default 0).
|
|
22249
|
-
* @returns Sortino ratio for the rolling window
|
|
22394
|
+
* @returns Sortino ratio for the rolling window with its cohort, or a typed
|
|
22395
|
+
* unavailable result.
|
|
22250
22396
|
* @throws When `windowSize` is not a positive integer or any input is non-finite.
|
|
22251
22397
|
*/
|
|
22252
22398
|
function calculateRollingSortino(tradeReturns, windowSize, riskFreeRate = 0) {
|
|
22253
22399
|
assertWindowSize("calculateRollingSortino", windowSize);
|
|
22254
|
-
if (tradeReturns.length < windowSize)
|
|
22255
|
-
return
|
|
22400
|
+
if (tradeReturns.length < windowSize) {
|
|
22401
|
+
return insufficientWindow("calculateRollingSortino", tradeReturns.length, windowSize);
|
|
22402
|
+
}
|
|
22256
22403
|
assertFiniteArray("calculateRollingSortino", tradeReturns);
|
|
22257
|
-
|
|
22404
|
+
const cohort = sampleCohort(windowSize, windowSize);
|
|
22405
|
+
const sortino = calculateSortino(tradeReturns.slice(-windowSize), riskFreeRate);
|
|
22406
|
+
if (sortino === null) {
|
|
22407
|
+
// `calculateSortino` returns null only for a window it cannot form a
|
|
22408
|
+
// dispersion over — fewer than two samples. That is a property of the
|
|
22409
|
+
// window, so it is reported as one rather than as a ratio of zero.
|
|
22410
|
+
return unavailableStatistic("insufficient_samples", `calculateRollingSortino: window of ${windowSize} cannot support a dispersion estimate`, cohort);
|
|
22411
|
+
}
|
|
22412
|
+
return availableStatistic(sortino, cohort);
|
|
22258
22413
|
}
|
|
22259
22414
|
/**
|
|
22260
22415
|
* Z-score of live-expectancy vs backtest-expectancy, scaled by the backtest stddev.
|
|
22261
22416
|
* Positive Z = live outperforming; negative Z = live underperforming.
|
|
22262
22417
|
*
|
|
22263
|
-
*
|
|
22418
|
+
* The live expectancy is taken as a {@link SampleStatistic} rather than a bare
|
|
22419
|
+
* number so the z-score inherits the cohort it was actually derived from. A
|
|
22420
|
+
* z-score is a statement about how surprising a sample mean is, and how
|
|
22421
|
+
* surprising it is depends entirely on how many trades produced it — quoting
|
|
22422
|
+
* the z alone is the exact substitution this type exists to block. An
|
|
22423
|
+
* unavailable live expectancy yields an unavailable z, because there is no
|
|
22424
|
+
* mean to compare.
|
|
22425
|
+
*
|
|
22426
|
+
* @param liveExpectancy - Mean P&L per trade in the live window, with its cohort.
|
|
22264
22427
|
* @param backtestExpectancy - Mean P&L per trade from the calibration backtest.
|
|
22265
22428
|
* @param backtestStddev - Stddev of per-trade P&L in the backtest. Must be > 0.
|
|
22266
|
-
* @returns Z-score measuring divergence
|
|
22267
|
-
* @throws When
|
|
22429
|
+
* @returns Z-score measuring live-vs-backtest divergence, carrying the live cohort.
|
|
22430
|
+
* @throws When the backtest inputs are non-finite or `backtestStddev` is not positive.
|
|
22268
22431
|
*/
|
|
22269
22432
|
function calculateBacktestDivergenceZ(liveExpectancy, backtestExpectancy, backtestStddev) {
|
|
22270
|
-
if (!Number.isFinite(
|
|
22433
|
+
if (!Number.isFinite(backtestExpectancy) || !Number.isFinite(backtestStddev)) {
|
|
22271
22434
|
throw new Error("calculateBacktestDivergenceZ: inputs must be finite numbers");
|
|
22272
22435
|
}
|
|
22273
22436
|
if (backtestStddev <= 0) {
|
|
22274
22437
|
throw new Error("calculateBacktestDivergenceZ: stddev must be > 0");
|
|
22275
22438
|
}
|
|
22276
|
-
|
|
22439
|
+
if (!liveExpectancy.available) {
|
|
22440
|
+
return unavailableStatistic(liveExpectancy.reason, `calculateBacktestDivergenceZ: live expectancy unavailable (${liveExpectancy.detail})`, sampleCohort(liveExpectancy.requestedCount, liveExpectancy.sampleCount));
|
|
22441
|
+
}
|
|
22442
|
+
if (!Number.isFinite(liveExpectancy.value)) {
|
|
22443
|
+
throw new Error("calculateBacktestDivergenceZ: inputs must be finite numbers");
|
|
22444
|
+
}
|
|
22445
|
+
return availableStatistic((liveExpectancy.value - backtestExpectancy) / backtestStddev, sampleCohort(liveExpectancy.requestedCount, liveExpectancy.sampleCount));
|
|
22277
22446
|
}
|
|
22278
22447
|
|
|
22279
22448
|
var strategyNs = /*#__PURE__*/Object.freeze({
|
|
@@ -62317,13 +62486,18 @@ const DEFAULT_PAGINATION_DELAY_MS = 300;
|
|
|
62317
62486
|
*/
|
|
62318
62487
|
const MAX_ORDERS_PER_REQUEST = 500;
|
|
62319
62488
|
/**
|
|
62320
|
-
* Order statuses that are considered "open"
|
|
62489
|
+
* Order statuses that are considered "open".
|
|
62490
|
+
*
|
|
62491
|
+
* `held` is included because a held conditional leg (a bracket's stop-loss,
|
|
62492
|
+
* say) is a working order resting at the broker, returned by Alpaca's own
|
|
62493
|
+
* `status=open` listing and cancelable like any other open order.
|
|
62321
62494
|
*/
|
|
62322
62495
|
const OPEN_ORDER_STATUSES = [
|
|
62323
62496
|
"new",
|
|
62324
62497
|
"accepted",
|
|
62325
62498
|
"pending_new",
|
|
62326
62499
|
"accepted_for_bidding",
|
|
62500
|
+
"held",
|
|
62327
62501
|
"partially_filled",
|
|
62328
62502
|
];
|
|
62329
62503
|
/**
|
|
@@ -62331,13 +62505,15 @@ const OPEN_ORDER_STATUSES = [
|
|
|
62331
62505
|
*/
|
|
62332
62506
|
const FILLED_ORDER_STATUSES = ["filled"];
|
|
62333
62507
|
/**
|
|
62334
|
-
* Order statuses that can still potentially be filled
|
|
62508
|
+
* Order statuses that can still potentially be filled. A `held` leg fills once
|
|
62509
|
+
* its parent fills or its trigger is met.
|
|
62335
62510
|
*/
|
|
62336
62511
|
const FILLABLE_ORDER_STATUSES = [
|
|
62337
62512
|
"new",
|
|
62338
62513
|
"accepted",
|
|
62339
62514
|
"pending_new",
|
|
62340
62515
|
"accepted_for_bidding",
|
|
62516
|
+
"held",
|
|
62341
62517
|
"partially_filled",
|
|
62342
62518
|
];
|
|
62343
62519
|
/**
|
|
@@ -72145,11 +72321,35 @@ class CircuitBreakerRegistry {
|
|
|
72145
72321
|
* Register that an attempt is starting, so half-open probes stay bounded.
|
|
72146
72322
|
*
|
|
72147
72323
|
* @param routeKey The route's stable key.
|
|
72148
|
-
* @returns
|
|
72324
|
+
* @returns Whether the attempt took a half-open probe slot. A caller holding
|
|
72325
|
+
* one must end the attempt with {@link onSuccess}, {@link onFailure} or
|
|
72326
|
+
* {@link onAttemptAbandoned}, or the slot is never returned.
|
|
72149
72327
|
*/
|
|
72150
72328
|
onAttemptStart(routeKey) {
|
|
72151
72329
|
if (this.stateOf(routeKey) === "half-open") {
|
|
72152
72330
|
this.recordFor(routeKey).probesInFlight += 1;
|
|
72331
|
+
return true;
|
|
72332
|
+
}
|
|
72333
|
+
return false;
|
|
72334
|
+
}
|
|
72335
|
+
/**
|
|
72336
|
+
* Return a half-open probe slot whose attempt ended without a verdict.
|
|
72337
|
+
*
|
|
72338
|
+
* A probe that never tested the provider — refused by the client's own
|
|
72339
|
+
* pacing guard, cancelled by its caller, or found to be the wrong leg for the
|
|
72340
|
+
* request — says nothing about whether the route has recovered, so neither a
|
|
72341
|
+
* success nor a failure is recorded. The slot must still come back. Without
|
|
72342
|
+
* it the half-open route admits no further probe, no probe can ever close or
|
|
72343
|
+
* re-open the breaker, and the route stays excluded for the life of the
|
|
72344
|
+
* process while its traffic is quietly served by the next leg.
|
|
72345
|
+
*
|
|
72346
|
+
* @param routeKey The route's stable key.
|
|
72347
|
+
* @returns void
|
|
72348
|
+
*/
|
|
72349
|
+
onAttemptAbandoned(routeKey) {
|
|
72350
|
+
const record = this.records.get(routeKey);
|
|
72351
|
+
if (record !== undefined && record.probesInFlight > 0) {
|
|
72352
|
+
record.probesInFlight -= 1;
|
|
72153
72353
|
}
|
|
72154
72354
|
}
|
|
72155
72355
|
/**
|
|
@@ -72420,11 +72620,13 @@ var defaults$1 = {
|
|
|
72420
72620
|
var providers$1 = {
|
|
72421
72621
|
anthropic: {
|
|
72422
72622
|
basis: "conservative-default",
|
|
72623
|
+
scope: "model",
|
|
72624
|
+
scope_source: "https://platform.claude.com/docs/en/api/rate-limits",
|
|
72423
72625
|
requests_per_minute: 120,
|
|
72424
72626
|
max_concurrent: 12,
|
|
72425
72627
|
acquire_timeout_ms: 15000,
|
|
72426
72628
|
source: null,
|
|
72427
|
-
note: "
|
|
72629
|
+
note: "The unit is per model: the scope_source states 'Rate limits are applied separately for each model; therefore you can use different models up to their respective limits simultaneously', measured as requests, input tokens and output tokens per minute for each model class, with no concurrency ceiling. Each model therefore gets its own guard, so traffic on one model class (an Opus incumbent serving background aliases) cannot refuse calls to another (the Haiku incumbent of the hot-path alias). The NUMBERS stay conservative-default: the organisation's usage tier sets the real ceilings and is read only from the Claude Console rate-limits page, which has not been transcribed. The lowest standard tier listed at the source allows 1,000 requests per minute per model; organisations with limited history can start on an evaluation tier below that, which is why these values are held well under it. Transcribe the tier's limits (W3-07) and set basis to published."
|
|
72428
72630
|
},
|
|
72429
72631
|
openai: {
|
|
72430
72632
|
basis: "conservative-default",
|
|
@@ -72443,12 +72645,14 @@ var providers$1 = {
|
|
|
72443
72645
|
note: "Raised 2026-09-15 after the 4-concurrent ceiling was measured starving the live equity decision path: the alias chain reported 'exhausted its fallback chain' with every leg skipped by this guard (deepinfra primary+secondary and the anthropic incumbent), 173 of 473 signal-coordination calls failed (36.6%), and decisions were lost outright. Corroborating evidence at the time: ZERO 429s observed on any provider, the engine's own global fan-out gate permits 100 concurrent with 17 active, and 877 signal-analysis calls had been admitted to that gate. Concurrency and RPM are raised TOGETHER because they bind in series - lifting max_concurrent alone would only move the bottleneck to the token bucket. STILL conservative-default, NOT published: no provider console was read for these numbers, so they remain a deliberate under-estimate of an unknown ceiling. Transcribe the real tier limits (W3-07) and set basis to published."
|
|
72444
72646
|
},
|
|
72445
72647
|
deepinfra: {
|
|
72446
|
-
basis: "
|
|
72648
|
+
basis: "published",
|
|
72649
|
+
scope: "model",
|
|
72447
72650
|
requests_per_minute: 240,
|
|
72448
|
-
|
|
72651
|
+
requests_per_minute_basis: "conservative-default",
|
|
72652
|
+
max_concurrent: 200,
|
|
72449
72653
|
acquire_timeout_ms: 15000,
|
|
72450
|
-
source:
|
|
72451
|
-
note: "
|
|
72654
|
+
source: "https://docs.deepinfra.com/account/rate-limits",
|
|
72655
|
+
note: "Transcribed 2026-09-23 from the source, which states 'Every account has a default limit of 200 concurrent requests per model', that two models queried simultaneously allow 400 in total (200 per model), and that 'The rate limit is on concurrent requests, not per-minute volume.' Concurrency is therefore the bound DeepInfra enforces, and it is enforced per MODEL, so each model gets its own guard at 200. One guard shared by the whole account enforced a ceiling DeepInfra does not impose, and because an alias's primary and secondary are both served from this account, it refused the secondary exactly when the primary's queue was full. DeepInfra publishes no per-minute ceiling, so requests_per_minute is the client's own pacing backstop (requests_per_minute_basis: conservative-default), keyed per model like the bound DeepInfra does enforce. Exceeding the ceiling returns HTTP 429, and a very busy model can return 429 below it. The ceiling belongs to the account and this guard to one process, so every process calling the same model through the same account shares the 200."
|
|
72452
72656
|
},
|
|
72453
72657
|
fireworks: {
|
|
72454
72658
|
basis: "conservative-default",
|
|
@@ -72498,7 +72702,10 @@ var limitsConfig = {
|
|
|
72498
72702
|
* provider's circuit breaker, fail over to a more expensive leg, and keep doing
|
|
72499
72703
|
* so — converting a self-inflicted pacing problem into a permanent routing
|
|
72500
72704
|
* change nobody chose. Pacing at the client is what keeps the breaker measuring
|
|
72501
|
-
* the provider rather than measuring us.
|
|
72705
|
+
* the provider rather than measuring us. The same reasoning bounds the guard
|
|
72706
|
+
* from the other side: a client held far BELOW the provider's ceiling refuses
|
|
72707
|
+
* calls the provider would have served, and the chain answers those refusals by
|
|
72708
|
+
* failing over — the same unchosen routing change, arrived at by under-driving.
|
|
72502
72709
|
*
|
|
72503
72710
|
* Two distinct bounds are applied because they fail differently. The rate bound
|
|
72504
72711
|
* (requests per minute) protects the provider's published ceiling. The
|
|
@@ -72507,8 +72714,18 @@ var limitsConfig = {
|
|
|
72507
72714
|
* every one of them blows its latency budget and the fan-out produces a hundred
|
|
72508
72715
|
* timeouts instead of a queue.
|
|
72509
72716
|
*
|
|
72510
|
-
*
|
|
72511
|
-
*
|
|
72717
|
+
* Each guard is keyed by the unit its provider enforces limits in. A provider
|
|
72718
|
+
* that publishes its ceilings per model gets one independent guard per model.
|
|
72719
|
+
* Sharing one guard across its models would enforce a ceiling the provider does
|
|
72720
|
+
* not impose, and — when a chain's primary and secondary are served by the same
|
|
72721
|
+
* provider — would refuse the secondary at exactly the moment the primary's
|
|
72722
|
+
* queue is full, so the fallback that exists for that moment is never reached.
|
|
72723
|
+
*
|
|
72724
|
+
* Limits live in `provider-limits.json` rather than in code, each beside the
|
|
72725
|
+
* source it was transcribed from, so a published ceiling and a conservative
|
|
72726
|
+
* guess can never be mistaken for one another in review. The file is bundled
|
|
72727
|
+
* at build time: changing a limit is a release of this package, not a runtime
|
|
72728
|
+
* switch.
|
|
72512
72729
|
*
|
|
72513
72730
|
* @module llm/rate-guard
|
|
72514
72731
|
*/
|
|
@@ -72548,64 +72765,141 @@ class RateGuardTimeoutError extends Error {
|
|
|
72548
72765
|
provider;
|
|
72549
72766
|
/** Which of the two bounds the caller waited on. */
|
|
72550
72767
|
bound;
|
|
72768
|
+
/** The model whose guard refused the call, when the provider's limits apply per model. */
|
|
72769
|
+
modelId;
|
|
72770
|
+
/** Whether the caller stopped waiting before the guard's own wait budget ran out. */
|
|
72771
|
+
abandoned;
|
|
72551
72772
|
/**
|
|
72552
72773
|
* @param provider The provider.
|
|
72553
72774
|
* @param bound Which bound was binding.
|
|
72554
|
-
* @param waitedMs How long the caller
|
|
72775
|
+
* @param waitedMs How long the caller was prepared to wait.
|
|
72776
|
+
* @param detail The model, and whether the caller left before the budget ran out.
|
|
72555
72777
|
*/
|
|
72556
|
-
constructor(provider, bound, waitedMs) {
|
|
72557
|
-
|
|
72778
|
+
constructor(provider, bound, waitedMs, detail = {}) {
|
|
72779
|
+
const guard = detail.modelId === undefined
|
|
72780
|
+
? `client-side ${bound} guard for provider "${provider}"`
|
|
72781
|
+
: `client-side ${bound} guard for provider "${provider}", model "${detail.modelId}",`;
|
|
72782
|
+
const outcome = detail.abandoned === true
|
|
72783
|
+
? `was left by its caller before it could admit the call (wait budget ${waitedMs} ms)`
|
|
72784
|
+
: `did not admit the call within ${waitedMs} ms`;
|
|
72785
|
+
super(`${guard} ${outcome}. ` +
|
|
72558
72786
|
"The provider was never contacted, so this says nothing about its health.");
|
|
72559
72787
|
this.name = "RateGuardTimeoutError";
|
|
72560
72788
|
this.provider = provider;
|
|
72561
72789
|
this.bound = bound;
|
|
72790
|
+
this.modelId = detail.modelId;
|
|
72791
|
+
this.abandoned = detail.abandoned === true;
|
|
72562
72792
|
}
|
|
72563
72793
|
}
|
|
72794
|
+
/**
|
|
72795
|
+
* The guard a call is held by.
|
|
72796
|
+
*
|
|
72797
|
+
* A call to a per-model provider that names no model shares one provider-wide
|
|
72798
|
+
* guard held at the per-model ceiling. That is never looser than the limit of
|
|
72799
|
+
* any single model it might reach, so the fallback errs toward pacing.
|
|
72800
|
+
*
|
|
72801
|
+
* @param provider The provider key.
|
|
72802
|
+
* @param modelId The model the call is addressed to, if known.
|
|
72803
|
+
* @returns The guard's identity.
|
|
72804
|
+
*/
|
|
72805
|
+
function guardIdentity(provider, modelId) {
|
|
72806
|
+
const perModel = limitsFor(provider).scope === "model" && modelId !== undefined && modelId.length > 0;
|
|
72807
|
+
return perModel
|
|
72808
|
+
? { key: `${provider}/${modelId}`, provider, modelId }
|
|
72809
|
+
: { key: provider, provider, modelId: undefined };
|
|
72810
|
+
}
|
|
72564
72811
|
/**
|
|
72565
72812
|
* A counting semaphore bounding simultaneous in-flight calls.
|
|
72566
72813
|
*
|
|
72567
|
-
* Written here rather than pulled from a dependency because
|
|
72568
|
-
*
|
|
72569
|
-
*
|
|
72570
|
-
*
|
|
72814
|
+
* Written here rather than pulled from a dependency because the waiting
|
|
72815
|
+
* behaviour is the point. A waiter that times out must be removed from the
|
|
72816
|
+
* queue, or a burst of abandoned callers permanently consumes the permits that
|
|
72817
|
+
* later callers need. And a waiter whose caller has stopped waiting must leave
|
|
72818
|
+
* at once: left queued, it holds its caller until the wait budget expires and
|
|
72819
|
+
* is then handed a permit it can only waste.
|
|
72571
72820
|
*/
|
|
72572
72821
|
class ConcurrencyGate {
|
|
72573
72822
|
inFlight = 0;
|
|
72574
72823
|
waiters = [];
|
|
72575
72824
|
limit;
|
|
72576
|
-
|
|
72825
|
+
identity;
|
|
72577
72826
|
/**
|
|
72578
|
-
* @param
|
|
72827
|
+
* @param identity The guard this gate implements.
|
|
72579
72828
|
* @param limit Maximum simultaneous in-flight calls.
|
|
72580
72829
|
*/
|
|
72581
|
-
constructor(
|
|
72582
|
-
this.
|
|
72830
|
+
constructor(identity, limit) {
|
|
72831
|
+
this.identity = identity;
|
|
72583
72832
|
this.limit = limit;
|
|
72584
72833
|
}
|
|
72585
72834
|
/**
|
|
72586
72835
|
* Wait for a permit.
|
|
72587
72836
|
*
|
|
72588
72837
|
* @param timeoutMs How long the caller is willing to queue.
|
|
72838
|
+
* @param signal The caller's cancellation; firing it takes the caller out of the queue.
|
|
72589
72839
|
* @returns A release function the caller must invoke exactly once.
|
|
72840
|
+
* @throws {RateGuardTimeoutError} When no permit was granted in time, or the caller stopped waiting.
|
|
72590
72841
|
*/
|
|
72591
|
-
async acquire(timeoutMs) {
|
|
72842
|
+
async acquire(timeoutMs, signal) {
|
|
72843
|
+
if (signal?.aborted === true) {
|
|
72844
|
+
// Nobody is waiting for this answer. Taking a permit for it would spend
|
|
72845
|
+
// capacity a live caller needs on a call that can only be torn down.
|
|
72846
|
+
throw this.refusal(timeoutMs, true);
|
|
72847
|
+
}
|
|
72592
72848
|
if (this.inFlight < this.limit) {
|
|
72593
72849
|
this.inFlight += 1;
|
|
72594
72850
|
return () => this.release();
|
|
72595
72851
|
}
|
|
72596
72852
|
await new Promise((resolve, reject) => {
|
|
72597
|
-
|
|
72598
|
-
|
|
72599
|
-
|
|
72600
|
-
|
|
72853
|
+
/**
|
|
72854
|
+
* Take this waiter out of the queue and refuse it. A waiter that `release`
|
|
72855
|
+
* has already admitted is no longer queued; it now holds a permit, which
|
|
72856
|
+
* its call returns, so there is nothing to undo here.
|
|
72857
|
+
*
|
|
72858
|
+
* @param abandoned Whether the caller left before the wait budget ran out.
|
|
72859
|
+
* @returns void
|
|
72860
|
+
*/
|
|
72861
|
+
const leave = (abandoned) => {
|
|
72862
|
+
const index = this.waiters.indexOf(waiter);
|
|
72863
|
+
if (index === -1) {
|
|
72864
|
+
return;
|
|
72601
72865
|
}
|
|
72602
|
-
|
|
72866
|
+
this.waiters.splice(index, 1);
|
|
72867
|
+
clearTimeout(timer);
|
|
72868
|
+
signal?.removeEventListener("abort", onAbort);
|
|
72869
|
+
reject(this.refusal(timeoutMs, abandoned));
|
|
72870
|
+
};
|
|
72871
|
+
const onAbort = () => {
|
|
72872
|
+
leave(true);
|
|
72873
|
+
};
|
|
72874
|
+
const timer = setTimeout(() => {
|
|
72875
|
+
leave(false);
|
|
72603
72876
|
}, timeoutMs);
|
|
72604
|
-
|
|
72877
|
+
const waiter = {
|
|
72878
|
+
admit: () => {
|
|
72879
|
+
clearTimeout(timer);
|
|
72880
|
+
signal?.removeEventListener("abort", onAbort);
|
|
72881
|
+
resolve();
|
|
72882
|
+
},
|
|
72883
|
+
};
|
|
72884
|
+
this.waiters.push(waiter);
|
|
72885
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
72605
72886
|
});
|
|
72606
72887
|
this.inFlight += 1;
|
|
72607
72888
|
return () => this.release();
|
|
72608
72889
|
}
|
|
72890
|
+
/**
|
|
72891
|
+
* Build the refusal for a caller this gate did not admit.
|
|
72892
|
+
*
|
|
72893
|
+
* @param timeoutMs The wait budget the caller had.
|
|
72894
|
+
* @param abandoned Whether the caller left before the budget ran out.
|
|
72895
|
+
* @returns The error to raise.
|
|
72896
|
+
*/
|
|
72897
|
+
refusal(timeoutMs, abandoned) {
|
|
72898
|
+
return new RateGuardTimeoutError(this.identity.provider, "concurrency", timeoutMs, {
|
|
72899
|
+
modelId: this.identity.modelId,
|
|
72900
|
+
abandoned,
|
|
72901
|
+
});
|
|
72902
|
+
}
|
|
72609
72903
|
/**
|
|
72610
72904
|
* Return a permit and admit the next waiter.
|
|
72611
72905
|
*
|
|
@@ -72615,8 +72909,7 @@ class ConcurrencyGate {
|
|
|
72615
72909
|
this.inFlight -= 1;
|
|
72616
72910
|
const next = this.waiters.shift();
|
|
72617
72911
|
if (next !== undefined) {
|
|
72618
|
-
|
|
72619
|
-
next.resolve();
|
|
72912
|
+
next.admit();
|
|
72620
72913
|
}
|
|
72621
72914
|
}
|
|
72622
72915
|
/**
|
|
@@ -72632,44 +72925,47 @@ class ConcurrencyGate {
|
|
|
72632
72925
|
return this.waiters.length;
|
|
72633
72926
|
}
|
|
72634
72927
|
}
|
|
72635
|
-
/**
|
|
72928
|
+
/** Guards, created on first use and shared process-wide, keyed by {@link GuardIdentity.key}. */
|
|
72636
72929
|
const rateLimiters = new Map();
|
|
72637
72930
|
const concurrencyGates = new Map();
|
|
72931
|
+
const guardIdentities = new Map();
|
|
72638
72932
|
/**
|
|
72639
|
-
* The rate limiter for a
|
|
72933
|
+
* The rate limiter for a guard.
|
|
72640
72934
|
*
|
|
72641
72935
|
* Shared process-wide rather than per-call-site, because the provider's ceiling
|
|
72642
72936
|
* applies to the process as a whole. Per-call-site limiters would each stay
|
|
72643
72937
|
* under the ceiling while their sum sailed past it.
|
|
72644
72938
|
*
|
|
72645
|
-
* @param
|
|
72939
|
+
* @param identity The guard.
|
|
72646
72940
|
* @returns Its limiter.
|
|
72647
72941
|
*/
|
|
72648
|
-
function rateLimiterFor(
|
|
72649
|
-
let limiter = rateLimiters.get(
|
|
72942
|
+
function rateLimiterFor(identity) {
|
|
72943
|
+
let limiter = rateLimiters.get(identity.key);
|
|
72650
72944
|
if (limiter === undefined) {
|
|
72651
|
-
const limits = limitsFor(provider);
|
|
72945
|
+
const limits = limitsFor(identity.provider);
|
|
72652
72946
|
limiter = new TokenBucketRateLimiter({
|
|
72653
72947
|
maxTokens: limits.requests_per_minute,
|
|
72654
72948
|
refillRate: limits.requests_per_minute / SECONDS_PER_MINUTE,
|
|
72655
|
-
label: `llm:${
|
|
72949
|
+
label: `llm:${identity.key}`,
|
|
72656
72950
|
timeoutMs: limits.acquire_timeout_ms,
|
|
72657
72951
|
});
|
|
72658
|
-
rateLimiters.set(
|
|
72952
|
+
rateLimiters.set(identity.key, limiter);
|
|
72953
|
+
guardIdentities.set(identity.key, identity);
|
|
72659
72954
|
}
|
|
72660
72955
|
return limiter;
|
|
72661
72956
|
}
|
|
72662
72957
|
/**
|
|
72663
|
-
* The concurrency gate for a
|
|
72958
|
+
* The concurrency gate for a guard.
|
|
72664
72959
|
*
|
|
72665
|
-
* @param
|
|
72960
|
+
* @param identity The guard.
|
|
72666
72961
|
* @returns Its gate.
|
|
72667
72962
|
*/
|
|
72668
|
-
function concurrencyGateFor(
|
|
72669
|
-
let gate = concurrencyGates.get(
|
|
72963
|
+
function concurrencyGateFor(identity) {
|
|
72964
|
+
let gate = concurrencyGates.get(identity.key);
|
|
72670
72965
|
if (gate === undefined) {
|
|
72671
|
-
gate = new ConcurrencyGate(
|
|
72672
|
-
concurrencyGates.set(
|
|
72966
|
+
gate = new ConcurrencyGate(identity, limitsFor(identity.provider).max_concurrent);
|
|
72967
|
+
concurrencyGates.set(identity.key, gate);
|
|
72968
|
+
guardIdentities.set(identity.key, identity);
|
|
72673
72969
|
}
|
|
72674
72970
|
return gate;
|
|
72675
72971
|
}
|
|
@@ -72691,21 +72987,26 @@ function concurrencyGateFor(provider) {
|
|
|
72691
72987
|
* @param provider The provider key.
|
|
72692
72988
|
* @param call The work to run once admitted.
|
|
72693
72989
|
* @param maxWaitMs Ceiling on queue time; the configured guard timeout applies when lower.
|
|
72990
|
+
* @param scope The model the call addresses, and the caller's cancellation.
|
|
72694
72991
|
* @returns The call's result.
|
|
72695
|
-
* @throws {RateGuardTimeoutError} When neither bound admitted the call in time
|
|
72992
|
+
* @throws {RateGuardTimeoutError} When neither bound admitted the call in time,
|
|
72993
|
+
* or the caller stopped waiting first.
|
|
72696
72994
|
*/
|
|
72697
|
-
async function withProviderGuards(provider, call, maxWaitMs) {
|
|
72995
|
+
async function withProviderGuards(provider, call, maxWaitMs, scope = {}) {
|
|
72698
72996
|
const limits = limitsFor(provider);
|
|
72997
|
+
const identity = guardIdentity(provider, scope.modelId);
|
|
72699
72998
|
const waitBudgetMs = maxWaitMs === undefined
|
|
72700
72999
|
? limits.acquire_timeout_ms
|
|
72701
73000
|
: Math.min(maxWaitMs, limits.acquire_timeout_ms);
|
|
72702
73001
|
try {
|
|
72703
|
-
await rateLimiterFor(
|
|
73002
|
+
await rateLimiterFor(identity).acquire();
|
|
72704
73003
|
}
|
|
72705
73004
|
catch {
|
|
72706
|
-
throw new RateGuardTimeoutError(provider, "rate", waitBudgetMs
|
|
73005
|
+
throw new RateGuardTimeoutError(provider, "rate", waitBudgetMs, {
|
|
73006
|
+
modelId: identity.modelId,
|
|
73007
|
+
});
|
|
72707
73008
|
}
|
|
72708
|
-
const release = await concurrencyGateFor(
|
|
73009
|
+
const release = await concurrencyGateFor(identity).acquire(waitBudgetMs, scope.signal);
|
|
72709
73010
|
try {
|
|
72710
73011
|
return await call();
|
|
72711
73012
|
}
|
|
@@ -72719,16 +73020,20 @@ async function withProviderGuards(provider, call, maxWaitMs) {
|
|
|
72719
73020
|
/**
|
|
72720
73021
|
* Inspect the guards currently in use.
|
|
72721
73022
|
*
|
|
72722
|
-
* @returns A snapshot per
|
|
73023
|
+
* @returns A snapshot per guard that has been used, sorted by guard key.
|
|
72723
73024
|
*/
|
|
72724
73025
|
function guardSnapshots() {
|
|
72725
|
-
|
|
72726
|
-
|
|
72727
|
-
|
|
72728
|
-
const
|
|
72729
|
-
const
|
|
73026
|
+
return [...guardIdentities.values()]
|
|
73027
|
+
.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
|
|
73028
|
+
.map((identity) => {
|
|
73029
|
+
const limits = limitsFor(identity.provider);
|
|
73030
|
+
const limiter = rateLimiters.get(identity.key);
|
|
73031
|
+
const gate = concurrencyGates.get(identity.key);
|
|
72730
73032
|
return {
|
|
72731
|
-
|
|
73033
|
+
key: identity.key,
|
|
73034
|
+
provider: identity.provider,
|
|
73035
|
+
modelId: identity.modelId,
|
|
73036
|
+
scope: limits.scope ?? "provider",
|
|
72732
73037
|
basis: limits.basis,
|
|
72733
73038
|
requestsPerMinute: limits.requests_per_minute,
|
|
72734
73039
|
maxConcurrent: limits.max_concurrent,
|
|
@@ -72753,6 +73058,105 @@ function resetProviderGuards() {
|
|
|
72753
73058
|
}
|
|
72754
73059
|
rateLimiters.clear();
|
|
72755
73060
|
concurrencyGates.clear();
|
|
73061
|
+
guardIdentities.clear();
|
|
73062
|
+
}
|
|
73063
|
+
|
|
73064
|
+
/**
|
|
73065
|
+
* Interpretation of a model's answer to a structured (JSON) request.
|
|
73066
|
+
*
|
|
73067
|
+
* A JSON request is a promise about the answer's SHAPE, and providers keep it
|
|
73068
|
+
* in different ways. An OpenAI-compatible host given `json_object` constrains
|
|
73069
|
+
* its decoder, so its answer is bare JSON. A provider with no schema-less JSON
|
|
73070
|
+
* mode — Anthropic, reached through the gateway, supports structured output
|
|
73071
|
+
* only against a caller-supplied schema — receives nothing but the prompt's
|
|
73072
|
+
* instructions for a `json` request, and a model following them commonly
|
|
73073
|
+
* returns the object inside one markdown code fence. The fence is presentation,
|
|
73074
|
+
* not content: the object inside it is the answer the model gave.
|
|
73075
|
+
*
|
|
73076
|
+
* So exactly ONE enclosing fence is removed before parsing, and nothing else is
|
|
73077
|
+
* forgiven. Prose before or after the fence, two fenced blocks, a fence that
|
|
73078
|
+
* never closes (a truncated answer), and a fence declaring another language all
|
|
73079
|
+
* still fail. Each of those is an answer whose meaning a parser would have to
|
|
73080
|
+
* guess, and a guessed object is a decision made on data no model produced.
|
|
73081
|
+
*
|
|
73082
|
+
* Content that is not fenced is parsed exactly as it always was: JSON cannot
|
|
73083
|
+
* begin with a backtick, so every answer that parsed before this unwrapping
|
|
73084
|
+
* existed takes the same path and yields the same value.
|
|
73085
|
+
*
|
|
73086
|
+
* @module llm/structured-content
|
|
73087
|
+
*/
|
|
73088
|
+
/**
|
|
73089
|
+
* One markdown fence enclosing the whole answer: an opening line of three
|
|
73090
|
+
* backticks, optionally labelled `json`, then the body, then three closing
|
|
73091
|
+
* backticks, with nothing but whitespace outside them. The body is anchored at
|
|
73092
|
+
* both ends, so an answer holding two fenced blocks captures the text between
|
|
73093
|
+
* them and fails to parse instead of yielding either block.
|
|
73094
|
+
*/
|
|
73095
|
+
const SINGLE_ENCLOSING_JSON_FENCE = /^\s*```(?:json)?[ \t]*\r?\n([\s\S]*?)\r?\n?[ \t]*```\s*$/i;
|
|
73096
|
+
/**
|
|
73097
|
+
* Thrown when a provider answered a structured request with content that does
|
|
73098
|
+
* not parse.
|
|
73099
|
+
*
|
|
73100
|
+
* Carries the usage the provider billed for that answer. The tokens were spent
|
|
73101
|
+
* whether or not the content parsed, and a chain that dropped them would report
|
|
73102
|
+
* a failed attempt as free — understating spend by exactly the calls that went
|
|
73103
|
+
* wrong.
|
|
73104
|
+
*/
|
|
73105
|
+
class LlmResponseFormatError extends Error {
|
|
73106
|
+
/** The format the caller asked for. */
|
|
73107
|
+
responseFormat;
|
|
73108
|
+
/** What the provider billed for the answer that did not parse. */
|
|
73109
|
+
usage;
|
|
73110
|
+
/** Whether the answer sat inside one enclosing fence that was removed before parsing. */
|
|
73111
|
+
fenced;
|
|
73112
|
+
/**
|
|
73113
|
+
* @param responseFormat The format the caller asked for.
|
|
73114
|
+
* @param usage What the provider billed for the answer.
|
|
73115
|
+
* @param fenced Whether one enclosing fence was removed before parsing.
|
|
73116
|
+
* @param cause The parser's own complaint.
|
|
73117
|
+
*/
|
|
73118
|
+
constructor(responseFormat, usage, fenced, cause) {
|
|
73119
|
+
super(`LLM returned content that is not valid JSON for a ${responseFormat} request` +
|
|
73120
|
+
(fenced ? " (inside one enclosing markdown fence)" : "") +
|
|
73121
|
+
`: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
73122
|
+
this.name = "LlmResponseFormatError";
|
|
73123
|
+
this.responseFormat = responseFormat;
|
|
73124
|
+
this.usage = usage;
|
|
73125
|
+
this.fenced = fenced;
|
|
73126
|
+
}
|
|
73127
|
+
}
|
|
73128
|
+
/**
|
|
73129
|
+
* The body of the one markdown fence that encloses an answer, if exactly one does.
|
|
73130
|
+
*
|
|
73131
|
+
* @param text The model's answer.
|
|
73132
|
+
* @returns The fenced body, or null when the answer is not wholly one fenced block.
|
|
73133
|
+
*/
|
|
73134
|
+
function unwrapSingleJsonFence(text) {
|
|
73135
|
+
const match = SINGLE_ENCLOSING_JSON_FENCE.exec(text);
|
|
73136
|
+
return match === null ? null : match[1];
|
|
73137
|
+
}
|
|
73138
|
+
/**
|
|
73139
|
+
* Parse a model's answer to a structured request.
|
|
73140
|
+
*
|
|
73141
|
+
* A JSON format that does not parse is an error, not an empty object. Returning
|
|
73142
|
+
* a default here would hand the caller a well-typed value that means nothing,
|
|
73143
|
+
* and the failure would surface much later as a decision made on absent data.
|
|
73144
|
+
*
|
|
73145
|
+
* @param content The raw content of the model's message.
|
|
73146
|
+
* @param responseFormat The structured format the caller asked for.
|
|
73147
|
+
* @param usage What the provider billed for this answer, carried on failure.
|
|
73148
|
+
* @returns The parsed value.
|
|
73149
|
+
* @throws {LlmResponseFormatError} When the content is not JSON, fenced or not.
|
|
73150
|
+
*/
|
|
73151
|
+
function parseStructuredContent(content, responseFormat, usage) {
|
|
73152
|
+
const text = typeof content === "string" ? content : "";
|
|
73153
|
+
const fencedBody = unwrapSingleJsonFence(text);
|
|
73154
|
+
try {
|
|
73155
|
+
return JSON.parse(fencedBody ?? text);
|
|
73156
|
+
}
|
|
73157
|
+
catch (error) {
|
|
73158
|
+
throw new LlmResponseFormatError(typeof responseFormat === "string" ? responseFormat : "json_schema", usage, fencedBody !== null, error);
|
|
73159
|
+
}
|
|
72756
73160
|
}
|
|
72757
73161
|
|
|
72758
73162
|
/**
|
|
@@ -72889,7 +73293,9 @@ async function runLeg(leg, params, execution) {
|
|
|
72889
73293
|
// The guards wrap the transport rather than the whole leg, so the per-leg
|
|
72890
73294
|
// timeout above still bounds the total wait: a caller queued behind the
|
|
72891
73295
|
// rate limiter is spending its budget just as surely as one waiting on the
|
|
72892
|
-
// provider, and only one clock should govern both.
|
|
73296
|
+
// provider, and only one clock should govern both. The leg's own signal is
|
|
73297
|
+
// handed to the guard as well, so a leg whose budget or caller is gone
|
|
73298
|
+
// leaves the queue at once instead of holding its place in it.
|
|
72893
73299
|
return await withProviderGuards(leg.route.providerName, () => leg.transport.execute({
|
|
72894
73300
|
route: leg.route,
|
|
72895
73301
|
content: execution.content,
|
|
@@ -72899,7 +73305,7 @@ async function runLeg(leg, params, execution) {
|
|
|
72899
73305
|
context: execution.context,
|
|
72900
73306
|
signal: controller.signal,
|
|
72901
73307
|
correlationId: execution.correlationId,
|
|
72902
|
-
}), budgetMs);
|
|
73308
|
+
}), budgetMs, { modelId: leg.route.modelId, signal: controller.signal });
|
|
72903
73309
|
}
|
|
72904
73310
|
finally {
|
|
72905
73311
|
clearTimeout(timer);
|
|
@@ -73011,7 +73417,7 @@ async function executeChain(alias, execution) {
|
|
|
73011
73417
|
continue;
|
|
73012
73418
|
}
|
|
73013
73419
|
const startedAt = now();
|
|
73014
|
-
execution.breakers.onAttemptStart(route.routeKey);
|
|
73420
|
+
const holdsProbe = execution.breakers.onAttemptStart(route.routeKey);
|
|
73015
73421
|
try {
|
|
73016
73422
|
const response = await runLeg(leg, leg.params, execution);
|
|
73017
73423
|
execution.breakers.onSuccess(route.routeKey);
|
|
@@ -73034,6 +73440,15 @@ async function executeChain(alias, execution) {
|
|
|
73034
73440
|
if (countsAgainstHealth) {
|
|
73035
73441
|
execution.breakers.onFailure(route.routeKey);
|
|
73036
73442
|
}
|
|
73443
|
+
else if (holdsProbe) {
|
|
73444
|
+
// No verdict on the route's health, but the probe slot this attempt
|
|
73445
|
+
// took must come back, or a half-open route admits no probe ever again.
|
|
73446
|
+
execution.breakers.onAttemptAbandoned(route.routeKey);
|
|
73447
|
+
}
|
|
73448
|
+
// A provider that answered with unparseable content still billed for the
|
|
73449
|
+
// answer; the spend belongs in the total whether or not a later leg serves.
|
|
73450
|
+
const billed = error instanceof LlmResponseFormatError ? error.usage : undefined;
|
|
73451
|
+
totalUsage = sumUsage(totalUsage, billed);
|
|
73037
73452
|
const record = {
|
|
73038
73453
|
routeKey: route.routeKey,
|
|
73039
73454
|
role: route.role,
|
|
@@ -73042,6 +73457,7 @@ async function executeChain(alias, execution) {
|
|
|
73042
73457
|
outcome,
|
|
73043
73458
|
durationMs: now() - startedAt,
|
|
73044
73459
|
reason,
|
|
73460
|
+
...(billed === undefined ? {} : { usage: billed }),
|
|
73045
73461
|
};
|
|
73046
73462
|
attempts.push(record);
|
|
73047
73463
|
execution.onAttempt?.(record);
|
|
@@ -74335,9 +74751,13 @@ function createGatewayTransport(config) {
|
|
|
74335
74751
|
const payload = (await response.json());
|
|
74336
74752
|
const choices = payload.choices;
|
|
74337
74753
|
const message = choices?.[0]?.message;
|
|
74754
|
+
// Usage is read before the content is interpreted. The provider billed for
|
|
74755
|
+
// this answer whether or not it parses, and a parse failure that dropped
|
|
74756
|
+
// the count would report the attempt as free.
|
|
74757
|
+
const usage = readUsage(payload, request);
|
|
74338
74758
|
return {
|
|
74339
|
-
response:
|
|
74340
|
-
usage
|
|
74759
|
+
response: interpretContent(message?.content, request.responseFormat, usage),
|
|
74760
|
+
usage,
|
|
74341
74761
|
tool_calls: Array.isArray(message?.tool_calls)
|
|
74342
74762
|
? message.tool_calls
|
|
74343
74763
|
: undefined,
|
|
@@ -74372,25 +74792,22 @@ function buildMessages(request) {
|
|
|
74372
74792
|
/**
|
|
74373
74793
|
* Interpret the model's content according to the requested format.
|
|
74374
74794
|
*
|
|
74375
|
-
*
|
|
74376
|
-
*
|
|
74377
|
-
*
|
|
74795
|
+
* Text is returned as sent. A structured format is parsed under the strict
|
|
74796
|
+
* single-fence rule of {@link parseStructuredContent}; a structured answer that
|
|
74797
|
+
* does not parse is an error carrying what the provider billed for it, never an
|
|
74798
|
+
* empty object.
|
|
74378
74799
|
*
|
|
74379
74800
|
* @param content The raw content.
|
|
74380
74801
|
* @param responseFormat The format the caller asked for.
|
|
74381
|
-
* @
|
|
74802
|
+
* @param usage What the provider billed for this answer.
|
|
74803
|
+
* @returns The interpreted value.
|
|
74804
|
+
* @throws {LlmResponseFormatError} When a structured answer does not parse.
|
|
74382
74805
|
*/
|
|
74383
|
-
function
|
|
74384
|
-
const text = typeof content === "string" ? content : "";
|
|
74806
|
+
function interpretContent(content, responseFormat, usage) {
|
|
74385
74807
|
if (responseFormat === "text") {
|
|
74386
|
-
return
|
|
74387
|
-
}
|
|
74388
|
-
try {
|
|
74389
|
-
return JSON.parse(text);
|
|
74390
|
-
}
|
|
74391
|
-
catch (error) {
|
|
74392
|
-
throw new Error(`LLM returned content that is not valid JSON for a ${typeof responseFormat === "string" ? responseFormat : "json_schema"} request: ${error instanceof Error ? error.message : String(error)}`);
|
|
74808
|
+
return (typeof content === "string" ? content : "");
|
|
74393
74809
|
}
|
|
74810
|
+
return parseStructuredContent(content, responseFormat, usage);
|
|
74394
74811
|
}
|
|
74395
74812
|
|
|
74396
74813
|
/**
|
|
@@ -78485,6 +78902,7 @@ const OrderStatusSchema = enumType([
|
|
|
78485
78902
|
"accepted",
|
|
78486
78903
|
"pending_new",
|
|
78487
78904
|
"accepted_for_bidding",
|
|
78905
|
+
"held",
|
|
78488
78906
|
"stopped",
|
|
78489
78907
|
"rejected",
|
|
78490
78908
|
"suspended",
|
|
@@ -80094,5 +80512,5 @@ const adaptic = {
|
|
|
80094
80512
|
};
|
|
80095
80513
|
const adptc = adaptic;
|
|
80096
80514
|
|
|
80097
|
-
export { API_RETRY_CONFIGS, AVNewsArticleSchema, AVNewsResponseSchema, AdapticUtilsError, AlpacaAccountDetailsSchema, AlpacaApiError, AlpacaBarSchema, AlpacaClient, AlpacaCryptoBarsResponseSchema, AlpacaHistoricalBarsResponseSchema, AlpacaLatestBarsResponseSchema, AlpacaLatestQuotesResponseSchema, AlpacaLatestTradesResponseSchema, AlpacaMarketDataAPI, AlpacaNewsArticleSchema, AlpacaNewsResponseSchema, AlpacaOrderSchema, AlpacaOrdersArraySchema, AlpacaPortfolioHistoryResponseSchema, AlpacaPositionSchema, AlpacaPositionsArraySchema, AlpacaQuoteSchema, AlpacaTradeSchema, AlpacaTradingAPI, AlphaVantageError, AlphaVantageQuoteResponseSchema, AssetAllocationEngine, AuthenticationError, AutonomyMode, BTC_PAIRS, BarError, ChainExhaustedError, CircuitBreakerRegistry, CircuitOpenError, CryptoDataError, CryptoOrderError, DEFAULT_CACHE_OPTIONS, DEFAULT_RISK_FREE_RATE, DEFAULT_TIMEOUTS, DEFAULT_TRADING_POLICY, DataFormatError, DecisionMemoryOutcome, DecisionOutcome, DecisionRecordStatus, DirectTransportRefusedError, DuplicateClientOrderIdError, GatewayResponseError, GatewayUnreachableError, HttpClientError, HttpServerError, KEEP_ALIVE_DEFAULTS, LlmProvider, MARKET_DATA_API, MassiveAggregatesResponseSchema, MassiveApiError, MassiveDailyOpenCloseSchema, MassiveErrorResponseSchema, MassiveGroupedDailyResponseSchema, MassiveLastTradeResponseSchema, MassiveTickerDetailsResponseSchema, MassiveTickerInfoSchema, MassiveTradeSchema as MassiveTradeZodSchema, MassiveTradesResponseSchema, NetworkError, NewsError, NoServableRouteError, OptionStrategyError, OptionsDataError, OverlaySeverity, OverlayStatus, OverlayType, QuoteError, RISK_FREE_RATE_TTL_MS, RateGuardTimeoutError, RateLimitError, RawMassivePriceDataSchema, SchemaRetryExhaustedError, StampedeProtectedCache, StreamProviderError, StreamTruncatedError, TRADING_API, TimeoutError, TokenBucketRateLimiter, TradeError, TrailingStopValidationError, USDC_PAIRS, USDT_PAIRS, USD_PAIRS, UnknownAliasError, UnsupportedBrokerError, UnsupportedCapabilityError, ValidationError, ValidationResponseError, WEBSOCKET_STREAMS, WebSocketError, account, adaptic, adptc, alpaca, analyzeBars, approximateImpliedVolatility, atrNs as atr, bracketOrders, buildOCCSymbol, buildOptionSymbol, buildRetryPrompt, buyCryptoNotional, buyToClose, buyToOpen, buyWithStopLoss, buyWithTrailingStop, calculateMoneyness, calculateOrderValue, calculatePeriodPerformance, calculatePutCallRatio, calculateTotalFilledValue, callLLMByAlias, callWithValidation, cancelAllCryptoOrders, cancelOCOOrder, cancelOTOOrder, cancelTrailingStop, cancelTrailingStopsForSymbol, checkTradingEligibility, clearClientCache, clock, closeAllOptionPositions, closeOptionPosition, closedIncumbentLeg, collectStream, configureLlmClient, createAlpacaClient, createAlpacaMarketDataAPI, createAlpacaTradingAPI, createBracketOrder, createBrokerClient, createButterflySpread, createClientFromEnv, createCoveredCall, createCryptoLimitOrder, createCryptoMarketOrder, createCryptoOrder, createCryptoStopLimitOrder, createCryptoStopOrder, createDirectTransport, createExecutorFromTradingAPI, createGatewayTransport, createIronCondor$1 as createIronCondor, createIronCondor as createIronCondorAdvanced, createMultiLegOptionOrder, createOCOOrder, createOTOOrder, createOptionOrder, createPortfolioTrailingStops, createProtectiveBracket, createStampedeProtectedCache, createStraddle$1 as createStraddle, createStraddle as createStraddleAdvanced, createStrangle$1 as createStrangle, createStrangle as createStrangleAdvanced, createStreamManager, createTimeoutSignal, createTrailingStop, createVerticalSpread$1 as createVerticalSpread, createVerticalSpread as createVerticalSpreadAdvanced, enrichAlpacaError, entryWithPercentStopLoss, exerciseOption, extractAlpacaBrokerError, extractGreeks, filterByExpiration, filterByStrike, filterByType, filterOrdersByDateRange, findATMOptions, findATMStrikes, findNearestExpiration, findOptionsByDelta, formatOrderForLog, formatOrderSummary, gatewayModelNameFor, generateOptimalAllocation, getAccountConfiguration, getAccountDetails, getAccountSummary, getAgentPoolStatus, getAllOrders, getAlpacaBrokerErrorCode, getAlpacaBrokerErrorDetail, getAlpacaCalendar, getAlpacaClock, getAverageDailyVolume, getBars, getBuyingPower, getCachedRiskFreeRateSync, getCachedRiskFreeRateSyncWithProvenance, getCrypto24HourChange, getCryptoBars, getCryptoDailyPrices, getCryptoPairsByQuote, getCryptoPrice, getCryptoSnapshots, getCryptoSpread, getCryptoStreamUrl, getCryptoTrades, getCurrentPrice, getCurrentPrices, getDailyPrices, getDailyReturns, getDaysToExpiration, getDefaultRiskProfile, getEquityCurve, getExpirationDates, getFilledOrders, getGroupedOptionChain, getHistoricalOptionsBars, getHistoricalTrades, getIntradayPrices, getLatestBars, getLatestCryptoQuotes, getLatestCryptoTrades, getLatestNews, getLatestOptionsQuotes, getLatestOptionsTrades, getLatestQuote, getLatestQuotes, getLatestTrade, getLatestTrades, getLogger, getMarginInfo, getNews, getNewsForSymbols, getOCOOrderStatus, getOTOOrderStatus, getOpenCryptoOrders, getOpenOrders$1 as getOpenOrdersQuery, getOpenTrailingStops, getOptionChain, getOptionContract, getOptionContracts, getOptionSpread, getOptionsChain, getOptionsSnapshots, getOptionsStreamUrl, getOptionsTradingLevel, getOrderHistory, getOrdersBySymbol, getPDTStatus, getPopularCryptoPairs, getPortfolioHistory, getPreviousClose, getPriceRange, getRiskFreeRate, getRiskFreeRateWithProvenance, getSpread, getSpreads, getStockStreamUrl, getStrikePrices, getSupportedCryptoPairs, getSymbolSentiment, getTimeout, getTradeVolume, getTradingApiUrl, getTradingWebSocketUrl, getTrailingStopHWM, groupOrdersByStatus, groupOrdersBySymbol, guardSnapshots, hasActiveTrailingStop, hasGoodLiquidity as hasOptionLiquidity, hasGoodLiquidity$1 as hasStockLiquidity, hasSufficientVolume, httpAgent, httpsAgent, isAlpacaBrokerCredentials, isContractTradable, isCryptoPair, isExpiringWithin, isMarginAccount, isOptionOrderCancelable, isOptionOrderTerminal, isOrderFillable, isOrderFilled, isOrderOpen, isOrderTerminal$1 as isOrderTerminalStatus, isSupportedCryptoPair, isTransientNetworkError, index$1 as legacyApi, limitBuyWithTakeProfit, limitsFor, limitsInventory, listAliases, llmAliases, llmBreakers, normaliseAnthropicStream, normaliseOpenAiStream, normaliseParams, normaliseStream, ocoOrders, orderUtils, orderedRoutes, otoOrders, paginate, paginateAll, parseOCCSymbol, protectLongPosition, protectShortPosition, rateLimiters$1 as rateLimiters, resetLogger, resetProviderGuards, resetRiskFreeRateCache, resolveChain, resolveDefaultDirectCaller, riskNs as risk, rollOptionPosition, roundPriceForAlpaca$3 as roundPriceForAlpaca, roundPriceForAlpacaNumber, routeKeyFor, routeSupports, routeTable, safeValidateResponse, searchNews, sellAllCrypto, sellCryptoNotional, sellToClose, sellToOpen, setLogger, setRiskFreeRate, shortWithStopLoss, sortOrdersByDate, strategyNs as strategy, sumUsage, index as tradingPolicy, trailingStops, updateAccountConfiguration, updateTrailingStop, validateAlpacaCredentials, validateAlphaVantageApiKey, validateMassiveApiKey$1 as validateMassiveApiKey, validateMultiLegOrder, validateResponse, verifyFetchKeepAlive, volatilityNs as volatility, waitForOrderFill, withProviderGuards, withRetry, withTimeout };
|
|
80515
|
+
export { API_RETRY_CONFIGS, AVNewsArticleSchema, AVNewsResponseSchema, AdapticUtilsError, AlpacaAccountDetailsSchema, AlpacaApiError, AlpacaBarSchema, AlpacaClient, AlpacaCryptoBarsResponseSchema, AlpacaHistoricalBarsResponseSchema, AlpacaLatestBarsResponseSchema, AlpacaLatestQuotesResponseSchema, AlpacaLatestTradesResponseSchema, AlpacaMarketDataAPI, AlpacaNewsArticleSchema, AlpacaNewsResponseSchema, AlpacaOrderSchema, AlpacaOrdersArraySchema, AlpacaPortfolioHistoryResponseSchema, AlpacaPositionSchema, AlpacaPositionsArraySchema, AlpacaQuoteSchema, AlpacaTradeSchema, AlpacaTradingAPI, AlphaVantageError, AlphaVantageQuoteResponseSchema, AssetAllocationEngine, AuthenticationError, AutonomyMode, BTC_PAIRS, BarError, ChainExhaustedError, CircuitBreakerRegistry, CircuitOpenError, CryptoDataError, CryptoOrderError, DEFAULT_CACHE_OPTIONS, DEFAULT_RISK_FREE_RATE, DEFAULT_TIMEOUTS, DEFAULT_TRADING_POLICY, DataFormatError, DecisionMemoryOutcome, DecisionOutcome, DecisionRecordStatus, DirectTransportRefusedError, DuplicateClientOrderIdError, GatewayResponseError, GatewayUnreachableError, HttpClientError, HttpServerError, KEEP_ALIVE_DEFAULTS, LlmProvider, LlmResponseFormatError, MARKET_DATA_API, MassiveAggregatesResponseSchema, MassiveApiError, MassiveDailyOpenCloseSchema, MassiveErrorResponseSchema, MassiveGroupedDailyResponseSchema, MassiveLastTradeResponseSchema, MassiveTickerDetailsResponseSchema, MassiveTickerInfoSchema, MassiveTradeSchema as MassiveTradeZodSchema, MassiveTradesResponseSchema, NetworkError, NewsError, NoServableRouteError, OptionStrategyError, OptionsDataError, OverlaySeverity, OverlayStatus, OverlayType, QuoteError, RISK_FREE_RATE_TTL_MS, RateGuardTimeoutError, RateLimitError, RawMassivePriceDataSchema, SchemaRetryExhaustedError, StampedeProtectedCache, StreamProviderError, StreamTruncatedError, TRADING_API, TimeoutError, TokenBucketRateLimiter, TradeError, TrailingStopValidationError, USDC_PAIRS, USDT_PAIRS, USD_PAIRS, UnknownAliasError, UnsupportedBrokerError, UnsupportedCapabilityError, ValidationError, ValidationResponseError, WEBSOCKET_STREAMS, WebSocketError, account, adaptic, adptc, alpaca, analyzeBars, approximateImpliedVolatility, atrNs as atr, availableStatistic, bracketOrders, buildOCCSymbol, buildOptionSymbol, buildRetryPrompt, buyCryptoNotional, buyToClose, buyToOpen, buyWithStopLoss, buyWithTrailingStop, calculateMoneyness, calculateOrderValue, calculatePeriodPerformance, calculatePutCallRatio, calculateTotalFilledValue, callLLMByAlias, callWithValidation, cancelAllCryptoOrders, cancelOCOOrder, cancelOTOOrder, cancelTrailingStop, cancelTrailingStopsForSymbol, checkTradingEligibility, clearClientCache, clock, closeAllOptionPositions, closeOptionPosition, closedIncumbentLeg, collectStream, configureLlmClient, createAlpacaClient, createAlpacaMarketDataAPI, createAlpacaTradingAPI, createBracketOrder, createBrokerClient, createButterflySpread, createClientFromEnv, createCoveredCall, createCryptoLimitOrder, createCryptoMarketOrder, createCryptoOrder, createCryptoStopLimitOrder, createCryptoStopOrder, createDirectTransport, createExecutorFromTradingAPI, createGatewayTransport, createIronCondor$1 as createIronCondor, createIronCondor as createIronCondorAdvanced, createMultiLegOptionOrder, createOCOOrder, createOTOOrder, createOptionOrder, createPortfolioTrailingStops, createProtectiveBracket, createStampedeProtectedCache, createStraddle$1 as createStraddle, createStraddle as createStraddleAdvanced, createStrangle$1 as createStrangle, createStrangle as createStrangleAdvanced, createStreamManager, createTimeoutSignal, createTrailingStop, createVerticalSpread$1 as createVerticalSpread, createVerticalSpread as createVerticalSpreadAdvanced, enrichAlpacaError, entryWithPercentStopLoss, exerciseOption, extractAlpacaBrokerError, extractGreeks, filterByExpiration, filterByStrike, filterByType, filterOrdersByDateRange, findATMOptions, findATMStrikes, findNearestExpiration, findOptionsByDelta, formatOrderForLog, formatOrderSummary, gatewayModelNameFor, generateOptimalAllocation, getAccountConfiguration, getAccountDetails, getAccountSummary, getAgentPoolStatus, getAllOrders, getAlpacaBrokerErrorCode, getAlpacaBrokerErrorDetail, getAlpacaCalendar, getAlpacaClock, getAverageDailyVolume, getBars, getBuyingPower, getCachedRiskFreeRateSync, getCachedRiskFreeRateSyncWithProvenance, getCrypto24HourChange, getCryptoBars, getCryptoDailyPrices, getCryptoPairsByQuote, getCryptoPrice, getCryptoSnapshots, getCryptoSpread, getCryptoStreamUrl, getCryptoTrades, getCurrentPrice, getCurrentPrices, getDailyPrices, getDailyReturns, getDaysToExpiration, getDefaultRiskProfile, getEquityCurve, getExpirationDates, getFilledOrders, getGroupedOptionChain, getHistoricalOptionsBars, getHistoricalTrades, getIntradayPrices, getLatestBars, getLatestCryptoQuotes, getLatestCryptoTrades, getLatestNews, getLatestOptionsQuotes, getLatestOptionsTrades, getLatestQuote, getLatestQuotes, getLatestTrade, getLatestTrades, getLogger, getMarginInfo, getNews, getNewsForSymbols, getOCOOrderStatus, getOTOOrderStatus, getOpenCryptoOrders, getOpenOrders$1 as getOpenOrdersQuery, getOpenTrailingStops, getOptionChain, getOptionContract, getOptionContracts, getOptionSpread, getOptionsChain, getOptionsSnapshots, getOptionsStreamUrl, getOptionsTradingLevel, getOrderHistory, getOrdersBySymbol, getPDTStatus, getPopularCryptoPairs, getPortfolioHistory, getPreviousClose, getPriceRange, getRiskFreeRate, getRiskFreeRateWithProvenance, getSpread, getSpreads, getStockStreamUrl, getStrikePrices, getSupportedCryptoPairs, getSymbolSentiment, getTimeout, getTradeVolume, getTradingApiUrl, getTradingWebSocketUrl, getTrailingStopHWM, groupOrdersByStatus, groupOrdersBySymbol, guardSnapshots, hasActiveTrailingStop, hasGoodLiquidity as hasOptionLiquidity, hasGoodLiquidity$1 as hasStockLiquidity, hasSufficientVolume, httpAgent, httpsAgent, isAlpacaBrokerCredentials, isAvailable, isContractTradable, isCryptoPair, isExpiringWithin, isMarginAccount, isOptionOrderCancelable, isOptionOrderTerminal, isOrderFillable, isOrderFilled, isOrderOpen, isOrderTerminal$1 as isOrderTerminalStatus, isSupportedCryptoPair, isTransientNetworkError, index$1 as legacyApi, limitBuyWithTakeProfit, limitsFor, limitsInventory, listAliases, llmAliases, llmBreakers, normaliseAnthropicStream, normaliseOpenAiStream, normaliseParams, normaliseStream, ocoOrders, orderUtils, orderedRoutes, otoOrders, paginate, paginateAll, parseOCCSymbol, protectLongPosition, protectShortPosition, rateLimiters$1 as rateLimiters, resetLogger, resetProviderGuards, resetRiskFreeRateCache, resolveChain, resolveDefaultDirectCaller, riskNs as risk, rollOptionPosition, roundPriceForAlpaca$3 as roundPriceForAlpaca, roundPriceForAlpacaNumber, routeKeyFor, routeSupports, routeTable, safeValidateResponse, sampleCohort, searchNews, sellAllCrypto, sellCryptoNotional, sellToClose, sellToOpen, setLogger, setRiskFreeRate, shortWithStopLoss, sortOrdersByDate, strategyNs as strategy, sumUsage, index as tradingPolicy, trailingStops, unavailableStatistic, updateAccountConfiguration, updateTrailingStop, validateAlpacaCredentials, validateAlphaVantageApiKey, validateMassiveApiKey$1 as validateMassiveApiKey, validateMultiLegOrder, validateResponse, verifyFetchKeepAlive, volatilityNs as volatility, waitForOrderFill, withProviderGuards, withRetry, withTimeout };
|
|
80098
80516
|
//# sourceMappingURL=index.mjs.map
|