@pear-protocol/utils 0.0.23 → 0.1.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/asset/asset.types.d.ts +1 -0
- package/dist/asset/compute.d.ts +9 -1
- package/dist/asset/compute.test.d.ts +1 -0
- package/dist/fills/compute.test.d.ts +1 -0
- package/dist/fills/fill.types.d.ts +1 -0
- package/dist/fills/index.d.ts +1 -0
- package/dist/index.js +1373 -306
- package/dist/math/decimal.d.ts +57 -0
- package/dist/math/decimal.test.d.ts +1 -0
- package/dist/math/index.d.ts +1 -0
- package/dist/position/compute.test.d.ts +1 -0
- package/dist/position/index.d.ts +1 -0
- package/dist/position/key.d.ts +15 -0
- package/dist/precise-price.d.ts +5 -0
- package/dist/precise-quantity.d.ts +4 -0
- package/dist/sync/applicables/basket.d.ts +42 -0
- package/dist/sync/applicables/basket.test.d.ts +1 -0
- package/dist/sync/applicables/constants.d.ts +19 -0
- package/dist/sync/applicables/exposure.d.ts +47 -0
- package/dist/sync/applicables/exposure.test.d.ts +1 -0
- package/dist/sync/applicables/fills.d.ts +16 -0
- package/dist/sync/applicables/fills.test.d.ts +1 -0
- package/dist/sync/applicables/index.d.ts +34 -0
- package/dist/sync/applicables/index.test.d.ts +1 -0
- package/dist/sync/applicables/matching.d.ts +17 -0
- package/dist/sync/applicables/matching.test.d.ts +1 -0
- package/dist/sync/applicables/position.d.ts +17 -0
- package/dist/sync/applicables/position.test.d.ts +1 -0
- package/dist/sync/applicables/snapshot.d.ts +41 -0
- package/dist/sync/applicables/synthetics.d.ts +52 -0
- package/dist/sync/applicables/synthetics.test.d.ts +1 -0
- package/dist/sync/applicables/types.d.ts +77 -0
- package/dist/sync/compute.d.ts +23 -9
- package/dist/sync/cutoff.d.ts +35 -0
- package/dist/sync/cutoff.test.d.ts +1 -0
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/types.d.ts +12 -13
- package/dist/validate-leverage.d.ts +3 -0
- package/dist/validate-quantity.d.ts +4 -0
- 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 {};
|
package/dist/math/index.d.ts
CHANGED
|
@@ -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';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/position/index.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/precise-price.d.ts
CHANGED
|
@@ -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,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
|
+
};
|
package/dist/sync/compute.d.ts
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
import type { HttpTypes } from '@pear-protocol/types';
|
|
2
|
-
import type { ComputeSyncPayloadArgs, ComputeSyncPayloadResult
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export declare function
|
|
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
|
-
|
|
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;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/sync/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { computeSyncPayload } from './compute';
|
|
2
|
-
export type { ComputeSyncPayloadArgs, ComputeSyncPayloadResult
|
|
2
|
+
export type { ComputeSyncPayloadArgs, ComputeSyncPayloadResult } from './types';
|
package/dist/sync/types.d.ts
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
import type { HttpTypes } from '@pear-protocol/types';
|
|
2
|
-
import type BigNumber from 'bignumber.js';
|
|
3
|
-
export type OrphanCreatableExternalFill = Omit<HttpTypes.CreatableExternalFill, 'positionId' | 'type'>;
|
|
4
|
-
export type CompositeGroupKey = `${string}-${string}`;
|
|
5
|
-
export type GroupedCreatables = {
|
|
6
|
-
positionId: string;
|
|
7
|
-
symbol: string;
|
|
8
|
-
exposure: BigNumber;
|
|
9
|
-
fills: [HttpTypes.CreatableExternalFill, ...HttpTypes.CreatableExternalFill[]];
|
|
10
|
-
};
|
|
11
|
-
export type GroupedCreatablesMap = Map<CompositeGroupKey, GroupedCreatables>;
|
|
12
2
|
export type ComputeSyncPayloadArgs = {
|
|
3
|
+
tradeAccountId: string;
|
|
4
|
+
connector: HttpTypes.Connector;
|
|
13
5
|
openPositions: HttpTypes.OpenPositionLite[];
|
|
6
|
+
entryBySymbol: Record<string, string>;
|
|
14
7
|
provisionalFills: HttpTypes.ProvisionalFill[];
|
|
15
8
|
confirmedExchangeFillIds: string[];
|
|
16
9
|
externalFills: HttpTypes.UnknownExternalFill[];
|
|
10
|
+
exchangePositions: HttpTypes.ExternalPosition[];
|
|
11
|
+
neverSynced: boolean;
|
|
17
12
|
};
|
|
18
13
|
export type ComputeSyncPayloadResult = {
|
|
19
14
|
reconcilables: HttpTypes.ReconcileFillsRequest | null;
|
|
20
15
|
confirmables: HttpTypes.ConfirmFillsRequest | null;
|
|
21
|
-
|
|
16
|
+
applicables: HttpTypes.ApplicablePosition[];
|
|
17
|
+
exchangePositions: HttpTypes.ExternalPosition[];
|
|
22
18
|
meta: {
|
|
23
|
-
externalMinTs: number | null;
|
|
24
19
|
externalMaxTs: number | null;
|
|
25
|
-
processedCount: number;
|
|
26
20
|
};
|
|
21
|
+
firstSyncTrim: {
|
|
22
|
+
cutoffTs: number | null;
|
|
23
|
+
trimmedCount: number;
|
|
24
|
+
trimmedMaxTs: number;
|
|
25
|
+
} | null;
|
|
27
26
|
};
|