@adaptic/utils 0.0.1000 → 0.0.1002

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.
Files changed (33) hide show
  1. package/dist/index.cjs +160 -29
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.mjs +158 -30
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/test.js +0 -6
  6. package/dist/test.js.map +1 -1
  7. package/dist/types/__tests__/broker-factory.test.d.ts +2 -0
  8. package/dist/types/__tests__/broker-factory.test.d.ts.map +1 -0
  9. package/dist/types/__tests__/broker-types.test.d.ts +2 -0
  10. package/dist/types/__tests__/broker-types.test.d.ts.map +1 -0
  11. package/dist/types/alpaca/client.d.ts +1 -1
  12. package/dist/types/alpaca/client.d.ts.map +1 -1
  13. package/dist/types/alpaca/legacy/auth.d.ts +27 -0
  14. package/dist/types/alpaca/legacy/auth.d.ts.map +1 -1
  15. package/dist/types/alpaca/legacy/index.d.ts +1 -1
  16. package/dist/types/alpaca/legacy/index.d.ts.map +1 -1
  17. package/dist/types/broker/factory.d.ts +70 -0
  18. package/dist/types/broker/factory.d.ts.map +1 -0
  19. package/dist/types/broker/index.d.ts +9 -0
  20. package/dist/types/broker/index.d.ts.map +1 -0
  21. package/dist/types/errors/index.d.ts +14 -0
  22. package/dist/types/errors/index.d.ts.map +1 -1
  23. package/dist/types/index.d.ts +4 -3
  24. package/dist/types/index.d.ts.map +1 -1
  25. package/dist/types/market-hours.d.ts.map +1 -1
  26. package/dist/types/performance-metrics.d.ts.map +1 -1
  27. package/dist/types/types/alpaca-types.d.ts +1 -0
  28. package/dist/types/types/alpaca-types.d.ts.map +1 -1
  29. package/dist/types/types/broker-types.d.ts +112 -0
  30. package/dist/types/types/broker-types.d.ts.map +1 -0
  31. package/dist/types/types/index.d.ts +1 -0
  32. package/dist/types/types/index.d.ts.map +1 -1
  33. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -1247,12 +1247,6 @@ const marketEarlyCloses = {
1247
1247
  },
1248
1248
  },
1249
1249
  2026: {
1250
- "2026-07-02": {
1251
- date: "2026-07-02",
1252
- time: "13:00",
1253
- optionsTime: "13:15",
1254
- notes: "Independence Day observed, market closes early at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
1255
- },
1256
1250
  "2026-11-27": {
1257
1251
  date: "2026-11-27",
1258
1252
  time: "13:00",
@@ -2365,6 +2359,23 @@ class NetworkError extends AdapticUtilsError {
2365
2359
  this.service = service;
2366
2360
  }
2367
2361
  }
2362
+ /**
2363
+ * Unsupported brokerage provider errors
2364
+ * Thrown when a broker operation is requested for a provider that has no
2365
+ * implemented integration (e.g. IBKR or COINBASE before their adapters land,
2366
+ * or an unrecognised provider string from an untyped caller).
2367
+ * Never retryable — the caller must route to a supported provider.
2368
+ */
2369
+ class UnsupportedBrokerError extends AdapticUtilsError {
2370
+ provider;
2371
+ constructor(
2372
+ /** The provider that was requested but is not supported. */
2373
+ provider, cause) {
2374
+ super(`Brokerage provider "${provider}" is not supported. Supported providers: ALPACA`, "UNSUPPORTED_BROKER", "broker", false, // Unsupported providers are never retryable
2375
+ cause);
2376
+ this.provider = provider;
2377
+ }
2378
+ }
2368
2379
  /**
2369
2380
  * Data parsing and format errors
2370
2381
  * Used when API responses cannot be parsed or are in unexpected format
@@ -5779,9 +5790,18 @@ class AlpacaTradingAPI {
5779
5790
  *
5780
5791
  * @param auth - The authentication details for Alpaca
5781
5792
  * @returns Validated authentication credentials
5793
+ * @throws UnsupportedBrokerError if `auth.provider` is set to a non-ALPACA provider
5782
5794
  * @throws Error if authentication details are missing or invalid
5783
5795
  */
5784
5796
  async function validateAuth(auth) {
5797
+ // Multi-broker guard (SP2): this seam only resolves Alpaca credentials.
5798
+ // `auth.provider` is typed as "ALPACA" on AlpacaAuth, but untyped callers
5799
+ // (or future BrokerAuth adapters) may pass other providers at runtime —
5800
+ // fail fast with a typed error instead of silently hitting Alpaca hosts.
5801
+ const requestedProvider = auth.provider;
5802
+ if (requestedProvider !== undefined && requestedProvider !== "ALPACA") {
5803
+ throw new UnsupportedBrokerError(requestedProvider);
5804
+ }
5785
5805
  const inlineKey = auth.alpacaApiKey && auth.alpacaApiKey.trim().length > 0
5786
5806
  ? auth.alpacaApiKey
5787
5807
  : undefined;
@@ -5803,26 +5823,54 @@ async function validateAuth(auth) {
5803
5823
  };
5804
5824
  }
5805
5825
  if (auth.adapticAccountId) {
5806
- const client = await getSharedApolloClient();
5807
- const alpacaAccount = (await adaptic$1.alpacaAccount.get({
5808
- id: auth.adapticAccountId,
5809
- }, client));
5810
- if (!alpacaAccount || !alpacaAccount.APIKey || !alpacaAccount.APISecret) {
5811
- throw new Error("Alpaca account not found or incomplete");
5812
- }
5813
- validateAlpacaCredentials({
5814
- apiKey: alpacaAccount.APIKey,
5815
- apiSecret: alpacaAccount.APISecret,
5816
- isPaper: alpacaAccount.type === "PAPER",
5817
- });
5818
- return {
5819
- APIKey: alpacaAccount.APIKey,
5820
- APISecret: alpacaAccount.APISecret,
5821
- type: alpacaAccount.type,
5822
- };
5826
+ return resolveBrokerCredentials(auth.adapticAccountId);
5823
5827
  }
5824
5828
  throw new Error("Either adapticAccountId or both alpacaApiKey and alpacaApiSecret must be provided");
5825
5829
  }
5830
+ /**
5831
+ * Resolves broker credentials for a backend brokerage-account id.
5832
+ *
5833
+ * This is the SINGLE backend-coupled credential lookup in this package —
5834
+ * every account-id-based credential resolution must flow through here so
5835
+ * that backend model changes touch exactly one function.
5836
+ *
5837
+ * SP2 transition note: today the id is an `AlpacaAccount.id` resolved via
5838
+ * `adaptic.alpacaAccount.get`. When backend-legacy publishes the
5839
+ * `BrokerageAccount` model (backfilled with `id = AlpacaAccount.id`, so the
5840
+ * id space is identical), the switch to `adaptic.brokerageAccount.get`
5841
+ * happens INSIDE this function only, following the sequencing rule in
5842
+ * CLAUDE.md ("Multi-Broker Sequencing Rule"): backend-legacy publishes →
5843
+ * utils bumps the dependency and switches this helper → utils publishes →
5844
+ * engine bumps its pin. Do not reference `brokerageAccount` anywhere in
5845
+ * this package before the pinned backend-legacy version exports it.
5846
+ *
5847
+ * The lookup is a no-cache GraphQL round trip to backend-legacy; callers
5848
+ * holding inline credentials should never reach it (see `validateAuth`
5849
+ * precedence).
5850
+ *
5851
+ * @param brokerageAccountId - Backend brokerage-account id (currently the AlpacaAccount id)
5852
+ * @returns Validated authentication credentials
5853
+ * @throws Error if the account is not found or its credentials are incomplete
5854
+ */
5855
+ async function resolveBrokerCredentials(brokerageAccountId) {
5856
+ const client = await getSharedApolloClient();
5857
+ const alpacaAccount = (await adaptic$1.alpacaAccount.get({
5858
+ id: brokerageAccountId,
5859
+ }, client));
5860
+ if (!alpacaAccount || !alpacaAccount.APIKey || !alpacaAccount.APISecret) {
5861
+ throw new Error("Alpaca account not found or incomplete");
5862
+ }
5863
+ validateAlpacaCredentials({
5864
+ apiKey: alpacaAccount.APIKey,
5865
+ apiSecret: alpacaAccount.APISecret,
5866
+ isPaper: alpacaAccount.type === "PAPER",
5867
+ });
5868
+ return {
5869
+ APIKey: alpacaAccount.APIKey,
5870
+ APISecret: alpacaAccount.APISecret,
5871
+ type: alpacaAccount.type,
5872
+ };
5873
+ }
5826
5874
 
5827
5875
  /**
5828
5876
  * Legacy Alpaca Utility Functions
@@ -7952,6 +8000,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
7952
8000
  getOrders: getOrders$1,
7953
8001
  makeRequest: makeRequest,
7954
8002
  replaceOrder: replaceOrder$1,
8003
+ resolveBrokerCredentials: resolveBrokerCredentials,
7955
8004
  roundPriceForAlpaca: roundPriceForAlpaca$5,
7956
8005
  updateConfiguration: updateConfiguration,
7957
8006
  validateAuth: validateAuth
@@ -11174,9 +11223,16 @@ function calculateBetaFromReturns(portfolioReturns, benchmarkReturns) {
11174
11223
  const denom = n > 1 ? n - 1 : 1;
11175
11224
  covariance /= denom;
11176
11225
  variance /= denom;
11177
- // Handle zero variance
11178
- if (variance === 0) {
11179
- getLogger().warn("Benchmark variance is zero. Setting beta to 0.");
11226
+ // Handle zero (or numerically-degenerate) variance. A constant benchmark
11227
+ // series can still produce a tiny nonzero variance because the computed
11228
+ // mean differs from the constant by an ulp; dividing covariance by that
11229
+ // rounding noise yields a meaningless beta. Treat any variance at or
11230
+ // below the summation noise floor — (n * eps * |mean|)^2, the square of
11231
+ // the worst-case naive-summation error — as zero. When the mean is
11232
+ // exactly 0 this reduces to the exact zero check.
11233
+ const varianceNoiseFloor = (n * Number.EPSILON * Math.abs(averageBenchmarkReturn)) ** 2;
11234
+ if (variance <= varianceNoiseFloor) {
11235
+ getLogger().warn("Benchmark variance is zero or below the floating-point noise floor. Setting beta to 0.");
11180
11236
  return {
11181
11237
  beta: 0,
11182
11238
  covariance,
@@ -12355,8 +12411,33 @@ const timeDiffString = (milliseconds) => {
12355
12411
  return parts.join(", ");
12356
12412
  };
12357
12413
 
12414
+ /**
12415
+ * Multi-broker foundation types
12416
+ *
12417
+ * Provider-agnostic brokerage types for the org → fund → brokerageAccount →
12418
+ * broker alignment (SP2). These are strictly ADDITIVE: the existing
12419
+ * Alpaca-specific types (`AlpacaAuth`, `AlpacaCredentials`,
12420
+ * `AlpacaClientConfig`) remain the canonical shapes consumed by the engine
12421
+ * and are unchanged. New provider-aware call sites should prefer these
12422
+ * types; only ALPACA is implemented today — IBKR and COINBASE arms are
12423
+ * typed placeholders that resolve to `UnsupportedBrokerError` at runtime.
12424
+ *
12425
+ * @module @adaptic/utils/types/broker-types
12426
+ */
12427
+ /**
12428
+ * Type guard narrowing {@link BrokerCredentials} to the implemented
12429
+ * ALPACA arm.
12430
+ *
12431
+ * @param credentials - Any broker credentials union member
12432
+ * @returns True when the credentials belong to the ALPACA provider
12433
+ */
12434
+ function isAlpacaBrokerCredentials(credentials) {
12435
+ return credentials.provider === "ALPACA";
12436
+ }
12437
+
12358
12438
  var Types = /*#__PURE__*/Object.freeze({
12359
- __proto__: null
12439
+ __proto__: null,
12440
+ isAlpacaBrokerCredentials: isAlpacaBrokerCredentials
12360
12441
  });
12361
12442
 
12362
12443
  /**
@@ -50669,12 +50750,16 @@ class AlpacaClient {
50669
50750
  }
50670
50751
  // Client cache for connection pooling
50671
50752
  const clientCache = new Map();
50753
+ // Provider discriminant for cache-key scoping (multi-broker SP2 seam):
50754
+ // keeps Alpaca pool entries disjoint from future providers that might
50755
+ // reuse an identical apiKey string.
50756
+ const ALPACA_PROVIDER = "ALPACA";
50672
50757
  /**
50673
50758
  * Create or get a cached Alpaca client
50674
- * Uses apiKey as cache key for connection pooling
50759
+ * Uses provider + apiKey + accountType as cache key for connection pooling
50675
50760
  */
50676
50761
  function createAlpacaClient(config) {
50677
- const cacheKey = `${config.apiKey}-${config.accountType}`;
50762
+ const cacheKey = `${ALPACA_PROVIDER}-${config.apiKey}-${config.accountType}`;
50678
50763
  if (clientCache.has(cacheKey)) {
50679
50764
  log$k(`Returning cached client for ${config.accountType}`, { type: "debug" });
50680
50765
  return clientCache.get(cacheKey);
@@ -68794,6 +68879,49 @@ function verifyFetchKeepAlive() {
68794
68879
  };
68795
68880
  }
68796
68881
 
68882
+ /**
68883
+ * Broker Client Factory
68884
+ *
68885
+ * Provider-agnostic entry point for broker trading clients (SP2 multi-broker
68886
+ * seam). Strictly ADDITIVE: `createAlpacaClient`, `createAlpacaTradingAPI`,
68887
+ * and `createAlpacaMarketDataAPI` remain the canonical Alpaca factories and
68888
+ * are unchanged. Only ALPACA is implemented — all other providers throw a
68889
+ * typed {@link UnsupportedBrokerError}.
68890
+ *
68891
+ * @module @adaptic/utils/broker
68892
+ */
68893
+ /**
68894
+ * Create (or reuse from cache) a broker trading client for the given
68895
+ * credentials.
68896
+ *
68897
+ * ALPACA delegates to `createAlpacaClient`, whose connection-pool cache key
68898
+ * is provider-scoped (`ALPACA-<apiKey>-<accountType>`), so a future
68899
+ * provider reusing an identical apiKey string can never collide with an
68900
+ * Alpaca client. All other providers — including unknown provider strings
68901
+ * from untyped callers — throw {@link UnsupportedBrokerError}.
68902
+ *
68903
+ * @param credentials - Discriminated broker credentials union
68904
+ * @returns A provider-appropriate {@link BrokerTradingClient}
68905
+ * @throws UnsupportedBrokerError for any provider other than ALPACA
68906
+ */
68907
+ function createBrokerClient(credentials) {
68908
+ switch (credentials.provider) {
68909
+ case "ALPACA":
68910
+ return createAlpacaClient({
68911
+ apiKey: credentials.apiKey,
68912
+ apiSecret: credentials.apiSecret,
68913
+ accountType: credentials.type,
68914
+ });
68915
+ case "IBKR":
68916
+ case "COINBASE":
68917
+ throw new UnsupportedBrokerError(credentials.provider);
68918
+ }
68919
+ // Unreachable for typed callers (the switch above is exhaustive), but
68920
+ // untyped runtime callers may pass an unrecognised provider string —
68921
+ // fail fast with the same typed error rather than undefined behaviour.
68922
+ throw new UnsupportedBrokerError(String(credentials.provider));
68923
+ }
68924
+
68797
68925
  /**
68798
68926
  * Mirror enums for the trading policy preference system.
68799
68927
  * These enums are used by both the trading engine and the frontend app
@@ -69943,6 +70071,7 @@ exports.TrailingStopValidationError = TrailingStopValidationError;
69943
70071
  exports.USDC_PAIRS = USDC_PAIRS;
69944
70072
  exports.USDT_PAIRS = USDT_PAIRS;
69945
70073
  exports.USD_PAIRS = USD_PAIRS;
70074
+ exports.UnsupportedBrokerError = UnsupportedBrokerError;
69946
70075
  exports.ValidationError = ValidationError;
69947
70076
  exports.ValidationResponseError = ValidationResponseError;
69948
70077
  exports.WEBSOCKET_STREAMS = WEBSOCKET_STREAMS;
@@ -69981,6 +70110,7 @@ exports.createAlpacaClient = createAlpacaClient;
69981
70110
  exports.createAlpacaMarketDataAPI = createAlpacaMarketDataAPI;
69982
70111
  exports.createAlpacaTradingAPI = createAlpacaTradingAPI;
69983
70112
  exports.createBracketOrder = createBracketOrder;
70113
+ exports.createBrokerClient = createBrokerClient;
69984
70114
  exports.createButterflySpread = createButterflySpread;
69985
70115
  exports.createClientFromEnv = createClientFromEnv;
69986
70116
  exports.createCoveredCall = createCoveredCall;
@@ -70111,6 +70241,7 @@ exports.hasStockLiquidity = hasGoodLiquidity$1;
70111
70241
  exports.hasSufficientVolume = hasSufficientVolume;
70112
70242
  exports.httpAgent = httpAgent;
70113
70243
  exports.httpsAgent = httpsAgent;
70244
+ exports.isAlpacaBrokerCredentials = isAlpacaBrokerCredentials;
70114
70245
  exports.isContractTradable = isContractTradable;
70115
70246
  exports.isCryptoPair = isCryptoPair;
70116
70247
  exports.isExpiringWithin = isExpiringWithin;