@pear-protocol/utils 0.0.23 → 0.2.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.
Files changed (44) hide show
  1. package/dist/asset/asset.types.d.ts +1 -0
  2. package/dist/asset/compute.d.ts +9 -1
  3. package/dist/asset/compute.test.d.ts +1 -0
  4. package/dist/fills/compute.test.d.ts +1 -0
  5. package/dist/fills/fill.types.d.ts +1 -0
  6. package/dist/fills/index.d.ts +1 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +1437 -300
  9. package/dist/math/decimal.d.ts +57 -0
  10. package/dist/math/decimal.test.d.ts +1 -0
  11. package/dist/math/index.d.ts +1 -0
  12. package/dist/position/compute.d.ts +16 -0
  13. package/dist/position/compute.test.d.ts +1 -0
  14. package/dist/position/index.d.ts +1 -0
  15. package/dist/position/key.d.ts +15 -0
  16. package/dist/precise-price.d.ts +5 -0
  17. package/dist/precise-quantity.d.ts +4 -0
  18. package/dist/proxy.d.ts +34 -0
  19. package/dist/proxy.test.d.ts +1 -0
  20. package/dist/sync/applicables/basket.d.ts +42 -0
  21. package/dist/sync/applicables/basket.test.d.ts +1 -0
  22. package/dist/sync/applicables/constants.d.ts +19 -0
  23. package/dist/sync/applicables/exposure.d.ts +47 -0
  24. package/dist/sync/applicables/exposure.test.d.ts +1 -0
  25. package/dist/sync/applicables/fills.d.ts +16 -0
  26. package/dist/sync/applicables/fills.test.d.ts +1 -0
  27. package/dist/sync/applicables/index.d.ts +34 -0
  28. package/dist/sync/applicables/index.test.d.ts +1 -0
  29. package/dist/sync/applicables/matching.d.ts +17 -0
  30. package/dist/sync/applicables/matching.test.d.ts +1 -0
  31. package/dist/sync/applicables/position.d.ts +17 -0
  32. package/dist/sync/applicables/position.test.d.ts +1 -0
  33. package/dist/sync/applicables/snapshot.d.ts +41 -0
  34. package/dist/sync/applicables/synthetics.d.ts +52 -0
  35. package/dist/sync/applicables/synthetics.test.d.ts +1 -0
  36. package/dist/sync/applicables/types.d.ts +77 -0
  37. package/dist/sync/compute.d.ts +23 -9
  38. package/dist/sync/cutoff.d.ts +35 -0
  39. package/dist/sync/cutoff.test.d.ts +1 -0
  40. package/dist/sync/index.d.ts +1 -1
  41. package/dist/sync/types.d.ts +12 -13
  42. package/dist/validate-leverage.d.ts +3 -0
  43. package/dist/validate-quantity.d.ts +4 -0
  44. package/package.json +3 -2
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Centralized signed-decimal helpers. Everything that parses, formats, signs,
3
+ * compares, or does arithmetic on decimal strings/BigNumbers lives here — call
4
+ * sites never construct `new BigNumber(...)` directly.
5
+ */
6
+ import type { Side, SignedDecimalString } from '@pear-protocol/types';
7
+ import BigNumber from 'bignumber.js';
8
+ /**
9
+ * Either representation of a decimal value; helpers parse internally.
10
+ * Deliberately no `number` — floats stay out of decimal math.
11
+ */
12
+ export type DecimalLike = BigNumber | string;
13
+ /** Central constructor: parse a decimal string, or pass a BigNumber through. */
14
+ export declare function parse(value: DecimalLike): BigNumber;
15
+ /** Plain decimal string, never exponent notation (BigNumber `toFixed`). */
16
+ export declare function toDecimalString(value: DecimalLike): string;
17
+ /** Canonical signed-string wire format: `+…` / `-…`; zero is always `+0`. */
18
+ export declare function signed(value: DecimalLike): SignedDecimalString;
19
+ /** a + b */
20
+ export declare function add(a: DecimalLike, b: DecimalLike): BigNumber;
21
+ /** a - b */
22
+ export declare function subtract(a: DecimalLike, b: DecimalLike): BigNumber;
23
+ /** a × b */
24
+ export declare function multiply(a: DecimalLike, b: DecimalLike): BigNumber;
25
+ /** a ÷ b */
26
+ export declare function divide(a: DecimalLike, b: DecimalLike): BigNumber;
27
+ /** -a */
28
+ export declare function negate(value: DecimalLike): BigNumber;
29
+ /** |a| */
30
+ export declare function abs(value: DecimalLike): BigNumber;
31
+ /** The smaller of a and b. */
32
+ export declare function min(a: DecimalLike, b: DecimalLike): BigNumber;
33
+ /** True when the value equals zero. */
34
+ export declare function isZero(value: DecimalLike): boolean;
35
+ /**
36
+ * Sign-based like BigNumber: `+0` counts as positive. Call sites where zero
37
+ * matters must guard with {@link isZero} first.
38
+ */
39
+ export declare function isPositive(value: DecimalLike): boolean;
40
+ /** Sign-based like BigNumber: `-0` counts as negative (see {@link isPositive}). */
41
+ export declare function isNegative(value: DecimalLike): boolean;
42
+ /** True for a real, finite decimal (rejects NaN and ±Infinity). */
43
+ export declare function isFiniteDecimal(value: DecimalLike): boolean;
44
+ /** a == b (numeric equality, not string equality). */
45
+ export declare function isEqualTo(a: DecimalLike, b: DecimalLike): boolean;
46
+ /** a > b */
47
+ export declare function isGreaterThan(a: DecimalLike, b: DecimalLike): boolean;
48
+ /** a >= b */
49
+ export declare function isGreaterThanOrEqual(a: DecimalLike, b: DecimalLike): boolean;
50
+ /** a < b */
51
+ export declare function isLessThan(a: DecimalLike, b: DecimalLike): boolean;
52
+ /** a <= b */
53
+ export declare function isLessThanOrEqual(a: DecimalLike, b: DecimalLike): boolean;
54
+ /** The numeric sign of a trade side: BUY is +1, SELL is -1. */
55
+ export declare function sideSign(side: Side): 1 | -1;
56
+ /** Sign a quantity by its side: BUY positive, SELL negative. */
57
+ export declare function signedBySide(value: DecimalLike, side: Side): BigNumber;
@@ -0,0 +1 @@
1
+ export {};
@@ -22,3 +22,4 @@ export declare function sign(value: BigNumber): 1 | -1 | 0;
22
22
  */
23
23
  export declare function countDecimals(value: string): number;
24
24
  export declare function compareValue(track: 'PEAK' | 'TROUGH', value: BigNumber, threshold: number): boolean;
25
+ export * from './decimal';
@@ -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 @@
1
+ export {};
@@ -1,2 +1,3 @@
1
1
  export * from './compute';
2
+ export * from './key';
2
3
  export type { RealizedPositionState, UnrealizedPositionState } from './position.types';
@@ -0,0 +1,15 @@
1
+ type PositionKeyInput = {
2
+ tradeAccountId: string;
3
+ symbols: string[];
4
+ };
5
+ /**
6
+ * Canonical deterministic position key, reused for both PEAR and EXTERNAL
7
+ * positions and on both the client (sync compute) and the server (apply step).
8
+ *
9
+ * sha256 over { tradeAccountId.toLowerCase(), symbols.sort() }. No connector,
10
+ * no side — same-asset positions therefore produce the SAME key, which is
11
+ * exactly what the active-position unique index relies on and what makes the
12
+ * apply UPSERT find-or-create the single correct target.
13
+ */
14
+ export declare function generateDeterministicPositionKey(input: PositionKeyInput): string;
15
+ export {};
@@ -1,3 +1,8 @@
1
1
  import type { Connector, InstrumentPrecision, Side } from '@pear-protocol/types';
2
2
  import BigNumber from 'bignumber.js';
3
+ /**
4
+ * Round a raw price to an exchange-valid string for the given connector,
5
+ * biased so the order stays marketable (BUY rounds down, SELL rounds up),
6
+ * optionally padded with slippage tolerance first.
7
+ */
3
8
  export declare function precisePrice(connector: Connector, raw: BigNumber.Value, precision: InstrumentPrecision, side?: Side, slippageToleranceBps?: number): string;
@@ -1,3 +1,7 @@
1
1
  import type { Connector, InstrumentPrecision, Side } from '@pear-protocol/types';
2
2
  import BigNumber from 'bignumber.js';
3
+ /**
4
+ * Round a raw quantity to an exchange-valid string for the given connector
5
+ * (BUY rounds down, SELL rounds up).
6
+ */
3
7
  export declare function preciseQuantity(connector: Connector, raw: BigNumber.Value, precision: InstrumentPrecision, side: Side): string;
@@ -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 {};
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Detecting baskets — grouping raw external fills into the orders they were
3
+ * placed as, each combined into per-symbol legs with FIFO queues for splitting
4
+ * across positions.
5
+ */
6
+ import type { HttpTypes, Side } from '@pear-protocol/types';
7
+ import type { FillPart } from './types';
8
+ /**
9
+ * A detected order: its legs combined by symbol+side. Legs carry per-source FIFO
10
+ * queues, so {@link CombinedLeg.take} can split a single exchange fill across
11
+ * several positions.
12
+ */
13
+ export declare class Basket {
14
+ readonly legs: CombinedLeg[];
15
+ private constructor();
16
+ /** Detect baskets from a fill stream, returned in ascending time order. */
17
+ static detect(fills: HttpTypes.UnknownExternalFill[]): Basket[];
18
+ /** Active long symbol set the matcher compares against open positions. */
19
+ get longs(): Set<string>;
20
+ /** Active short symbol set the matcher compares against open positions. */
21
+ get shorts(): Set<string>;
22
+ }
23
+ /**
24
+ * One combined symbol+side leg of a basket. Owns the per-source FIFO queue, so
25
+ * taking quantity from the leg (splitting fills across positions) is a method on
26
+ * its own state instead of an outside helper mutating a plain object.
27
+ */
28
+ export declare class CombinedLeg {
29
+ readonly symbol: string;
30
+ readonly side: Side;
31
+ private _totalSize;
32
+ private readonly queue;
33
+ constructor(symbol: string, side: Side);
34
+ get totalSize(): BigNumber;
35
+ /** Append a fill to this leg's queue and grow the total size. */
36
+ addFill(fill: HttpTypes.UnknownExternalFill): void;
37
+ /**
38
+ * Take `qty` of fill slices from this leg (FIFO across source fills, consumes the queue).
39
+ * Returns the parts taken, which are consumed from the queue.
40
+ */
41
+ take(qty: BigNumber): FillPart[];
42
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { HttpTypes } from '@pear-protocol/types';
2
+ import type { BasketRule } from './types';
3
+ /**
4
+ * Float-precision dust floor. Exchange quantities are exact decimal strings and the
5
+ * whole matcher is BigNumber, so a real full close nets to exact zero. This floor only
6
+ * absorbs sub-1e-8 dust that a stray float (anywhere upstream) could leave behind —
7
+ * without it, that dust keeps a position "open", so a later reopen of the same pair
8
+ * merges in and every lifecycle collapses onto one position. NOT a per-instrument
9
+ * tolerance: a genuine partial close (>= 1e-8) stays open, as it should.
10
+ */
11
+ export declare const DUST: BigNumber;
12
+ export declare const MISSED_HISTORY_FILL_PREFIX = "opening-synthetic-";
13
+ export declare const BASKET_RULES: BasketRule[];
14
+ /**
15
+ * True when `cloid` is a Pear-originated cloid for the given connector.
16
+ * A missing or empty cloid is always false; connector-specific prefix rules
17
+ * are applied via the `PEAR_CLOID_CHECK` table.
18
+ */
19
+ export declare function isPearCloid(connector: HttpTypes.Connector, cloid: string | null | undefined): boolean;
@@ -0,0 +1,47 @@
1
+ import type { Side, SignedDecimalString } from '@pear-protocol/types';
2
+ import type { FillPart } from './types';
3
+ /**
4
+ * Signed per-asset exposure of a position — the running `symbol -> quantity` book
5
+ * (BUY positive, SELL negative). Wraps the raw map so the signed-quantity math, the
6
+ * DUST rule, and the leg-set queries live in one place instead of scattered helpers.
7
+ *
8
+ * Mutable by design: the matcher mutates a working position's exposure in place while
9
+ * replaying fills. Use {@link clone} when a caller needs an independent copy.
10
+ */
11
+ export declare class Exposure {
12
+ private readonly legs;
13
+ private constructor();
14
+ /** Create an exposure with no legs — the starting state for a new position. */
15
+ static empty(): Exposure;
16
+ /** Build from a wire `Record<symbol, quantity>` (existing DB position exposure). */
17
+ static fromRecord(record: Record<string, string>): Exposure;
18
+ /** Net signed exposure a set of fill parts contributes for `symbol` (BUY +, SELL −). */
19
+ static netFromParts(parts: FillPart[], symbol: string): BigNumber;
20
+ /** Return an independent copy of this exposure so mutations do not alias. */
21
+ clone(): Exposure;
22
+ /** Signed exposure for `symbol` (0 when absent). */
23
+ get(symbol: string): BigNumber;
24
+ /** Apply a signed `delta` to `symbol`, creating the leg if it does not exist. */
25
+ addDelta(symbol: string, delta: BigNumber): void;
26
+ /** Convert a side+quantity pair to a signed delta and forward to `addDelta`. */
27
+ addSigned(symbol: string, quantity: string, side: Side): void;
28
+ /** Active (non-zero) long and short leg sets. */
29
+ activeLegs(): {
30
+ longs: Set<string>;
31
+ shorts: Set<string>;
32
+ };
33
+ /** True once every leg sits within DUST of zero (effectively closed). */
34
+ isFlat(): boolean;
35
+ /** True when every leg is exactly zero (a fully CLOSED position). */
36
+ isAllZero(): boolean;
37
+ /**
38
+ * Snap every non-zero leg to exactly zero. Called once a position is flat so it
39
+ * reads CLOSED (all-zero exposure) and never lingers in the open list with stray
40
+ * dust that a later reopen of the same pair could merge into.
41
+ */
42
+ snapToZero(): void;
43
+ /** All symbols with a leg, including zeroed ones (a closed leg still carries cost basis). */
44
+ symbols(): string[];
45
+ /** Canonical signed-string view for the wire (`+0` for zero). */
46
+ toSigned(): Record<string, SignedDecimalString>;
47
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import type { HttpTypes } from '@pear-protocol/types';
2
+ import type { FillPart, MatchedPosition, PartId } from './types';
3
+ /**
4
+ * A source fill may be split into multiple slices (different positions). Build a
5
+ * stable id per slice: unsplit slices keep the native id; split slices get a
6
+ * deterministic `-pN` suffix plus an `original` pointer to the source.
7
+ * Returns an accessor; every part of every given position has an id by construction.
8
+ */
9
+ export declare function assignPartIds(positions: MatchedPosition[]): (part: FillPart) => PartId;
10
+ /**
11
+ * Convert a matched fill slice into the wire-shape ApplicableFill the server persists.
12
+ * Three shapes emerge: a real external fill, a real Pear fill (cloid we minted), or a
13
+ * missed-history synthetic (external, priced to reconcile with the exchange entry).
14
+ * Fees are quantity-prorated when the source fill was split across positions.
15
+ */
16
+ export declare function partToApplicableFill(part: FillPart, id: PartId, connector: HttpTypes.Connector): HttpTypes.ApplicableFill;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Turn a batch of external fills into `applicables` — fill baskets the server
3
+ * find-or-creates into positions.
4
+ *
5
+ * Each fill is processed against the currently open positions, oldest first:
6
+ * 1. close out an exact-match inverse position (same active legs),
7
+ * 2. else reduce any other positions partially holding that leg,
8
+ * 3. else merge the leftover into an open position already holding those legs, or open a new one.
9
+ * A fill can be split across several positions when it closes more than one.
10
+ *
11
+ * Once fills are assigned, one synthetic per asset covers any remaining gap to the
12
+ * exchange snapshot (the source of truth). Position exposure is NOT computed here —
13
+ * the server sums the assigned fills; the opening cost basis (initialExposureUSD) is
14
+ * derived per position from its own real fills and carried in `metadata`.
15
+ *
16
+ * Pipeline: snapshot → add missed-history fills → match → align with exchange → assemble.
17
+ * Diff outcomes: discover (new fill), gap (residual vs snapshot), gone (netted to zero)
18
+ */
19
+ import type { HttpTypes } from '@pear-protocol/types';
20
+ /**
21
+ * Build the `applicables` bucket: baskets of fills (real + synthetic) keyed by a
22
+ * deterministic positionKey, plus creation metadata. Positions are derived by
23
+ * replaying fills through dynamic matching (exact active-set match, cross-
24
+ * position close, subset-merge of leftover opens), so same-asset exposure
25
+ * consolidates instead of fragmenting per basket symbol-set.
26
+ */
27
+ export declare function buildApplicables(args: {
28
+ tradeAccountId: string;
29
+ connector: HttpTypes.Connector;
30
+ orphanFills: HttpTypes.UnknownExternalFill[];
31
+ openPositions: HttpTypes.OpenPositionLite[];
32
+ entryBySymbol: Record<string, string>;
33
+ exchangePositions: HttpTypes.ExternalPosition[];
34
+ }): HttpTypes.ApplicablePosition[];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import type { HttpTypes } from '@pear-protocol/types';
2
+ import type { PearHoldings } from './snapshot';
3
+ import type { MatchedPosition } from './types';
4
+ /**
5
+ * Replay external fills to decide which position each fill slice belongs to. Flow :
6
+ * - findExactCloseTarget picks the open position whose ACTIVE legs mirror the
7
+ * basket (dead legs ignored) — the basket closes that position first,
8
+ * - applyCloses reduces holders (the mirrored target first, then any other),
9
+ * - resolveLeftoverTarget subset-merges into a compatible position, else opens
10
+ * new (same-shape ADDs consolidate here).
11
+ * The asset SET of a position never grows, so positionKey == key(opening symbols).
12
+ */
13
+ export declare function matchPositions(args: {
14
+ tradeAccountId: string;
15
+ orphanFills: HttpTypes.UnknownExternalFill[];
16
+ pear: PearHoldings;
17
+ }): MatchedPosition[];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Builds the final `ApplicablePosition` output — folds assigned synthetics into
3
+ * exposure, derives status/origin/metadata.
4
+ */
5
+ import type { HttpTypes } from '@pear-protocol/types';
6
+ import type { MatchedPosition, NeededSynthetic } from './types';
7
+ /**
8
+ * Assemble the final ApplicablePosition records: resolve stable per-slice fill
9
+ * ids once across all positions, then build each applicable (dropping unchanged
10
+ * positions that produced no fills).
11
+ */
12
+ export declare function assembleApplicables(args: {
13
+ positions: MatchedPosition[];
14
+ connector: HttpTypes.Connector;
15
+ synthByPosition: Map<MatchedPosition, NeededSynthetic[]>;
16
+ now: number;
17
+ }): HttpTypes.ApplicablePosition[];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ import type { HttpTypes } from '@pear-protocol/types';
2
+ /**
3
+ * The exchange position snapshot — the enforced source of truth for exposure. Wraps the
4
+ * per-symbol lookup so callers ask `snapshot.signedSize(symbol)` instead of threading a
5
+ * raw map plus a sign helper.
6
+ */
7
+ export declare class ExchangeSnapshot {
8
+ private readonly bySymbol;
9
+ private constructor();
10
+ /** Index `positions` by symbol for O(1) lookup. */
11
+ static from(positions: HttpTypes.ExternalPosition[]): ExchangeSnapshot;
12
+ /** Raw exchange position for `symbol`, or `undefined` when the exchange holds nothing. */
13
+ get(symbol: string): HttpTypes.ExternalPosition | undefined;
14
+ /** Signed exchange exposure for `symbol` (0 when the exchange has no position). */
15
+ signedSize(symbol: string): BigNumber;
16
+ /** All symbols the exchange currently reports a position for. */
17
+ symbols(): string[];
18
+ }
19
+ /**
20
+ * What Pear's DB currently holds for one connector — the open positions, filtered
21
+ * once, plus the per-symbol views the synthetic steps need.
22
+ */
23
+ export declare class PearHoldings {
24
+ private readonly filtered;
25
+ private constructor();
26
+ /** Filter `openPositions` to the given connector and wrap. */
27
+ static from(args: {
28
+ openPositions: HttpTypes.OpenPositionLite[];
29
+ connector: HttpTypes.Connector;
30
+ }): PearHoldings;
31
+ /** The open Pear positions for this connector. */
32
+ positions(): HttpTypes.OpenPositionLite[];
33
+ /** Sum existing open Pear exposure per symbol. */
34
+ totalBySymbol(): Map<string, BigNumber>;
35
+ /**
36
+ * Which open DB position holds each active asset — the fallback target for a
37
+ * synthetic when no matched position already carries that asset. First open
38
+ * position with a non-zero leg wins per symbol.
39
+ */
40
+ positionBySymbol(): Map<string, HttpTypes.OpenPositionLite>;
41
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Everything synthetic lives here — the missed-history fills added before matching,
3
+ * the per-asset check against the exchange afterwards, and the assignment of each
4
+ * needed synthetic to one position.
5
+ */
6
+ import type { HttpTypes, Side } from '@pear-protocol/types';
7
+ import type { ExchangeSnapshot, PearHoldings } from './snapshot';
8
+ import type { MatchedPosition, NeededSynthetic } from './types';
9
+ /** True when an external fill was minted by the missed-history pass (its id carries the prefix). */
10
+ export declare function isMissedHistoryFill(fill: HttpTypes.UnknownExternalFill): boolean;
11
+ /**
12
+ * The exchange only returns recent fills — anything older is invisible to us.
13
+ * This adds stand-in (synthetic) fills covering that missed history, so the
14
+ * matcher replays the correct running exposure from the start. Returns the
15
+ * complete fill list, oldest first.
16
+ */
17
+ export declare function addFillsForMissedHistory(args: {
18
+ orphanFills: HttpTypes.UnknownExternalFill[];
19
+ pear: PearHoldings;
20
+ connector: HttpTypes.Connector;
21
+ entryBySymbol: Record<string, string>;
22
+ snapshot: ExchangeSnapshot;
23
+ }): HttpTypes.UnknownExternalFill[];
24
+ /**
25
+ * Compare what we now hold per asset with what the exchange says, and create one
26
+ * correcting synthetic per asset that is off — assigned to exactly one position
27
+ * (creating the position if none holds that asset). This is the safety net for
28
+ * residual drift, the only discovery path when no fills were fetched at all
29
+ * (e.g. first sync of an account whose fill history is beyond the fetch window),
30
+ * and the only close path for positions the exchange dropped without any fill
31
+ * activity this cycle. Returns the position list (extended with any created
32
+ * positions) plus the synthetics per position — the input array is never mutated.
33
+ */
34
+ export declare function alignPositionsWithExchange(args: {
35
+ positions: MatchedPosition[];
36
+ pear: PearHoldings;
37
+ snapshot: ExchangeSnapshot;
38
+ tradeAccountId: string;
39
+ }): {
40
+ positions: MatchedPosition[];
41
+ synthByPosition: Map<MatchedPosition, NeededSynthetic[]>;
42
+ };
43
+ /** Build the content-addressed synthetic fill for a position/asset/side triple. */
44
+ export declare function buildSyntheticFill(args: {
45
+ positionKey: string;
46
+ connector: HttpTypes.Connector;
47
+ symbol: string;
48
+ side: Side;
49
+ quantity: string;
50
+ timestamp: number;
51
+ closedReason?: HttpTypes.PositionClosedReason;
52
+ }): HttpTypes.ApplicableFill;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ import type { HttpTypes, Side } from '@pear-protocol/types';
2
+ import type { CombinedLeg } from './basket';
3
+ import type { Exposure } from './exposure';
4
+ /**
5
+ * A basket-detection rule. Fills for which `matches` is true AND that produce the same
6
+ * `key` are grouped into one basket. Rules are tried in order; the first match wins. A
7
+ * fill matching no rule falls back to the default (its own exchangeFillId), so it never
8
+ * groups with anything else.
9
+ */
10
+ export type BasketRule = {
11
+ matches: (fill: HttpTypes.UnknownExternalFill) => boolean;
12
+ key: (fill: HttpTypes.UnknownExternalFill) => string;
13
+ };
14
+ /**
15
+ * A slice of a source external fill assigned to one virtual position. A single
16
+ * external fill can be split across positions (e.g. a SELL that closes two open
17
+ * longs), so each slice tracks its source for fill-id and fee derivation.
18
+ */
19
+ export type FillPart = {
20
+ source: HttpTypes.UnknownExternalFill;
21
+ quantity: string;
22
+ };
23
+ /**
24
+ * A working position the matcher builds while replaying fills.
25
+ * Mutable mid-replay — its `exposure` is updated in place as fills land.
26
+ */
27
+ export type WorkingPosition = {
28
+ positionKey: string;
29
+ exposure: Exposure;
30
+ parts: FillPart[];
31
+ closed: boolean;
32
+ /** Origin of the seeded existing position; undefined for positions opened mid-replay. */
33
+ existingOrigin?: HttpTypes.PositionOrigin;
34
+ };
35
+ /** A matched position that received fill slices, ready to become an applicable. */
36
+ export type MatchedPosition = {
37
+ positionKey: string;
38
+ parts: FillPart[];
39
+ /** Lifecycle ended within this batch (exposure returned to zero). */
40
+ closed: boolean;
41
+ /**
42
+ * Exposure after replaying real fills (existing position + applied part deltas).
43
+ * Does NOT yet include the correcting synthetics from the align step — assembly
44
+ * folds those in to get the final exposure.
45
+ */
46
+ exposure: Exposure;
47
+ /** Origin of the seeded existing position; undefined when this applicable creates a position. */
48
+ existingOrigin?: HttpTypes.PositionOrigin;
49
+ };
50
+ /** One combined leg that could not be closed against existing exposure — an open. */
51
+ export type Leftover = {
52
+ side: Side;
53
+ qty: BigNumber;
54
+ leg: CombinedLeg;
55
+ };
56
+ /** A per-asset difference vs the exchange: 'gone' = exchange no longer has the asset, 'gap' = quantities differ. */
57
+ export type ExposureGap = {
58
+ kind: 'gone' | 'gap';
59
+ symbol: string;
60
+ side: Side;
61
+ quantity: string;
62
+ };
63
+ /** A synthetic fill the server needs to mint for an asset, plus where to attach it. */
64
+ export type NeededSynthetic = {
65
+ symbol: string;
66
+ side: Side;
67
+ quantity: string;
68
+ closedReason?: HttpTypes.PositionClosedReason;
69
+ };
70
+ /** Per-slice id + `original` pointer, resolved once across ALL positions so split slices stay unique. */
71
+ export type PartId = {
72
+ exchangeFillId: string;
73
+ original?: {
74
+ exchangeFillId: string;
75
+ quantity: string;
76
+ };
77
+ };
@@ -1,19 +1,33 @@
1
1
  import type { HttpTypes } from '@pear-protocol/types';
2
- import type { ComputeSyncPayloadArgs, ComputeSyncPayloadResult, GroupedCreatables, GroupedCreatablesMap, OrphanCreatableExternalFill } from './types';
2
+ import type { ComputeSyncPayloadArgs, ComputeSyncPayloadResult } from './types';
3
+ /**
4
+ * Derive the full sync payload from raw exchange data and local state.
5
+ *
6
+ * Async without internal awaits — the Promise signature is part of the public
7
+ * API consumed by exchanges-sdk, so it stays.
8
+ */
3
9
  export declare function computeSyncPayload(args: ComputeSyncPayloadArgs): Promise<ComputeSyncPayloadResult | null>;
10
+ /**
11
+ * Validate, normalise, deduplicate, and filter raw external fills.
12
+ * Fills with invalid ids/quantities/timestamps or already-confirmed ids are dropped.
13
+ * Duplicates (same exchangeFillId) keep the earliest timestamp; result is ascending-sorted.
14
+ */
4
15
  export declare function _prepareExternalFills(externalFills: HttpTypes.UnknownExternalFill[], confirmedExchangeFillIdsInput: string[]): HttpTypes.UnknownExternalFill[];
5
- export declare function _groupCreatablesBySymbol(orphanCreatables: OrphanCreatableExternalFill[], openPositions: HttpTypes.OpenPositionLite[]): GroupedCreatablesMap;
6
- export declare function _splitMaybeCompositeFillAcrossPositions(fill: OrphanCreatableExternalFill, positionCandidates: [HttpTypes.OpenPositionLite, ...HttpTypes.OpenPositionLite[]]): Array<{
7
- position: HttpTypes.OpenPositionLite;
8
- maybeModifiedFill: OrphanCreatableExternalFill;
9
- }>;
10
- export declare function _walkBackwardsToFindLastZeroCrossing(group: GroupedCreatables): HttpTypes.CreatableExternalFill[];
11
- export declare function _seggregateExternalFills(provisionalFills: HttpTypes.ProvisionalFill[], externalFills: HttpTypes.UnknownExternalFill[]): {
16
+ /**
17
+ * Partition external fills into reconcilables, confirmables, and orphans.
18
+ * Fills whose cloid matches a provisional fill we own go to confirm or reconcile;
19
+ * everything else (no cloid, or cloid we do not own) becomes an orphan.
20
+ */
21
+ export declare function _segregateExternalFills(provisionalFills: HttpTypes.ProvisionalFill[], externalFills: HttpTypes.UnknownExternalFill[]): {
12
22
  reconcilables: HttpTypes.ReconcileFillsRequest;
13
23
  confirmables: HttpTypes.ConfirmableExternalFill[];
14
- orphanCreatables: OrphanCreatableExternalFill[];
24
+ orphans: HttpTypes.UnknownExternalFill[];
15
25
  };
26
+ /** Trim a cloid; empty or whitespace-only collapses to undefined. */
16
27
  export declare function _normalizeCloid(cloid?: string | null): string | undefined;
28
+ /** Normalise an exchange fill id: trim + lowercase; absent or blank → undefined. */
17
29
  export declare function _normalizeExchangeFillId(exchangeFillId?: string | null): string | undefined;
30
+ /** Rejects scientific-notation quantities (e.g. 1e-8) that cannot be safely parsed as fills. */
18
31
  export declare function _isValidExternalQuantity(quantity: string): boolean;
32
+ /** Returns true only when the timestamp is a non-negative finite number. */
19
33
  export declare function _isValidExternalTimestamp(timestamp: number): boolean;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * First-sync history cutoff selection.
3
+ *
4
+ * An account's first sync fetches a fill history that can reach back through many
5
+ * finished lifecycles; replaying all of it would resurrect long-closed positions.
6
+ * This module replays the history BACKWARD from the exchange snapshot (the source
7
+ * of truth for current exposure) and picks the most recent boundary where the
8
+ * account was EXACTLY flat, so everything before it can be dropped. Seeding from
9
+ * the snapshot means a truncated history — a position opened before the fetched
10
+ * window — leaves a residual at every boundary and never produces a fake cutoff.
11
+ */
12
+ import type { HttpTypes } from '@pear-protocol/types';
13
+ /** Trim decision: keep `fills.slice(keepFromIndex)`; `fills.length` trims everything. */
14
+ export type FirstSyncCutoff = {
15
+ keepFromIndex: number;
16
+ };
17
+ /**
18
+ * Selects the first-sync cutoff boundary from an ascending-sorted, prepared fill
19
+ * history and the current exchange snapshot.
20
+ *
21
+ * Boundary i is the account state BEFORE fills[i] (boundary fills.length is the
22
+ * snapshot itself). A boundary qualifies when every leg of the backward-replayed
23
+ * book is exactly zero (BigNumber-exact — the DUST tolerance does NOT apply) and
24
+ * the boundary is not inside a same-millisecond fill group (Hyperliquid merges
25
+ * feeds whose same-ms order is not execution order). On Hyperliquid the cutoff is
26
+ * additionally capped at the first Pear-placed fill so it is always kept.
27
+ *
28
+ * Returns null to keep everything: empty history, no qualifying boundary, or the
29
+ * qualifying boundary is 0 (a cutoff of 0 trims nothing, so it canonicalizes to null).
30
+ */
31
+ export declare function selectFirstSyncCutoff(args: {
32
+ fills: HttpTypes.UnknownExternalFill[];
33
+ exchangePositions: HttpTypes.ExternalPosition[];
34
+ connector: HttpTypes.Connector;
35
+ }): FirstSyncCutoff | null;