@pear-protocol/utils 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from './math/index';
6
6
  export * from './position/index';
7
7
  export * from './precise-price';
8
8
  export * from './precise-quantity';
9
+ export * from './proxy';
9
10
  export * from './sync/index';
10
11
  export * from './validate-leverage';
11
12
  export * from './validate-quantity';
package/dist/index.js CHANGED
@@ -411,6 +411,37 @@ function computePositionGrossRealizedPnL(assetPnLs) {
411
411
  function computePositionNetRealizedPnL(assetPnLs) {
412
412
  return assetPnLs.reduce((acc, asset) => acc.plus(asset.netPnl), ZERO);
413
413
  }
414
+ function computePositionEntryPrices(exposureMap, fills) {
415
+ const entryPrices = {};
416
+ for (const instrumentId of Object.keys(exposureMap)) {
417
+ const assetFills = fills.filter((fill) => fill.symbol === instrumentId);
418
+ const entryPrice = computeEntryPriceForAsset(assetFills);
419
+ if (isZero(entryPrice)) continue;
420
+ entryPrices[instrumentId] = entryPrice.toFixed();
421
+ }
422
+ return entryPrices;
423
+ }
424
+ function computeUnrealizedPositionStateFromEntryPrices(exposureMap, entryPrices, priceMap) {
425
+ const assetUPnLs = [];
426
+ for (const [id, signedQuantityString] of Object.entries(exposureMap)) {
427
+ const currentPriceString = priceMap[id];
428
+ const entryPriceString = entryPrices[id];
429
+ if (!currentPriceString || !entryPriceString) continue;
430
+ const signedQuantity = parse(signedQuantityString);
431
+ if (isZero(signedQuantity)) continue;
432
+ const currentPrice = parse(currentPriceString);
433
+ const entryPrice2 = parse(entryPriceString);
434
+ const upnl2 = multiply(signedQuantity, subtract(currentPrice, entryPrice2));
435
+ assetUPnLs.push({ id, signedQuantity, entryPrice: entryPrice2, currentPrice, upnl: upnl2 });
436
+ }
437
+ const entryPrice = computePositionEntryPrice(assetUPnLs);
438
+ const entryNotional = computePositionEntryNotional(assetUPnLs);
439
+ const markPrice = computePositionCurrentPrice(assetUPnLs);
440
+ const markNotional = computePositionMarkNotional(assetUPnLs);
441
+ const upnl = computePositionUnrealizedPnL(assetUPnLs);
442
+ const upnlBips = computePositionUnrealizedPnLBips(upnl, entryNotional);
443
+ return { assetUPnLs, entryPrice, entryNotional, markPrice, markNotional, upnl, upnlBips };
444
+ }
414
445
  function generateDeterministicPositionKey(input) {
415
446
  const symbols = [...input.symbols].sort((a, b) => a.localeCompare(b));
416
447
  const payload = JSON.stringify({
@@ -512,6 +543,45 @@ function preciseStepGridQuantity(raw, precision, side) {
512
543
  return final.toFixed();
513
544
  }
514
545
 
546
+ // src/proxy.ts
547
+ var activeOrigin = null;
548
+ function configureProxy(config) {
549
+ if (!config?.enabled || !config.url) {
550
+ activeOrigin = null;
551
+ return;
552
+ }
553
+ try {
554
+ activeOrigin = new URL(config.url).origin;
555
+ } catch {
556
+ activeOrigin = null;
557
+ }
558
+ }
559
+ function toProxyUrl(target, origin) {
560
+ const proxied = new URL(origin);
561
+ proxied.pathname = target.pathname;
562
+ proxied.search = target.search;
563
+ return proxied.toString();
564
+ }
565
+ async function withProxyFallback(proxied, direct) {
566
+ try {
567
+ const response = await proxied();
568
+ if (response.status < 500) return response;
569
+ } catch {
570
+ }
571
+ return direct();
572
+ }
573
+ function proxyFetch(input, init) {
574
+ const origin = activeOrigin;
575
+ if (!origin) return fetch(input, init);
576
+ const target = new URL(input);
577
+ const headers = new Headers(init?.headers);
578
+ headers.set("X-Proxy-Target", target.host);
579
+ return withProxyFallback(
580
+ () => fetch(toProxyUrl(target, origin), { ...init, headers }),
581
+ () => fetch(input, init)
582
+ );
583
+ }
584
+
515
585
  // src/sync/applicables/constants.ts
516
586
  var DUST = parse("0.00000001");
517
587
  var MISSED_HISTORY_FILL_PREFIX = "opening-synthetic-";
@@ -750,7 +820,10 @@ var WorkingBook = class _WorkingBook {
750
820
  const book = new _WorkingBook(tradeAccountId);
751
821
  for (const p of openPositions) {
752
822
  const position = {
753
- positionKey: book.keyFor(Object.keys(p.exposure)),
823
+ // Trust the position's own stored key — never recompute it from exposure.
824
+ // A partial fill on the original order leaves exposure short of the
825
+ // symbols the key was hashed from, so a recomputed key would never match.
826
+ positionKey: p.positionKey,
754
827
  exposure: Exposure.fromRecord(p.exposure),
755
828
  parts: [],
756
829
  closed: false,
@@ -1036,7 +1109,7 @@ function assignSyntheticsToPositions(args) {
1036
1109
  let target = positions.find((pos) => !pos.closed && pos.parts.some((part) => part.source.symbol === symbol));
1037
1110
  if (!target) {
1038
1111
  const existing = existingBySymbol.get(symbol);
1039
- const positionKey = existing ? keyOf(Object.keys(existing.exposure)) : keyOf([symbol]);
1112
+ const positionKey = existing ? existing.positionKey : keyOf([symbol]);
1040
1113
  target = positions.find((pos) => pos.positionKey === positionKey) ?? created.find((pos) => pos.positionKey === positionKey);
1041
1114
  if (!target) {
1042
1115
  target = existing ? (
@@ -1677,4 +1750,4 @@ function validateQuantity(quantity, price, precision) {
1677
1750
  return true;
1678
1751
  }
1679
1752
 
1680
- export { BIPS, ZERO, abs, add, baseToContracts, compareValue, computeAssetEntryNotional, computeAssetGrossRealizedPnL, computeAssetRealizedPnLs, computeAssetUndeterminedSizes, computeAssetUnrealizedPnL, computeAssetUnrealizedPnLs, computeBasketWeightedRatioV1, computeEntryPriceForAsset, computeRealizedPnlBySymbol, computeRealizedPnlFromFills, computeRealizedPositionState, computeSyncPayload, computeUnrealizedPositionState, contractsToBase, countDecimals, divide, exponentiate, generateDeterministicPositionKey, isEqualTo, isFiniteDecimal, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual, isNegative, isPositive, isZero, min, multiply, negate, parse, precisePrice, preciseQuantity, sideSign, sign, signed, signedBySide, subtract, toDecimalString, validateLeverage, validateQuantity };
1753
+ export { BIPS, ZERO, abs, add, baseToContracts, buildApplicables, compareValue, computeAssetEntryNotional, computeAssetGrossRealizedPnL, computeAssetRealizedPnLs, computeAssetUndeterminedSizes, computeAssetUnrealizedPnL, computeAssetUnrealizedPnLs, computeBasketWeightedRatioV1, computeEntryPriceForAsset, computePositionEntryPrices, computeRealizedPnlBySymbol, computeRealizedPnlFromFills, computeRealizedPositionState, computeSyncPayload, computeUnrealizedPositionState, computeUnrealizedPositionStateFromEntryPrices, configureProxy, contractsToBase, countDecimals, divide, exponentiate, generateDeterministicPositionKey, isEqualTo, isFiniteDecimal, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual, isNegative, isPositive, isZero, min, multiply, negate, parse, precisePrice, preciseQuantity, proxyFetch, sideSign, sign, signed, signedBySide, subtract, toDecimalString, validateLeverage, validateQuantity };
@@ -19,4 +19,20 @@ export declare function computeUnrealizedPositionState(exposureMap: ExposureMap,
19
19
  * @returns The realized position state.
20
20
  */
21
21
  export declare function computeRealizedPositionState(fills: Fill[]): RealizedPositionState;
22
+ /**
23
+ * Weighted-average entry (cost-basis) price per instrument in a position, derived purely from
24
+ * fills — no mark price needed. This is the authoritative source for a position's entry price;
25
+ * callers that already have it (e.g. from a position API response) should NOT re-derive it a
26
+ * second time by replaying fills themselves.
27
+ *
28
+ * Formula (per instrument): cost-basis average via computeEntryPriceForAsset.
29
+ */
30
+ export declare function computePositionEntryPrices(exposureMap: ExposureMap, fills: Fill[]): Record<InstrumentId, string>;
31
+ /**
32
+ * Unrealized position state derived from ALREADY-KNOWN entry prices (e.g. the entryPrices field
33
+ * on a position API response) instead of replaying the fills ledger. Prefer this over
34
+ * computeUnrealizedPositionState whenever the caller already has entry prices — it needs no
35
+ * fills fetch at all, only a live mark-price map.
36
+ */
37
+ export declare function computeUnrealizedPositionStateFromEntryPrices(exposureMap: ExposureMap, entryPrices: Record<InstrumentId, string>, priceMap: PriceMap): UnrealizedPositionState;
22
38
  export {};
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Optional forward-proxy for outbound SDK HTTP calls.
3
+ *
4
+ * When enabled, every request is re-pointed at the proxy's origin with the original destination
5
+ * carried in the `X-Proxy-Target` header. The proxy restores method, path, query, body, and headers
6
+ * before forwarding upstream (see pear-proxy). Off by default — a transparent passthrough to the
7
+ * global `fetch`.
8
+ *
9
+ * Config is INJECTED by the caller via the SDK constructors (`configureProxy`), never read from the
10
+ * environment: the SDKs also run in the browser, where `process.env` does not exist.
11
+ *
12
+ * Fallback: the proxy is best-effort. If the proxied call fails for any reason other than a genuine
13
+ * 4xx response — a network/transport error, or a proxy-side 5xx such as the load-shed 503 or the
14
+ * request-timeout 504 — the request is retried once directly against the original target, un-proxied.
15
+ * A 4xx passes straight through, since that is the upstream's own answer and a direct retry would
16
+ * only repeat it.
17
+ */
18
+ export type ProxyConfig = {
19
+ /** Master switch. When false (or the URL is missing), `proxyFetch` is a transparent passthrough. */
20
+ enabled: boolean;
21
+ /** Proxy base URL; only its origin (scheme + host + port) is used. */
22
+ url?: string;
23
+ };
24
+ /**
25
+ * Set (or clear) the forward-proxy used by every `proxyFetch` call. Called from the SDK
26
+ * constructors with the caller-supplied config. Passing a disabled/absent config turns it off.
27
+ */
28
+ export declare function configureProxy(config: ProxyConfig | null | undefined): void;
29
+ /**
30
+ * `fetch` for a string/URL request that routes through the configured proxy when enabled (with a
31
+ * direct-call fallback); otherwise calls the global `fetch` unchanged. All SDK call sites pass a
32
+ * URL + init, so a `Request`-object input is intentionally not supported.
33
+ */
34
+ export declare function proxyFetch(input: string | URL, init?: RequestInit): Promise<Response>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,2 +1,3 @@
1
+ export { buildApplicables } from './applicables';
1
2
  export { computeSyncPayload } from './compute';
2
3
  export type { ComputeSyncPayloadArgs, ComputeSyncPayloadResult } from './types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pear-protocol/utils",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Pear Protocol Utility functions",
5
5
  "private": false,
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@noble/hashes": "^1.3.2",
30
- "@pear-protocol/types": "^1.6.0",
30
+ "@pear-protocol/types": "^1.14.1",
31
31
  "bignumber.js": "9.3.1"
32
32
  },
33
33
  "devDependencies": {