@pear-protocol/utils 1.6.1 → 1.7.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 +2 -1
- package/dist/index.js +214 -1364
- package/dist/market-reference-price.d.ts +52 -0
- package/dist/precise-quantity-with-mode.d.ts +21 -0
- package/dist/vaults/action-typed-data.d.ts +18 -1
- package/package.json +2 -2
- package/dist/sync/applicables/basket.d.ts +0 -39
- package/dist/sync/applicables/constants.d.ts +0 -26
- package/dist/sync/applicables/exposure.d.ts +0 -39
- package/dist/sync/applicables/fills.d.ts +0 -3
- package/dist/sync/applicables/index.d.ts +0 -37
- package/dist/sync/applicables/matching.d.ts +0 -20
- package/dist/sync/applicables/position.d.ts +0 -19
- package/dist/sync/applicables/snapshot.d.ts +0 -41
- package/dist/sync/applicables/synthetics.d.ts +0 -53
- package/dist/sync/applicables/types.d.ts +0 -82
- package/dist/sync/compute.d.ts +0 -28
- package/dist/sync/cutoff.d.ts +0 -35
- package/dist/sync/index.d.ts +0 -3
- package/dist/sync/types.d.ts +0 -25
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import BigNumber7 from 'bignumber.js';
|
|
2
2
|
import { sha256 } from '@noble/hashes/sha2';
|
|
3
3
|
import { bytesToHex, utf8ToBytes, randomBytes } from '@noble/hashes/utils';
|
|
4
|
-
import { WATERMARK_FUTURE_SKEW_MS } from '@pear-protocol/types';
|
|
5
4
|
import { keccak_256 } from '@noble/hashes/sha3';
|
|
6
5
|
import { base64urlnopad } from '@scure/base';
|
|
7
6
|
|
|
@@ -1011,6 +1010,168 @@ function computeRealizedForSymbol(fills) {
|
|
|
1011
1010
|
return realized;
|
|
1012
1011
|
}
|
|
1013
1012
|
__name(computeRealizedForSymbol, "computeRealizedForSymbol");
|
|
1013
|
+
BigNumber7.config({
|
|
1014
|
+
DECIMAL_PLACES: 30,
|
|
1015
|
+
ROUNDING_MODE: BigNumber7.ROUND_DOWN
|
|
1016
|
+
});
|
|
1017
|
+
var InvalidPriceError = class extends Error {
|
|
1018
|
+
static {
|
|
1019
|
+
__name(this, "InvalidPriceError");
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
function precisePrice(connector, raw, precision, side = "BUY", slippageToleranceBps = 0, rounding = "marketable") {
|
|
1023
|
+
const normalized = normalizePrice(raw);
|
|
1024
|
+
if (!normalized.isFinite() || !normalized.isGreaterThan(0)) {
|
|
1025
|
+
throw new InvalidPriceError(`Price ${String(raw)} is not a positive, finite price`);
|
|
1026
|
+
}
|
|
1027
|
+
const effectiveRaw = withSlippageTolerance(normalized, slippageToleranceBps, side);
|
|
1028
|
+
const roundingMode = resolveRoundingMode(side, rounding);
|
|
1029
|
+
const precise = preciseOnGrid(connector, effectiveRaw, precision, roundingMode);
|
|
1030
|
+
const clampsToOneStep = rounding === "marketable" && side === "SELL" && !new BigNumber7(precise).isGreaterThan(0);
|
|
1031
|
+
const result = clampsToOneStep ? smallestGridPrice(connector, precision) : precise;
|
|
1032
|
+
const resultBN = new BigNumber7(result);
|
|
1033
|
+
if (!resultBN.isFinite() || !resultBN.isGreaterThan(0)) {
|
|
1034
|
+
throw new InvalidPriceError(`Price ${effectiveRaw.toFixed()} has no valid ${side} price on the grid`);
|
|
1035
|
+
}
|
|
1036
|
+
return result;
|
|
1037
|
+
}
|
|
1038
|
+
__name(precisePrice, "precisePrice");
|
|
1039
|
+
function preciseOnGrid(connector, price, precision, rounding) {
|
|
1040
|
+
switch (connector) {
|
|
1041
|
+
case "hyperliquid":
|
|
1042
|
+
return preciseHyperliquidPrice(price, precision, rounding);
|
|
1043
|
+
case "binance":
|
|
1044
|
+
case "bybit":
|
|
1045
|
+
case "okx":
|
|
1046
|
+
case "lighter":
|
|
1047
|
+
case "lighter_rhc":
|
|
1048
|
+
return preciseTickGridPrice(price, precision, rounding);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
__name(preciseOnGrid, "preciseOnGrid");
|
|
1052
|
+
function smallestGridPrice(connector, precision) {
|
|
1053
|
+
return preciseOnGrid(connector, new BigNumber7(1).shiftedBy(-precision.price.decimals), precision, BigNumber7.ROUND_CEIL);
|
|
1054
|
+
}
|
|
1055
|
+
__name(smallestGridPrice, "smallestGridPrice");
|
|
1056
|
+
function resolveRoundingMode(side, rounding) {
|
|
1057
|
+
switch (rounding) {
|
|
1058
|
+
// Towards crossing: the buffer survives rounding.
|
|
1059
|
+
case "marketable":
|
|
1060
|
+
return side === "BUY" ? BigNumber7.ROUND_CEIL : BigNumber7.ROUND_FLOOR;
|
|
1061
|
+
// Away from crossing: the limit holds.
|
|
1062
|
+
case "within_limit":
|
|
1063
|
+
return side === "BUY" ? BigNumber7.ROUND_FLOOR : BigNumber7.ROUND_CEIL;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
__name(resolveRoundingMode, "resolveRoundingMode");
|
|
1067
|
+
function withSlippageTolerance(normalized, slippageToleranceBps, side) {
|
|
1068
|
+
if (slippageToleranceBps === 0) {
|
|
1069
|
+
return normalized;
|
|
1070
|
+
}
|
|
1071
|
+
const bps = new BigNumber7(slippageToleranceBps);
|
|
1072
|
+
const factor = bps.div(1e4);
|
|
1073
|
+
return side === "BUY" ? normalized.times(new BigNumber7(1).plus(factor)) : normalized.times(new BigNumber7(1).minus(factor));
|
|
1074
|
+
}
|
|
1075
|
+
__name(withSlippageTolerance, "withSlippageTolerance");
|
|
1076
|
+
function preciseHyperliquidPrice(price, precision, rounding) {
|
|
1077
|
+
const maxDecimals = precision.price.decimals;
|
|
1078
|
+
const maxSigFigs = precision.price.sigFigs ?? 5;
|
|
1079
|
+
price = price.decimalPlaces(maxDecimals, rounding);
|
|
1080
|
+
price = clampSigFigs(price, maxSigFigs, maxDecimals, rounding);
|
|
1081
|
+
return price.toFixed();
|
|
1082
|
+
}
|
|
1083
|
+
__name(preciseHyperliquidPrice, "preciseHyperliquidPrice");
|
|
1084
|
+
function preciseTickGridPrice(price, precision, rounding) {
|
|
1085
|
+
const tick = precision.price.tick ?? 1e-4;
|
|
1086
|
+
const tickBN = new BigNumber7(tick);
|
|
1087
|
+
const scaled = price.div(tickBN);
|
|
1088
|
+
const snapped = scaled.integerValue(rounding).times(tickBN);
|
|
1089
|
+
const maxDecimals = precision.price.decimals;
|
|
1090
|
+
const final = snapped.decimalPlaces(maxDecimals, rounding);
|
|
1091
|
+
return final.toFixed();
|
|
1092
|
+
}
|
|
1093
|
+
__name(preciseTickGridPrice, "preciseTickGridPrice");
|
|
1094
|
+
function clampSigFigs(price, maxSigFigs, maxDecimals, rounding) {
|
|
1095
|
+
const countSigFigs = /* @__PURE__ */ __name((value) => value.toFixed().replace(".", "").replace(/^0+/, "").length, "countSigFigs");
|
|
1096
|
+
if (countSigFigs(price) <= maxSigFigs) {
|
|
1097
|
+
return price;
|
|
1098
|
+
}
|
|
1099
|
+
for (let decimals = maxDecimals; decimals >= 1; decimals--) {
|
|
1100
|
+
const candidate = price.decimalPlaces(decimals, rounding);
|
|
1101
|
+
if (countSigFigs(candidate) <= maxSigFigs) {
|
|
1102
|
+
return candidate;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return price.decimalPlaces(0, rounding);
|
|
1106
|
+
}
|
|
1107
|
+
__name(clampSigFigs, "clampSigFigs");
|
|
1108
|
+
function normalizePrice(value) {
|
|
1109
|
+
const parsed = new BigNumber7(value);
|
|
1110
|
+
return new BigNumber7(parsed.toFixed());
|
|
1111
|
+
}
|
|
1112
|
+
__name(normalizePrice, "normalizePrice");
|
|
1113
|
+
|
|
1114
|
+
// src/market-reference-price.ts
|
|
1115
|
+
function resolveMarketLimitPrice(connector, quote, precision, side, slippageToleranceBps, pricing) {
|
|
1116
|
+
const reference = resolveMarketReference(quote, side, pricing);
|
|
1117
|
+
return precisePrice(connector, reference.price, precision, side, slippageToleranceBps, reference.rounding);
|
|
1118
|
+
}
|
|
1119
|
+
__name(resolveMarketLimitPrice, "resolveMarketLimitPrice");
|
|
1120
|
+
function resolveMarketReference(quote, side, pricing) {
|
|
1121
|
+
const reference = parse(quote.p);
|
|
1122
|
+
const touch = resolveUsableTouch(quote, reference);
|
|
1123
|
+
if (pricing === "mid" || !touch) {
|
|
1124
|
+
return {
|
|
1125
|
+
price: reference.toFixed(),
|
|
1126
|
+
rounding: "marketable"
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
switch (side) {
|
|
1130
|
+
case "BUY":
|
|
1131
|
+
return {
|
|
1132
|
+
price: touch.ask.toFixed(),
|
|
1133
|
+
rounding: "within_limit"
|
|
1134
|
+
};
|
|
1135
|
+
case "SELL":
|
|
1136
|
+
return {
|
|
1137
|
+
price: touch.bid.toFixed(),
|
|
1138
|
+
rounding: "within_limit"
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
__name(resolveMarketReference, "resolveMarketReference");
|
|
1143
|
+
function marketPricingFor(origin) {
|
|
1144
|
+
switch (origin) {
|
|
1145
|
+
case "direct":
|
|
1146
|
+
case "copy":
|
|
1147
|
+
case "manual-rebalance":
|
|
1148
|
+
return "touch";
|
|
1149
|
+
case "trigger":
|
|
1150
|
+
case "schedule":
|
|
1151
|
+
case "auto-rebalance":
|
|
1152
|
+
case "vault-liquidity":
|
|
1153
|
+
return "mid";
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
__name(marketPricingFor, "marketPricingFor");
|
|
1157
|
+
function resolveUsableTouch(quote, reference) {
|
|
1158
|
+
if (quote.bid === null || quote.ask === null) {
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
if (!isFiniteDecimal(quote.bid) || !isFiniteDecimal(quote.ask)) {
|
|
1162
|
+
return null;
|
|
1163
|
+
}
|
|
1164
|
+
const bid = parse(quote.bid);
|
|
1165
|
+
const ask = parse(quote.ask);
|
|
1166
|
+
if (!bid.isGreaterThan(0) || !bid.isLessThan(ask) || reference.isLessThan(bid) || reference.isGreaterThan(ask)) {
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
return {
|
|
1170
|
+
bid,
|
|
1171
|
+
ask
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
__name(resolveUsableTouch, "resolveUsableTouch");
|
|
1014
1175
|
|
|
1015
1176
|
// src/position/compute.ts
|
|
1016
1177
|
function computeUnrealizedPositionState(exposureMap, priceMap, fills) {
|
|
@@ -1188,106 +1349,6 @@ BigNumber7.config({
|
|
|
1188
1349
|
DECIMAL_PLACES: 30,
|
|
1189
1350
|
ROUNDING_MODE: BigNumber7.ROUND_DOWN
|
|
1190
1351
|
});
|
|
1191
|
-
var InvalidPriceError = class extends Error {
|
|
1192
|
-
static {
|
|
1193
|
-
__name(this, "InvalidPriceError");
|
|
1194
|
-
}
|
|
1195
|
-
};
|
|
1196
|
-
function precisePrice(connector, raw, precision, side = "BUY", slippageToleranceBps = 0, rounding = "marketable") {
|
|
1197
|
-
const normalized = normalizePrice(raw);
|
|
1198
|
-
if (!normalized.isFinite() || !normalized.isGreaterThan(0)) {
|
|
1199
|
-
throw new InvalidPriceError(`Price ${String(raw)} is not a positive, finite price`);
|
|
1200
|
-
}
|
|
1201
|
-
const effectiveRaw = withSlippageTolerance(normalized, slippageToleranceBps, side);
|
|
1202
|
-
const roundingMode = resolveRoundingMode(side, rounding);
|
|
1203
|
-
const precise = preciseOnGrid(connector, effectiveRaw, precision, roundingMode);
|
|
1204
|
-
const clampsToOneStep = rounding === "marketable" && side === "SELL" && !new BigNumber7(precise).isGreaterThan(0);
|
|
1205
|
-
const result = clampsToOneStep ? smallestGridPrice(connector, precision) : precise;
|
|
1206
|
-
const resultBN = new BigNumber7(result);
|
|
1207
|
-
if (!resultBN.isFinite() || !resultBN.isGreaterThan(0)) {
|
|
1208
|
-
throw new InvalidPriceError(`Price ${effectiveRaw.toFixed()} has no valid ${side} price on the grid`);
|
|
1209
|
-
}
|
|
1210
|
-
return result;
|
|
1211
|
-
}
|
|
1212
|
-
__name(precisePrice, "precisePrice");
|
|
1213
|
-
function preciseOnGrid(connector, price, precision, rounding) {
|
|
1214
|
-
switch (connector) {
|
|
1215
|
-
case "hyperliquid":
|
|
1216
|
-
return preciseHyperliquidPrice(price, precision, rounding);
|
|
1217
|
-
case "binance":
|
|
1218
|
-
case "bybit":
|
|
1219
|
-
case "okx":
|
|
1220
|
-
case "lighter":
|
|
1221
|
-
case "lighter_rhc":
|
|
1222
|
-
return preciseTickGridPrice(price, precision, rounding);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
__name(preciseOnGrid, "preciseOnGrid");
|
|
1226
|
-
function smallestGridPrice(connector, precision) {
|
|
1227
|
-
return preciseOnGrid(connector, new BigNumber7(1).shiftedBy(-precision.price.decimals), precision, BigNumber7.ROUND_CEIL);
|
|
1228
|
-
}
|
|
1229
|
-
__name(smallestGridPrice, "smallestGridPrice");
|
|
1230
|
-
function resolveRoundingMode(side, rounding) {
|
|
1231
|
-
switch (rounding) {
|
|
1232
|
-
// Towards crossing: the buffer survives rounding.
|
|
1233
|
-
case "marketable":
|
|
1234
|
-
return side === "BUY" ? BigNumber7.ROUND_CEIL : BigNumber7.ROUND_FLOOR;
|
|
1235
|
-
// Away from crossing: the limit holds.
|
|
1236
|
-
case "within_limit":
|
|
1237
|
-
return side === "BUY" ? BigNumber7.ROUND_FLOOR : BigNumber7.ROUND_CEIL;
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
__name(resolveRoundingMode, "resolveRoundingMode");
|
|
1241
|
-
function withSlippageTolerance(normalized, slippageToleranceBps, side) {
|
|
1242
|
-
if (slippageToleranceBps === 0) {
|
|
1243
|
-
return normalized;
|
|
1244
|
-
}
|
|
1245
|
-
const bps = new BigNumber7(slippageToleranceBps);
|
|
1246
|
-
const factor = bps.div(1e4);
|
|
1247
|
-
return side === "BUY" ? normalized.times(new BigNumber7(1).plus(factor)) : normalized.times(new BigNumber7(1).minus(factor));
|
|
1248
|
-
}
|
|
1249
|
-
__name(withSlippageTolerance, "withSlippageTolerance");
|
|
1250
|
-
function preciseHyperliquidPrice(price, precision, rounding) {
|
|
1251
|
-
const maxDecimals = precision.price.decimals;
|
|
1252
|
-
const maxSigFigs = precision.price.sigFigs ?? 5;
|
|
1253
|
-
price = price.decimalPlaces(maxDecimals, rounding);
|
|
1254
|
-
price = clampSigFigs(price, maxSigFigs, maxDecimals, rounding);
|
|
1255
|
-
return price.toFixed();
|
|
1256
|
-
}
|
|
1257
|
-
__name(preciseHyperliquidPrice, "preciseHyperliquidPrice");
|
|
1258
|
-
function preciseTickGridPrice(price, precision, rounding) {
|
|
1259
|
-
const tick = precision.price.tick ?? 1e-4;
|
|
1260
|
-
const tickBN = new BigNumber7(tick);
|
|
1261
|
-
const scaled = price.div(tickBN);
|
|
1262
|
-
const snapped = scaled.integerValue(rounding).times(tickBN);
|
|
1263
|
-
const maxDecimals = precision.price.decimals;
|
|
1264
|
-
const final = snapped.decimalPlaces(maxDecimals, rounding);
|
|
1265
|
-
return final.toFixed();
|
|
1266
|
-
}
|
|
1267
|
-
__name(preciseTickGridPrice, "preciseTickGridPrice");
|
|
1268
|
-
function clampSigFigs(price, maxSigFigs, maxDecimals, rounding) {
|
|
1269
|
-
const countSigFigs = /* @__PURE__ */ __name((value) => value.toFixed().replace(".", "").replace(/^0+/, "").length, "countSigFigs");
|
|
1270
|
-
if (countSigFigs(price) <= maxSigFigs) {
|
|
1271
|
-
return price;
|
|
1272
|
-
}
|
|
1273
|
-
for (let decimals = maxDecimals; decimals >= 1; decimals--) {
|
|
1274
|
-
const candidate = price.decimalPlaces(decimals, rounding);
|
|
1275
|
-
if (countSigFigs(candidate) <= maxSigFigs) {
|
|
1276
|
-
return candidate;
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
return price.decimalPlaces(0, rounding);
|
|
1280
|
-
}
|
|
1281
|
-
__name(clampSigFigs, "clampSigFigs");
|
|
1282
|
-
function normalizePrice(value) {
|
|
1283
|
-
const parsed = new BigNumber7(value);
|
|
1284
|
-
return new BigNumber7(parsed.toFixed());
|
|
1285
|
-
}
|
|
1286
|
-
__name(normalizePrice, "normalizePrice");
|
|
1287
|
-
BigNumber7.config({
|
|
1288
|
-
DECIMAL_PLACES: 30,
|
|
1289
|
-
ROUNDING_MODE: BigNumber7.ROUND_DOWN
|
|
1290
|
-
});
|
|
1291
1352
|
function preciseQuantity(connector, raw, precision, side, rounding = "side") {
|
|
1292
1353
|
const roundingMode = resolveRoundingMode2(side, rounding);
|
|
1293
1354
|
switch (connector) {
|
|
@@ -1329,6 +1390,20 @@ function preciseStepGridQuantity(raw, precision, roundingMode) {
|
|
|
1329
1390
|
}
|
|
1330
1391
|
__name(preciseStepGridQuantity, "preciseStepGridQuantity");
|
|
1331
1392
|
|
|
1393
|
+
// src/precise-quantity-with-mode.ts
|
|
1394
|
+
function preciseQuantityWithMode(connector, leg, precision, referencePrice, constrainUsdQuantity = false) {
|
|
1395
|
+
switch (leg.mode) {
|
|
1396
|
+
case "QUANTITY":
|
|
1397
|
+
return preciseQuantity(connector, leg.amount, precision, leg.side);
|
|
1398
|
+
case "USD": {
|
|
1399
|
+
const quantity = divide(leg.amount, referencePrice);
|
|
1400
|
+
const roundingSide = constrainUsdQuantity && !leg.reduceOnly ? "BUY" : leg.side;
|
|
1401
|
+
return preciseQuantity(connector, quantity, precision, roundingSide);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
__name(preciseQuantityWithMode, "preciseQuantityWithMode");
|
|
1406
|
+
|
|
1332
1407
|
// src/proxy.ts
|
|
1333
1408
|
var activeOrigin = null;
|
|
1334
1409
|
function configureProxy(config) {
|
|
@@ -1451,1271 +1526,15 @@ function isRefusedBelowMinimum(quantity, price, precision, isPartialVenueReduce)
|
|
|
1451
1526
|
}
|
|
1452
1527
|
__name(isRefusedBelowMinimum, "isRefusedBelowMinimum");
|
|
1453
1528
|
|
|
1454
|
-
// src/
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
var MISSED_HISTORY_FILL_PREFIX = "opening-synthetic-";
|
|
1458
|
-
var BASKET_RULES = [
|
|
1459
|
-
// V2 Basket Hyperliquid — legs of one Pear basket order share the order's cloid.
|
|
1460
|
-
{
|
|
1461
|
-
matches: /* @__PURE__ */ __name((fill) => fill.connector === "hyperliquid" && isPearCloid(fill.connector, fill.cloid), "matches"),
|
|
1462
|
-
key: /* @__PURE__ */ __name((fill) => `v2-basket-hl ${fill.cloid}`, "key")
|
|
1463
|
-
}
|
|
1464
|
-
];
|
|
1465
|
-
function isPearCloid(connector, cloid) {
|
|
1466
|
-
if (!cloid) {
|
|
1529
|
+
// src/validate-leverage.ts
|
|
1530
|
+
function validateLeverage(leverage, instrument) {
|
|
1531
|
+
if (leverage < 1 || leverage > 100) {
|
|
1467
1532
|
return false;
|
|
1468
1533
|
}
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
__name(isPearCloid, "isPearCloid");
|
|
1472
|
-
var PEAR_CLOID_CHECK = {
|
|
1473
|
-
hyperliquid: /* @__PURE__ */ __name((cloid) => cloid.startsWith("0x50454152"), "hyperliquid"),
|
|
1474
|
-
lighter: isLighterPearCloid,
|
|
1475
|
-
lighter_rhc: isLighterPearCloid,
|
|
1476
|
-
// No Pear brand is minted into a CEX client order id, so provenance cannot be read from it.
|
|
1477
|
-
binance: /* @__PURE__ */ __name(() => false, "binance"),
|
|
1478
|
-
bybit: /* @__PURE__ */ __name(() => false, "bybit"),
|
|
1479
|
-
okx: /* @__PURE__ */ __name(() => false, "okx")
|
|
1480
|
-
};
|
|
1481
|
-
function isLighterPearCloid(cloid) {
|
|
1482
|
-
return cloid.length === 15 && cloid.startsWith("10");
|
|
1483
|
-
}
|
|
1484
|
-
__name(isLighterPearCloid, "isLighterPearCloid");
|
|
1485
|
-
|
|
1486
|
-
// src/sync/applicables/basket.ts
|
|
1487
|
-
var Basket = class _Basket {
|
|
1488
|
-
static {
|
|
1489
|
-
__name(this, "Basket");
|
|
1490
|
-
}
|
|
1491
|
-
legs;
|
|
1492
|
-
fills;
|
|
1493
|
-
constructor(legs, fills) {
|
|
1494
|
-
this.legs = legs;
|
|
1495
|
-
this.fills = fills;
|
|
1496
|
-
}
|
|
1497
|
-
/** Detect baskets from a fill stream, returned in ascending time order. */
|
|
1498
|
-
static detect(fills) {
|
|
1499
|
-
return groupFillsIntoBaskets(fills).map((group) => new _Basket(combineFillsBySymbolAndSide(group), group));
|
|
1500
|
-
}
|
|
1501
|
-
};
|
|
1502
|
-
var CombinedLeg = class {
|
|
1503
|
-
static {
|
|
1504
|
-
__name(this, "CombinedLeg");
|
|
1505
|
-
}
|
|
1506
|
-
symbol;
|
|
1507
|
-
side;
|
|
1508
|
-
_totalSize = ZERO;
|
|
1509
|
-
queue = [];
|
|
1510
|
-
constructor(symbol, side) {
|
|
1511
|
-
this.symbol = symbol;
|
|
1512
|
-
this.side = side;
|
|
1513
|
-
}
|
|
1514
|
-
get totalSize() {
|
|
1515
|
-
return this._totalSize;
|
|
1516
|
-
}
|
|
1517
|
-
/** Append a fill to this leg's queue and grow the total size. */
|
|
1518
|
-
addFill(fill) {
|
|
1519
|
-
this._totalSize = this._totalSize.plus(fill.quantity);
|
|
1520
|
-
this.queue.push({
|
|
1521
|
-
fill,
|
|
1522
|
-
remaining: parse(fill.quantity)
|
|
1523
|
-
});
|
|
1524
|
-
}
|
|
1525
|
-
/**
|
|
1526
|
-
* Take `qty` of fill slices from this leg (FIFO across source fills, consumes the queue).
|
|
1527
|
-
* Returns the parts taken, which are consumed from the queue.
|
|
1528
|
-
*/
|
|
1529
|
-
take(qty) {
|
|
1530
|
-
const parts = [];
|
|
1531
|
-
let need = qty;
|
|
1532
|
-
for (const entry of this.queue) {
|
|
1533
|
-
if (need.isLessThanOrEqualTo(0)) {
|
|
1534
|
-
break;
|
|
1535
|
-
}
|
|
1536
|
-
if (entry.remaining.isZero()) {
|
|
1537
|
-
continue;
|
|
1538
|
-
}
|
|
1539
|
-
const taken = min(entry.remaining, need);
|
|
1540
|
-
parts.push({
|
|
1541
|
-
source: entry.fill,
|
|
1542
|
-
quantity: taken.toFixed()
|
|
1543
|
-
});
|
|
1544
|
-
entry.remaining = entry.remaining.minus(taken);
|
|
1545
|
-
need = need.minus(taken);
|
|
1546
|
-
}
|
|
1547
|
-
return parts;
|
|
1548
|
-
}
|
|
1549
|
-
};
|
|
1550
|
-
function groupFillsIntoBaskets(fills) {
|
|
1551
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1552
|
-
for (const fill of fills) {
|
|
1553
|
-
const rule = BASKET_RULES.find((r) => r.matches(fill));
|
|
1554
|
-
const key = rule ? rule.key(fill) : `id ${fill.exchangeFillId}`;
|
|
1555
|
-
const bucket = buckets.get(key) ?? {
|
|
1556
|
-
timestamp: fill.timestamp,
|
|
1557
|
-
fills: []
|
|
1558
|
-
};
|
|
1559
|
-
bucket.timestamp = Math.min(bucket.timestamp, fill.timestamp);
|
|
1560
|
-
bucket.fills.push(fill);
|
|
1561
|
-
buckets.set(key, bucket);
|
|
1562
|
-
}
|
|
1563
|
-
const sorted = [
|
|
1564
|
-
...buckets.values()
|
|
1565
|
-
].sort((a, b) => a.timestamp - b.timestamp);
|
|
1566
|
-
return sorted.map((bucket) => bucket.fills);
|
|
1567
|
-
}
|
|
1568
|
-
__name(groupFillsIntoBaskets, "groupFillsIntoBaskets");
|
|
1569
|
-
function combineFillsBySymbolAndSide(group) {
|
|
1570
|
-
const map = /* @__PURE__ */ new Map();
|
|
1571
|
-
for (const fill of group) {
|
|
1572
|
-
const key = `${fill.symbol}-${fill.side}`;
|
|
1573
|
-
let combined = map.get(key);
|
|
1574
|
-
if (!combined) {
|
|
1575
|
-
combined = new CombinedLeg(fill.symbol, fill.side);
|
|
1576
|
-
map.set(key, combined);
|
|
1577
|
-
}
|
|
1578
|
-
combined.addFill(fill);
|
|
1534
|
+
if (leverage > instrument.leverage) {
|
|
1535
|
+
return false;
|
|
1579
1536
|
}
|
|
1580
|
-
return
|
|
1581
|
-
...map.values()
|
|
1582
|
-
];
|
|
1583
|
-
}
|
|
1584
|
-
__name(combineFillsBySymbolAndSide, "combineFillsBySymbolAndSide");
|
|
1585
|
-
|
|
1586
|
-
// src/sync/applicables/exposure.ts
|
|
1587
|
-
var Exposure = class _Exposure {
|
|
1588
|
-
static {
|
|
1589
|
-
__name(this, "Exposure");
|
|
1590
|
-
}
|
|
1591
|
-
legs;
|
|
1592
|
-
constructor(legs) {
|
|
1593
|
-
this.legs = legs;
|
|
1594
|
-
}
|
|
1595
|
-
/** Create an exposure with no legs — the starting state for a new position. */
|
|
1596
|
-
static empty() {
|
|
1597
|
-
return new _Exposure(/* @__PURE__ */ new Map());
|
|
1598
|
-
}
|
|
1599
|
-
/** Build from a wire `Record<symbol, quantity>` (existing DB position exposure). */
|
|
1600
|
-
static fromRecord(record) {
|
|
1601
|
-
const legs = /* @__PURE__ */ new Map();
|
|
1602
|
-
for (const [symbol, qty] of Object.entries(record)) {
|
|
1603
|
-
legs.set(symbol, parse(qty));
|
|
1604
|
-
}
|
|
1605
|
-
return new _Exposure(legs);
|
|
1606
|
-
}
|
|
1607
|
-
/** Net signed exposure a set of fill parts contributes for `symbol` (BUY +, SELL −). */
|
|
1608
|
-
static netFromParts(parts, symbol) {
|
|
1609
|
-
let total = ZERO;
|
|
1610
|
-
for (const part of parts) {
|
|
1611
|
-
if (part.source.symbol === symbol) {
|
|
1612
|
-
total = total.plus(signedBySide(part.quantity, part.source.side));
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
return total;
|
|
1616
|
-
}
|
|
1617
|
-
/** Return an independent copy of this exposure so mutations do not alias. */
|
|
1618
|
-
clone() {
|
|
1619
|
-
return new _Exposure(new Map(this.legs));
|
|
1620
|
-
}
|
|
1621
|
-
/** Signed exposure for `symbol` (0 when absent). */
|
|
1622
|
-
get(symbol) {
|
|
1623
|
-
return this.legs.get(symbol) ?? ZERO;
|
|
1624
|
-
}
|
|
1625
|
-
/** Apply a signed `delta` to `symbol`, creating the leg if it does not exist. */
|
|
1626
|
-
addDelta(symbol, delta) {
|
|
1627
|
-
this.legs.set(symbol, this.get(symbol).plus(delta));
|
|
1628
|
-
}
|
|
1629
|
-
/** Convert a side+quantity pair to a signed delta and forward to `addDelta`. */
|
|
1630
|
-
addSigned(symbol, quantity, side) {
|
|
1631
|
-
this.addDelta(symbol, signedBySide(quantity, side));
|
|
1632
|
-
}
|
|
1633
|
-
/** Active (non-zero) long and short leg sets. */
|
|
1634
|
-
activeLegs() {
|
|
1635
|
-
const longs = /* @__PURE__ */ new Set();
|
|
1636
|
-
const shorts = /* @__PURE__ */ new Set();
|
|
1637
|
-
for (const [symbol, qty] of this.legs) {
|
|
1638
|
-
switch (sign(qty)) {
|
|
1639
|
-
case 1:
|
|
1640
|
-
longs.add(symbol);
|
|
1641
|
-
break;
|
|
1642
|
-
case -1:
|
|
1643
|
-
shorts.add(symbol);
|
|
1644
|
-
break;
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
return {
|
|
1648
|
-
longs,
|
|
1649
|
-
shorts
|
|
1650
|
-
};
|
|
1651
|
-
}
|
|
1652
|
-
/** True when every leg is exactly zero (a fully CLOSED position). */
|
|
1653
|
-
isAllZero() {
|
|
1654
|
-
for (const qty of this.legs.values()) {
|
|
1655
|
-
if (!qty.isZero()) {
|
|
1656
|
-
return false;
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
return true;
|
|
1660
|
-
}
|
|
1661
|
-
/** All symbols with a leg, including zeroed ones (a closed leg still carries cost basis). */
|
|
1662
|
-
symbols() {
|
|
1663
|
-
return [
|
|
1664
|
-
...this.legs.keys()
|
|
1665
|
-
];
|
|
1666
|
-
}
|
|
1667
|
-
/** Canonical signed-string view for the wire (`+0` for zero). */
|
|
1668
|
-
toSigned() {
|
|
1669
|
-
const out = {};
|
|
1670
|
-
for (const [symbol, qty] of this.legs) {
|
|
1671
|
-
out[symbol] = signed(qty);
|
|
1672
|
-
}
|
|
1673
|
-
return out;
|
|
1674
|
-
}
|
|
1675
|
-
};
|
|
1676
|
-
|
|
1677
|
-
// src/sync/applicables/synthetics.ts
|
|
1678
|
-
function isMissedHistoryFill(fill) {
|
|
1679
|
-
return fill.exchangeFillId.startsWith(MISSED_HISTORY_FILL_PREFIX);
|
|
1680
|
-
}
|
|
1681
|
-
__name(isMissedHistoryFill, "isMissedHistoryFill");
|
|
1682
|
-
function addFillsForMissedHistory(args) {
|
|
1683
|
-
const missedHistoryFills = buildMissedHistoryFills(args);
|
|
1684
|
-
return [
|
|
1685
|
-
...missedHistoryFills,
|
|
1686
|
-
...args.orphanFills
|
|
1687
|
-
].sort((a, b) => a.timestamp - b.timestamp);
|
|
1688
|
-
}
|
|
1689
|
-
__name(addFillsForMissedHistory, "addFillsForMissedHistory");
|
|
1690
|
-
function alignPositionsWithExchange(args) {
|
|
1691
|
-
const { positions, pear, snapshot, tradeAccountId } = args;
|
|
1692
|
-
const neededSynthetics = findNeededSynthetics({
|
|
1693
|
-
positions,
|
|
1694
|
-
pear,
|
|
1695
|
-
snapshot
|
|
1696
|
-
});
|
|
1697
|
-
return assignSyntheticsToPositions({
|
|
1698
|
-
neededSynthetics,
|
|
1699
|
-
positions,
|
|
1700
|
-
pear,
|
|
1701
|
-
tradeAccountId
|
|
1702
|
-
});
|
|
1703
|
-
}
|
|
1704
|
-
__name(alignPositionsWithExchange, "alignPositionsWithExchange");
|
|
1705
|
-
function buildSyntheticFill(args) {
|
|
1706
|
-
return {
|
|
1707
|
-
// Content-addressed id — no wall-clock, no positionId, so re-syncs collide
|
|
1708
|
-
// on the dedup index and synthetic generation stays idempotent.
|
|
1709
|
-
exchangeFillId: syntheticFillId(args),
|
|
1710
|
-
cloid: null,
|
|
1711
|
-
connector: args.connector,
|
|
1712
|
-
symbol: args.symbol,
|
|
1713
|
-
side: args.side,
|
|
1714
|
-
quantity: args.quantity,
|
|
1715
|
-
// Gone-close: exit price is unknown. "0" is the unpriceable sentinel — real
|
|
1716
|
-
// fills are never 0 and missed-history fills derive a positive price, so
|
|
1717
|
-
// consumers treat a 0-price fill as unpriceable (excluded from PnL/avg-entry,
|
|
1718
|
-
// its closed size routed to undeterminedSize).
|
|
1719
|
-
price: "0",
|
|
1720
|
-
usd: "0",
|
|
1721
|
-
timestamp: args.timestamp,
|
|
1722
|
-
reduceOnly: false,
|
|
1723
|
-
synthetic: true,
|
|
1724
|
-
isExternal: true,
|
|
1725
|
-
tradeFee: "0",
|
|
1726
|
-
builderFee: "0",
|
|
1727
|
-
closedReason: args.closedReason,
|
|
1728
|
-
liquidation: null
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
|
-
__name(buildSyntheticFill, "buildSyntheticFill");
|
|
1732
|
-
function syntheticFillId(args) {
|
|
1733
|
-
if (args.lifecycleStartedAt === null) {
|
|
1734
|
-
return `synthetic-${args.positionKey}-${args.symbol}-${args.side}`;
|
|
1735
|
-
}
|
|
1736
|
-
return `synthetic-${args.positionKey}-${args.lifecycleStartedAt}-${args.symbol}-${args.side}`;
|
|
1737
|
-
}
|
|
1738
|
-
__name(syntheticFillId, "syntheticFillId");
|
|
1739
|
-
function buildMissedHistoryFills(args) {
|
|
1740
|
-
const { orphanFills, pear, connector, entryBySymbol, snapshot } = args;
|
|
1741
|
-
const fillTotals = /* @__PURE__ */ new Map();
|
|
1742
|
-
const orphanNotional = /* @__PURE__ */ new Map();
|
|
1743
|
-
for (const fill of orphanFills) {
|
|
1744
|
-
fillTotals.set(fill.symbol, add(fillTotals.get(fill.symbol) ?? ZERO, signedBySide(fill.quantity, fill.side)));
|
|
1745
|
-
const notional = multiply(fill.quantity, fill.price);
|
|
1746
|
-
orphanNotional.set(fill.symbol, add(orphanNotional.get(fill.symbol) ?? ZERO, notional));
|
|
1747
|
-
}
|
|
1748
|
-
const pearTotals = pear.totalBySymbol();
|
|
1749
|
-
const assets = /* @__PURE__ */ new Set([
|
|
1750
|
-
...orphanFills.map((fill) => fill.symbol),
|
|
1751
|
-
...snapshot.symbols()
|
|
1752
|
-
]);
|
|
1753
|
-
const earliestTs = orphanFills.reduce((min2, fill) => Math.min(min2, fill.timestamp), Infinity);
|
|
1754
|
-
if (!Number.isFinite(earliestTs)) {
|
|
1755
|
-
return [];
|
|
1756
|
-
}
|
|
1757
|
-
const watermark = Math.max(1, earliestTs - 1);
|
|
1758
|
-
const haveBySymbol = /* @__PURE__ */ __name((symbol) => add(pearTotals.get(symbol) ?? ZERO, fillTotals.get(symbol) ?? ZERO), "haveBySymbol");
|
|
1759
|
-
const result = [];
|
|
1760
|
-
for (const gap of gapsToTarget({
|
|
1761
|
-
assets,
|
|
1762
|
-
haveBySymbol,
|
|
1763
|
-
snapshot
|
|
1764
|
-
})) {
|
|
1765
|
-
const { symbol } = gap;
|
|
1766
|
-
const price = priceForMissedHistoryFill({
|
|
1767
|
-
exchangePosition: snapshot.get(symbol),
|
|
1768
|
-
syntheticQty: gap.quantity,
|
|
1769
|
-
pearQty: pearTotals.get(symbol) ?? ZERO,
|
|
1770
|
-
pearEntry: entryBySymbol[symbol],
|
|
1771
|
-
orphanNotional: orphanNotional.get(symbol) ?? ZERO
|
|
1772
|
-
});
|
|
1773
|
-
result.push({
|
|
1774
|
-
connector,
|
|
1775
|
-
exchangeFillId: `${MISSED_HISTORY_FILL_PREFIX}${symbol}-${gap.side.toLowerCase()}-${watermark}`,
|
|
1776
|
-
cloid: null,
|
|
1777
|
-
symbol,
|
|
1778
|
-
price,
|
|
1779
|
-
quantity: gap.quantity,
|
|
1780
|
-
side: gap.side,
|
|
1781
|
-
reduceOnly: false,
|
|
1782
|
-
timestamp: watermark,
|
|
1783
|
-
tradeFee: "0",
|
|
1784
|
-
builderFee: "0",
|
|
1785
|
-
liquidation: null
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
return result;
|
|
1789
|
-
}
|
|
1790
|
-
__name(buildMissedHistoryFills, "buildMissedHistoryFills");
|
|
1791
|
-
function priceForMissedHistoryFill(args) {
|
|
1792
|
-
const { exchangePosition, syntheticQty, pearQty, pearEntry, orphanNotional } = args;
|
|
1793
|
-
const fallback = exchangePosition?.entryPrice ?? "0";
|
|
1794
|
-
if (!exchangePosition) {
|
|
1795
|
-
return fallback;
|
|
1796
|
-
}
|
|
1797
|
-
let realNotional = orphanNotional;
|
|
1798
|
-
if (!isZero(pearQty)) {
|
|
1799
|
-
if (pearEntry === void 0) {
|
|
1800
|
-
return fallback;
|
|
1801
|
-
}
|
|
1802
|
-
realNotional = add(realNotional, multiply(abs(pearQty), pearEntry));
|
|
1803
|
-
}
|
|
1804
|
-
const synthQty = parse(syntheticQty);
|
|
1805
|
-
if (isZero(synthQty)) {
|
|
1806
|
-
return fallback;
|
|
1807
|
-
}
|
|
1808
|
-
const targetNotional = multiply(exchangePosition.size, exchangePosition.entryPrice);
|
|
1809
|
-
const derived = divide(subtract(targetNotional, realNotional), synthQty);
|
|
1810
|
-
return derived.isFinite() && derived.isPositive() ? derived.toFixed() : fallback;
|
|
1811
|
-
}
|
|
1812
|
-
__name(priceForMissedHistoryFill, "priceForMissedHistoryFill");
|
|
1813
|
-
function findNeededSynthetics(args) {
|
|
1814
|
-
const { positions, pear, snapshot } = args;
|
|
1815
|
-
const pearTotals = pear.totalBySymbol();
|
|
1816
|
-
const assets = /* @__PURE__ */ new Set();
|
|
1817
|
-
for (const pos of positions) {
|
|
1818
|
-
for (const part of pos.parts) {
|
|
1819
|
-
assets.add(part.source.symbol);
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
for (const symbol of snapshot.symbols()) {
|
|
1823
|
-
assets.add(symbol);
|
|
1824
|
-
}
|
|
1825
|
-
for (const symbol of pearTotals.keys()) {
|
|
1826
|
-
assets.add(symbol);
|
|
1827
|
-
}
|
|
1828
|
-
const haveBySymbol = /* @__PURE__ */ __name((symbol) => {
|
|
1829
|
-
let totalFills = ZERO;
|
|
1830
|
-
for (const pos of positions) {
|
|
1831
|
-
totalFills = add(totalFills, Exposure.netFromParts(pos.parts, symbol));
|
|
1832
|
-
}
|
|
1833
|
-
return add(pearTotals.get(symbol) ?? ZERO, totalFills);
|
|
1834
|
-
}, "haveBySymbol");
|
|
1835
|
-
const result = /* @__PURE__ */ new Map();
|
|
1836
|
-
for (const gap of gapsToTarget({
|
|
1837
|
-
assets,
|
|
1838
|
-
haveBySymbol,
|
|
1839
|
-
snapshot
|
|
1840
|
-
})) {
|
|
1841
|
-
switch (gap.kind) {
|
|
1842
|
-
case "gone":
|
|
1843
|
-
result.set(gap.symbol, {
|
|
1844
|
-
symbol: gap.symbol,
|
|
1845
|
-
side: gap.side,
|
|
1846
|
-
quantity: gap.quantity,
|
|
1847
|
-
closedReason: "USER"
|
|
1848
|
-
});
|
|
1849
|
-
break;
|
|
1850
|
-
case "gap":
|
|
1851
|
-
result.set(gap.symbol, {
|
|
1852
|
-
symbol: gap.symbol,
|
|
1853
|
-
side: gap.side,
|
|
1854
|
-
quantity: gap.quantity
|
|
1855
|
-
});
|
|
1856
|
-
break;
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
return result;
|
|
1860
|
-
}
|
|
1861
|
-
__name(findNeededSynthetics, "findNeededSynthetics");
|
|
1862
|
-
function assignSyntheticsToPositions(args) {
|
|
1863
|
-
const { neededSynthetics, positions, pear, tradeAccountId } = args;
|
|
1864
|
-
const keyOf = /* @__PURE__ */ __name((symbols) => generateDeterministicPositionKey({
|
|
1865
|
-
tradeAccountId,
|
|
1866
|
-
symbols
|
|
1867
|
-
}), "keyOf");
|
|
1868
|
-
const existingBySymbol = pear.positionBySymbol();
|
|
1869
|
-
const created = [];
|
|
1870
|
-
const byPosition = /* @__PURE__ */ new Map();
|
|
1871
|
-
for (const [symbol, spec] of neededSynthetics) {
|
|
1872
|
-
let target = positions.find((pos) => !pos.closed && pos.parts.some((part) => part.source.symbol === symbol));
|
|
1873
|
-
if (!target) {
|
|
1874
|
-
const existing = existingBySymbol.get(symbol);
|
|
1875
|
-
const positionKey = existing ? existing.positionKey : keyOf([
|
|
1876
|
-
symbol
|
|
1877
|
-
]);
|
|
1878
|
-
const isKeyTarget = /* @__PURE__ */ __name((pos) => pos.positionKey === positionKey && (!existing || pos.existingOrigin !== void 0), "isKeyTarget");
|
|
1879
|
-
target = positions.find(isKeyTarget) ?? created.find(isKeyTarget);
|
|
1880
|
-
if (!target) {
|
|
1881
|
-
target = existing ? (
|
|
1882
|
-
// seed its exposure and origin so folding the synthetic yields the true
|
|
1883
|
-
// final state (a gone-close nets to zero -> CLOSED/USER, a gap writes
|
|
1884
|
-
// the full total, not just the delta).
|
|
1885
|
-
{
|
|
1886
|
-
positionKey,
|
|
1887
|
-
parts: [],
|
|
1888
|
-
closed: false,
|
|
1889
|
-
exposure: Exposure.fromRecord(existing.exposure),
|
|
1890
|
-
existingOrigin: existing.origin,
|
|
1891
|
-
lifecycleStartedAt: Date.parse(existing.createdAt)
|
|
1892
|
-
}
|
|
1893
|
-
) : {
|
|
1894
|
-
positionKey,
|
|
1895
|
-
parts: [],
|
|
1896
|
-
closed: false,
|
|
1897
|
-
exposure: Exposure.empty(),
|
|
1898
|
-
lifecycleStartedAt: null
|
|
1899
|
-
};
|
|
1900
|
-
created.push(target);
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
const list = byPosition.get(target) ?? [];
|
|
1904
|
-
list.push(spec);
|
|
1905
|
-
byPosition.set(target, list);
|
|
1906
|
-
}
|
|
1907
|
-
return {
|
|
1908
|
-
positions: [
|
|
1909
|
-
...positions,
|
|
1910
|
-
...created
|
|
1911
|
-
],
|
|
1912
|
-
synthByPosition: byPosition
|
|
1913
|
-
};
|
|
1914
|
-
}
|
|
1915
|
-
__name(assignSyntheticsToPositions, "assignSyntheticsToPositions");
|
|
1916
|
-
function gapsToTarget(args) {
|
|
1917
|
-
const { assets, haveBySymbol, snapshot } = args;
|
|
1918
|
-
const gaps = [];
|
|
1919
|
-
for (const symbol of assets) {
|
|
1920
|
-
const gap = findGapToTarget({
|
|
1921
|
-
symbol,
|
|
1922
|
-
have: haveBySymbol(symbol),
|
|
1923
|
-
target: snapshot.signedSize(symbol)
|
|
1924
|
-
});
|
|
1925
|
-
if (gap) {
|
|
1926
|
-
gaps.push(gap);
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
return gaps;
|
|
1930
|
-
}
|
|
1931
|
-
__name(gapsToTarget, "gapsToTarget");
|
|
1932
|
-
function findGapToTarget(args) {
|
|
1933
|
-
const { symbol, have, target } = args;
|
|
1934
|
-
if (isZero(target)) {
|
|
1935
|
-
if (isGreaterThan(abs(have), DUST)) {
|
|
1936
|
-
return {
|
|
1937
|
-
kind: "gone",
|
|
1938
|
-
symbol,
|
|
1939
|
-
side: have.isPositive() ? "SELL" : "BUY",
|
|
1940
|
-
quantity: have.abs().toFixed()
|
|
1941
|
-
};
|
|
1942
|
-
}
|
|
1943
|
-
return null;
|
|
1944
|
-
}
|
|
1945
|
-
const residual = subtract(target, have);
|
|
1946
|
-
if (isGreaterThan(abs(residual), DUST)) {
|
|
1947
|
-
return {
|
|
1948
|
-
kind: "gap",
|
|
1949
|
-
symbol,
|
|
1950
|
-
side: residual.isPositive() ? "BUY" : "SELL",
|
|
1951
|
-
quantity: residual.abs().toFixed()
|
|
1952
|
-
};
|
|
1953
|
-
}
|
|
1954
|
-
return null;
|
|
1955
|
-
}
|
|
1956
|
-
__name(findGapToTarget, "findGapToTarget");
|
|
1957
|
-
|
|
1958
|
-
// src/sync/applicables/matching.ts
|
|
1959
|
-
function matchPositions(args) {
|
|
1960
|
-
const { tradeAccountId, orphanFills, pear } = args;
|
|
1961
|
-
const book = WorkingBook.load({
|
|
1962
|
-
tradeAccountId,
|
|
1963
|
-
openPositions: pear.positions()
|
|
1964
|
-
});
|
|
1965
|
-
for (const basket of Basket.detect(orphanFills)) {
|
|
1966
|
-
const at = basketTime(basket);
|
|
1967
|
-
book.foldIntoLive(at);
|
|
1968
|
-
const leftover = applyCloses({
|
|
1969
|
-
legs: basket.legs,
|
|
1970
|
-
book,
|
|
1971
|
-
at
|
|
1972
|
-
});
|
|
1973
|
-
if (leftover.length === 0) {
|
|
1974
|
-
continue;
|
|
1975
|
-
}
|
|
1976
|
-
const target = resolveLeftoverTarget({
|
|
1977
|
-
book,
|
|
1978
|
-
leftover,
|
|
1979
|
-
at
|
|
1980
|
-
});
|
|
1981
|
-
applyLeftover(target, leftover);
|
|
1982
|
-
}
|
|
1983
|
-
book.foldIntoLive(Number.POSITIVE_INFINITY);
|
|
1984
|
-
return book.toMatched();
|
|
1985
|
-
}
|
|
1986
|
-
__name(matchPositions, "matchPositions");
|
|
1987
|
-
function basketTime(basket) {
|
|
1988
|
-
const realTimestamps = basket.fills.filter((fill) => !isMissedHistoryFill(fill)).map((fill) => fill.timestamp);
|
|
1989
|
-
if (realTimestamps.length === 0) {
|
|
1990
|
-
return Number.POSITIVE_INFINITY;
|
|
1991
|
-
}
|
|
1992
|
-
return Math.min(...realTimestamps);
|
|
1993
|
-
}
|
|
1994
|
-
__name(basketTime, "basketTime");
|
|
1995
|
-
var WorkingBook = class WorkingBook2 {
|
|
1996
|
-
static {
|
|
1997
|
-
__name(this, "WorkingBook");
|
|
1998
|
-
}
|
|
1999
|
-
tradeAccountId;
|
|
2000
|
-
all = [];
|
|
2001
|
-
constructor(tradeAccountId) {
|
|
2002
|
-
this.tradeAccountId = tradeAccountId;
|
|
2003
|
-
}
|
|
2004
|
-
/** Seed the book from existing open DB positions (they arrive pre-filtered by connector). */
|
|
2005
|
-
static load(args) {
|
|
2006
|
-
const { tradeAccountId, openPositions } = args;
|
|
2007
|
-
const book = new WorkingBook2(tradeAccountId);
|
|
2008
|
-
for (const p of openPositions) {
|
|
2009
|
-
const position = {
|
|
2010
|
-
// Trust the position's own stored key — never recompute it from exposure.
|
|
2011
|
-
// A partial fill on the original order leaves exposure short of the
|
|
2012
|
-
// symbols the key was hashed from, so a recomputed key would never match.
|
|
2013
|
-
positionKey: p.positionKey,
|
|
2014
|
-
createdAt: Date.parse(p.createdAt),
|
|
2015
|
-
orderId: p.id,
|
|
2016
|
-
exposure: Exposure.fromRecord(p.exposure),
|
|
2017
|
-
parts: [],
|
|
2018
|
-
closed: false,
|
|
2019
|
-
existingOrigin: p.origin,
|
|
2020
|
-
// A seeded position takes fills from its creation, less the late-arrival grace.
|
|
2021
|
-
liveFrom: Date.parse(p.createdAt) - LATE_FILL_GRACE_MS
|
|
2022
|
-
};
|
|
2023
|
-
book.all.push(position);
|
|
2024
|
-
}
|
|
2025
|
-
return book;
|
|
2026
|
-
}
|
|
2027
|
-
/** Deterministic position key for a symbol set under this book's trade account. */
|
|
2028
|
-
keyFor(symbols) {
|
|
2029
|
-
return generateDeterministicPositionKey({
|
|
2030
|
-
tradeAccountId: this.tradeAccountId,
|
|
2031
|
-
symbols
|
|
2032
|
-
});
|
|
2033
|
-
}
|
|
2034
|
-
/** Working positions open and already existing at basket time `at`, in creation order. */
|
|
2035
|
-
live(at) {
|
|
2036
|
-
return this.all.filter((position) => !position.closed && position.liveFrom <= at).sort((a, b) => a.createdAt - b.createdAt || a.orderId.localeCompare(b.orderId));
|
|
2037
|
-
}
|
|
2038
|
-
/** Look up a position open and existing at `at` by its positionKey (for dedup on merge). */
|
|
2039
|
-
findLiveByKey(key, at) {
|
|
2040
|
-
return this.live(at).find((position) => position.positionKey === key);
|
|
2041
|
-
}
|
|
2042
|
-
/**
|
|
2043
|
-
* Fold every open position opened mid-replay into the open seeded position sharing its
|
|
2044
|
-
* key once that seeded position is live at `at`. The earlier open was still holding
|
|
2045
|
-
* exposure when the seeded position was created, and the server keeps at most one open
|
|
2046
|
-
* row per key. Ownership is ambiguous here — exposure and timestamps cannot prove the
|
|
2047
|
-
* seeded position traded on top of that history — so the fold conservatively keeps
|
|
2048
|
-
* the attribution the matcher gave before #1067.
|
|
2049
|
-
*/
|
|
2050
|
-
foldIntoLive(at) {
|
|
2051
|
-
for (const seeded of this.live(at).filter((position) => position.existingOrigin !== void 0)) {
|
|
2052
|
-
const earlier = this.all.filter((position) => position.existingOrigin === void 0 && !position.closed && position.positionKey === seeded.positionKey);
|
|
2053
|
-
for (const position of earlier) {
|
|
2054
|
-
seeded.parts.push(...position.parts);
|
|
2055
|
-
for (const symbol of position.exposure.symbols()) {
|
|
2056
|
-
seeded.exposure.addDelta(symbol, position.exposure.get(symbol));
|
|
2057
|
-
}
|
|
2058
|
-
this.all = this.all.filter((candidate) => candidate !== position);
|
|
2059
|
-
}
|
|
2060
|
-
this.markClosedIfFlat(seeded);
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
/** Register a fresh empty working position under `key` and return it. */
|
|
2064
|
-
create(key, createdAt) {
|
|
2065
|
-
const position = {
|
|
2066
|
-
positionKey: key,
|
|
2067
|
-
createdAt,
|
|
2068
|
-
orderId: key,
|
|
2069
|
-
exposure: Exposure.empty(),
|
|
2070
|
-
parts: [],
|
|
2071
|
-
closed: false,
|
|
2072
|
-
// Opened by this replay's own fills, so it takes fills from the start.
|
|
2073
|
-
liveFrom: Number.NEGATIVE_INFINITY
|
|
2074
|
-
};
|
|
2075
|
-
this.all.push(position);
|
|
2076
|
-
return position;
|
|
2077
|
-
}
|
|
2078
|
-
/** Flip a position to closed once every leg is exactly zero. */
|
|
2079
|
-
markClosedIfFlat(position) {
|
|
2080
|
-
if (position.closed || !position.exposure.isAllZero()) {
|
|
2081
|
-
return;
|
|
2082
|
-
}
|
|
2083
|
-
position.closed = true;
|
|
2084
|
-
}
|
|
2085
|
-
/** Working positions that received fills become MatchedPosition records; untouched ones are dropped. */
|
|
2086
|
-
toMatched() {
|
|
2087
|
-
return this.all.filter((position) => position.parts.length > 0).map((position) => ({
|
|
2088
|
-
positionKey: position.positionKey,
|
|
2089
|
-
parts: position.parts,
|
|
2090
|
-
closed: position.closed,
|
|
2091
|
-
exposure: position.exposure,
|
|
2092
|
-
existingOrigin: position.existingOrigin,
|
|
2093
|
-
lifecycleStartedAt: position.createdAt
|
|
2094
|
-
}));
|
|
2095
|
-
}
|
|
2096
|
-
};
|
|
2097
|
-
function applyCloses(args) {
|
|
2098
|
-
const { legs, book, at } = args;
|
|
2099
|
-
const openList = book.live(at);
|
|
2100
|
-
const leftover = [];
|
|
2101
|
-
for (const leg of legs) {
|
|
2102
|
-
let remaining = leg.totalSize;
|
|
2103
|
-
const closeTargetIsLong = sideSign(leg.side) === -1;
|
|
2104
|
-
const holders = openList.filter((position) => {
|
|
2105
|
-
const qty = position.exposure.get(leg.symbol);
|
|
2106
|
-
if (qty.isZero()) {
|
|
2107
|
-
return false;
|
|
2108
|
-
}
|
|
2109
|
-
return closeTargetIsLong ? qty.isPositive() : qty.isNegative();
|
|
2110
|
-
});
|
|
2111
|
-
for (const position of holders) {
|
|
2112
|
-
if (isLessThanOrEqual(remaining, ZERO)) {
|
|
2113
|
-
break;
|
|
2114
|
-
}
|
|
2115
|
-
const have = position.exposure.get(leg.symbol).abs();
|
|
2116
|
-
const reduce = min(have, remaining);
|
|
2117
|
-
position.parts.push(...leg.take(reduce));
|
|
2118
|
-
position.exposure.addDelta(leg.symbol, signedBySide(reduce, leg.side));
|
|
2119
|
-
remaining = remaining.minus(reduce);
|
|
2120
|
-
book.markClosedIfFlat(position);
|
|
2121
|
-
}
|
|
2122
|
-
if (isGreaterThan(remaining, ZERO)) {
|
|
2123
|
-
leftover.push({
|
|
2124
|
-
side: leg.side,
|
|
2125
|
-
qty: remaining,
|
|
2126
|
-
leg
|
|
2127
|
-
});
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
return leftover;
|
|
2131
|
-
}
|
|
2132
|
-
__name(applyCloses, "applyCloses");
|
|
2133
|
-
function resolveLeftoverTarget(args) {
|
|
2134
|
-
const { book, leftover, at } = args;
|
|
2135
|
-
const leftoverLongs = new Set(leftover.filter((entry) => entry.side === "BUY").map((entry) => entry.leg.symbol));
|
|
2136
|
-
const leftoverShorts = new Set(leftover.filter((entry) => entry.side === "SELL").map((entry) => entry.leg.symbol));
|
|
2137
|
-
const target = book.live(at).find((position) => {
|
|
2138
|
-
const { longs, shorts } = position.exposure.activeLegs();
|
|
2139
|
-
const sameActiveShape = setEq(leftoverLongs, longs) && setEq(leftoverShorts, shorts);
|
|
2140
|
-
const noDormantLegs = position.exposure.symbols().length === longs.size + shorts.size;
|
|
2141
|
-
return sameActiveShape && noDormantLegs;
|
|
2142
|
-
});
|
|
2143
|
-
if (target) {
|
|
2144
|
-
return target;
|
|
2145
|
-
}
|
|
2146
|
-
const symbols = [
|
|
2147
|
-
.../* @__PURE__ */ new Set([
|
|
2148
|
-
...leftoverLongs,
|
|
2149
|
-
...leftoverShorts
|
|
2150
|
-
])
|
|
2151
|
-
];
|
|
2152
|
-
const positionKey = book.keyFor(symbols);
|
|
2153
|
-
return book.findLiveByKey(positionKey, at) ?? book.create(positionKey, at);
|
|
2154
|
-
}
|
|
2155
|
-
__name(resolveLeftoverTarget, "resolveLeftoverTarget");
|
|
2156
|
-
function applyLeftover(target, leftover) {
|
|
2157
|
-
for (const entry of leftover) {
|
|
2158
|
-
const parts = entry.leg.take(entry.qty);
|
|
2159
|
-
target.parts.push(...parts);
|
|
2160
|
-
if (target.existingOrigin === void 0) {
|
|
2161
|
-
target.createdAt = Math.min(...target.parts.map((part) => part.source.timestamp));
|
|
2162
|
-
target.orderId = target.parts.filter((part) => part.source.timestamp === target.createdAt).map((part) => part.source.exchangeFillId).sort()[0] ?? target.positionKey;
|
|
2163
|
-
}
|
|
2164
|
-
target.exposure.addSigned(entry.leg.symbol, entry.qty.toFixed(), entry.side);
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
__name(applyLeftover, "applyLeftover");
|
|
2168
|
-
function setEq(a, b) {
|
|
2169
|
-
return a.size === b.size && [
|
|
2170
|
-
...a
|
|
2171
|
-
].every((symbol) => b.has(symbol));
|
|
2172
|
-
}
|
|
2173
|
-
__name(setEq, "setEq");
|
|
2174
|
-
|
|
2175
|
-
// src/sync/applicables/fills.ts
|
|
2176
|
-
function sourceToApplicableFill(fill, connector) {
|
|
2177
|
-
const synthetic = isMissedHistoryFill(fill);
|
|
2178
|
-
return {
|
|
2179
|
-
exchangeFillId: fill.exchangeFillId,
|
|
2180
|
-
connector,
|
|
2181
|
-
symbol: fill.symbol,
|
|
2182
|
-
side: fill.side,
|
|
2183
|
-
quantity: fill.quantity,
|
|
2184
|
-
price: fill.price,
|
|
2185
|
-
usd: multiply(fill.quantity, fill.price).toFixed(),
|
|
2186
|
-
timestamp: fill.timestamp,
|
|
2187
|
-
cloid: synthetic ? null : fill.cloid ?? null,
|
|
2188
|
-
reduceOnly: synthetic ? false : fill.reduceOnly,
|
|
2189
|
-
synthetic,
|
|
2190
|
-
isExternal: synthetic || !isPearCloid(connector, fill.cloid),
|
|
2191
|
-
tradeFee: synthetic ? "0" : fill.tradeFee,
|
|
2192
|
-
builderFee: synthetic ? "0" : fill.builderFee,
|
|
2193
|
-
liquidation: synthetic ? null : fill.liquidation
|
|
2194
|
-
};
|
|
2195
|
-
}
|
|
2196
|
-
__name(sourceToApplicableFill, "sourceToApplicableFill");
|
|
2197
|
-
|
|
2198
|
-
// src/sync/applicables/position.ts
|
|
2199
|
-
function assembleApplicables(args) {
|
|
2200
|
-
const { positions, connector, synthByPosition, now } = args;
|
|
2201
|
-
const fillsById = /* @__PURE__ */ new Map();
|
|
2202
|
-
const applicables = [];
|
|
2203
|
-
for (const pos of positions) {
|
|
2204
|
-
const applicable = assembleOneApplicable(pos, connector, fillsById, synthByPosition.get(pos) ?? [], now);
|
|
2205
|
-
if (applicable) {
|
|
2206
|
-
applicables.push(applicable);
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
return {
|
|
2210
|
-
fills: [
|
|
2211
|
-
...fillsById.values()
|
|
2212
|
-
].sort((a, b) => a.timestamp - b.timestamp || a.exchangeFillId.localeCompare(b.exchangeFillId)),
|
|
2213
|
-
applicables
|
|
2214
|
-
};
|
|
2215
|
-
}
|
|
2216
|
-
__name(assembleApplicables, "assembleApplicables");
|
|
2217
|
-
function assembleOneApplicable(pos, connector, fillsById, assignedSynthetics, now) {
|
|
2218
|
-
const { realFills, assignments, closedReason } = finalizeRealFills(pos, connector);
|
|
2219
|
-
const syntheticFills = assignedSynthetics.map((synthetic) => buildSyntheticFill({
|
|
2220
|
-
positionKey: pos.positionKey,
|
|
2221
|
-
lifecycleStartedAt: pos.lifecycleStartedAt,
|
|
2222
|
-
connector,
|
|
2223
|
-
timestamp: anchorSyntheticTimestamp(realFills, synthetic.symbol, now),
|
|
2224
|
-
...synthetic
|
|
2225
|
-
}));
|
|
2226
|
-
for (const part of pos.parts) {
|
|
2227
|
-
fillsById.set(part.source.exchangeFillId, sourceToApplicableFill(part.source, connector));
|
|
2228
|
-
}
|
|
2229
|
-
for (const fill of syntheticFills) {
|
|
2230
|
-
fillsById.set(fill.exchangeFillId, fill);
|
|
2231
|
-
assignments.push({
|
|
2232
|
-
exchangeFillId: fill.exchangeFillId,
|
|
2233
|
-
quantity: fill.quantity
|
|
2234
|
-
});
|
|
2235
|
-
}
|
|
2236
|
-
if (realFills.length === 0 && syntheticFills.length === 0) {
|
|
2237
|
-
return null;
|
|
2238
|
-
}
|
|
2239
|
-
const exposure = finalExposure(pos, assignedSynthetics);
|
|
2240
|
-
const closed = exposure.isAllZero();
|
|
2241
|
-
const status = closed ? "CLOSED" : "OPEN";
|
|
2242
|
-
const origin = mergeOrigin(pos.existingOrigin, [
|
|
2243
|
-
...realFills,
|
|
2244
|
-
...syntheticFills
|
|
2245
|
-
]);
|
|
2246
|
-
return {
|
|
2247
|
-
positionKey: pos.positionKey,
|
|
2248
|
-
exposure: exposure.toSigned(),
|
|
2249
|
-
origin,
|
|
2250
|
-
status,
|
|
2251
|
-
...closed ? {
|
|
2252
|
-
closedReason
|
|
2253
|
-
} : {},
|
|
2254
|
-
assignments: assignments.sort((a, b) => (fillsById.get(a.exchangeFillId)?.timestamp ?? 0) - (fillsById.get(b.exchangeFillId)?.timestamp ?? 0)),
|
|
2255
|
-
metadata: buildMetadata(new Set(exposure.symbols()), realFills)
|
|
2256
|
-
};
|
|
2257
|
-
}
|
|
2258
|
-
__name(assembleOneApplicable, "assembleOneApplicable");
|
|
2259
|
-
function finalizeRealFills(position, connector) {
|
|
2260
|
-
const newestFirst = [
|
|
2261
|
-
...position.parts
|
|
2262
|
-
].sort((a, b) => a.source.timestamp - b.source.timestamp).reverse();
|
|
2263
|
-
const exposure = position.exposure.clone();
|
|
2264
|
-
const realFills = [];
|
|
2265
|
-
const assignments = [];
|
|
2266
|
-
let closedReason = "USER";
|
|
2267
|
-
for (const [index, part] of newestFirst.entries()) {
|
|
2268
|
-
const after = exposure.get(part.source.symbol);
|
|
2269
|
-
const flatAfter = exposure.isAllZero();
|
|
2270
|
-
exposure.addDelta(part.source.symbol, negate(signedBySide(part.quantity, part.source.side)));
|
|
2271
|
-
const reducesLeg = isGreaterThan(exposure.get(part.source.symbol).abs(), after.abs());
|
|
2272
|
-
const liquidationOutcome = part.source.liquidation && reducesLeg ? flatAfter ? "CLOSED" : "REDUCED" : null;
|
|
2273
|
-
if (index === 0 && position.closed && liquidationOutcome === "CLOSED") {
|
|
2274
|
-
closedReason = "LIQUIDATED";
|
|
2275
|
-
}
|
|
2276
|
-
realFills.push({
|
|
2277
|
-
...sourceToApplicableFill(part.source, connector),
|
|
2278
|
-
quantity: part.quantity,
|
|
2279
|
-
usd: multiply(part.quantity, part.source.price).toFixed()
|
|
2280
|
-
});
|
|
2281
|
-
assignments.push({
|
|
2282
|
-
exchangeFillId: part.source.exchangeFillId,
|
|
2283
|
-
quantity: part.quantity,
|
|
2284
|
-
...liquidationOutcome === null ? {} : {
|
|
2285
|
-
liquidationOutcome
|
|
2286
|
-
}
|
|
2287
|
-
});
|
|
2288
|
-
}
|
|
2289
|
-
realFills.reverse();
|
|
2290
|
-
assignments.reverse();
|
|
2291
|
-
return {
|
|
2292
|
-
realFills,
|
|
2293
|
-
assignments,
|
|
2294
|
-
closedReason
|
|
2295
|
-
};
|
|
2296
|
-
}
|
|
2297
|
-
__name(finalizeRealFills, "finalizeRealFills");
|
|
2298
|
-
function finalExposure(pos, synthetics) {
|
|
2299
|
-
const exposure = pos.exposure.clone();
|
|
2300
|
-
for (const synthetic of synthetics) {
|
|
2301
|
-
exposure.addSigned(synthetic.symbol, synthetic.quantity, synthetic.side);
|
|
2302
|
-
}
|
|
2303
|
-
return exposure;
|
|
2304
|
-
}
|
|
2305
|
-
__name(finalExposure, "finalExposure");
|
|
2306
|
-
function anchorSyntheticTimestamp(realFills, symbol, now) {
|
|
2307
|
-
const oldestForSymbol = realFills.find((fill) => fill.symbol === symbol)?.timestamp;
|
|
2308
|
-
if (oldestForSymbol !== void 0) {
|
|
2309
|
-
return Math.max(1, oldestForSymbol - 1);
|
|
2310
|
-
}
|
|
2311
|
-
const oldestRealTs = realFills[0]?.timestamp;
|
|
2312
|
-
return oldestRealTs !== void 0 ? Math.max(1, oldestRealTs - 1) : now;
|
|
2313
|
-
}
|
|
2314
|
-
__name(anchorSyntheticTimestamp, "anchorSyntheticTimestamp");
|
|
2315
|
-
function buildMetadata(symbols, realFills) {
|
|
2316
|
-
const initialExposureUSD = {};
|
|
2317
|
-
for (const symbol of symbols) {
|
|
2318
|
-
const legFills = realFills.flatMap((fill) => fill.symbol === symbol && fill.usd != null ? [
|
|
2319
|
-
{
|
|
2320
|
-
side: fill.side,
|
|
2321
|
-
usd: fill.usd
|
|
2322
|
-
}
|
|
2323
|
-
] : []);
|
|
2324
|
-
const opener = legFills[0];
|
|
2325
|
-
if (!opener) {
|
|
2326
|
-
continue;
|
|
2327
|
-
}
|
|
2328
|
-
const notional = legFills.filter((fill) => fill.side === opener.side).reduce((sum, fill) => add(sum, fill.usd), ZERO);
|
|
2329
|
-
const signedNotional = signed(opener.side === "BUY" ? notional : notional.negated());
|
|
2330
|
-
initialExposureUSD[symbol] = signedNotional;
|
|
2331
|
-
}
|
|
2332
|
-
return {
|
|
2333
|
-
initialExposureUSD
|
|
2334
|
-
};
|
|
2335
|
-
}
|
|
2336
|
-
__name(buildMetadata, "buildMetadata");
|
|
2337
|
-
function mergeOrigin(existing, fills) {
|
|
2338
|
-
let anyExternal = false;
|
|
2339
|
-
let anyPear = false;
|
|
2340
|
-
switch (existing) {
|
|
2341
|
-
case "EXTERNAL":
|
|
2342
|
-
anyExternal = true;
|
|
2343
|
-
break;
|
|
2344
|
-
case "PEAR":
|
|
2345
|
-
anyPear = true;
|
|
2346
|
-
break;
|
|
2347
|
-
case "PARTIAL":
|
|
2348
|
-
anyExternal = true;
|
|
2349
|
-
anyPear = true;
|
|
2350
|
-
break;
|
|
2351
|
-
}
|
|
2352
|
-
for (const fill of fills) {
|
|
2353
|
-
if (fill.isExternal) {
|
|
2354
|
-
anyExternal = true;
|
|
2355
|
-
} else {
|
|
2356
|
-
anyPear = true;
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
if (anyExternal && anyPear) {
|
|
2360
|
-
return "PARTIAL";
|
|
2361
|
-
}
|
|
2362
|
-
return anyPear ? "PEAR" : "EXTERNAL";
|
|
2363
|
-
}
|
|
2364
|
-
__name(mergeOrigin, "mergeOrigin");
|
|
2365
|
-
|
|
2366
|
-
// src/sync/applicables/snapshot.ts
|
|
2367
|
-
var ExchangeSnapshot = class _ExchangeSnapshot {
|
|
2368
|
-
static {
|
|
2369
|
-
__name(this, "ExchangeSnapshot");
|
|
2370
|
-
}
|
|
2371
|
-
bySymbol;
|
|
2372
|
-
constructor(bySymbol) {
|
|
2373
|
-
this.bySymbol = bySymbol;
|
|
2374
|
-
}
|
|
2375
|
-
/** Index `positions` by symbol for O(1) lookup. */
|
|
2376
|
-
static from(positions) {
|
|
2377
|
-
return new _ExchangeSnapshot(new Map(positions.map((position) => [
|
|
2378
|
-
position.symbol,
|
|
2379
|
-
position
|
|
2380
|
-
])));
|
|
2381
|
-
}
|
|
2382
|
-
/** Raw exchange position for `symbol`, or `undefined` when the exchange holds nothing. */
|
|
2383
|
-
get(symbol) {
|
|
2384
|
-
return this.bySymbol.get(symbol);
|
|
2385
|
-
}
|
|
2386
|
-
/** Signed exchange exposure for `symbol` (0 when the exchange has no position). */
|
|
2387
|
-
signedSize(symbol) {
|
|
2388
|
-
const position = this.bySymbol.get(symbol);
|
|
2389
|
-
if (!position) {
|
|
2390
|
-
return ZERO;
|
|
2391
|
-
}
|
|
2392
|
-
const size = parse(position.size);
|
|
2393
|
-
switch (position.side) {
|
|
2394
|
-
case "BUY":
|
|
2395
|
-
return size;
|
|
2396
|
-
case "SELL":
|
|
2397
|
-
return size.negated();
|
|
2398
|
-
}
|
|
2399
|
-
}
|
|
2400
|
-
/** All symbols the exchange currently reports a position for. */
|
|
2401
|
-
symbols() {
|
|
2402
|
-
return [
|
|
2403
|
-
...this.bySymbol.keys()
|
|
2404
|
-
];
|
|
2405
|
-
}
|
|
2406
|
-
};
|
|
2407
|
-
var PearHoldings = class _PearHoldings {
|
|
2408
|
-
static {
|
|
2409
|
-
__name(this, "PearHoldings");
|
|
2410
|
-
}
|
|
2411
|
-
filtered;
|
|
2412
|
-
constructor(filtered) {
|
|
2413
|
-
this.filtered = filtered;
|
|
2414
|
-
}
|
|
2415
|
-
/** Filter `openPositions` to the given connector and wrap. */
|
|
2416
|
-
static from(args) {
|
|
2417
|
-
const { openPositions, connector } = args;
|
|
2418
|
-
return new _PearHoldings(openPositions.filter((position) => position.connector === connector).sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt) || a.id.localeCompare(b.id)));
|
|
2419
|
-
}
|
|
2420
|
-
/** The open Pear positions for this connector. */
|
|
2421
|
-
positions() {
|
|
2422
|
-
return this.filtered;
|
|
2423
|
-
}
|
|
2424
|
-
/** Sum existing open Pear exposure per symbol. */
|
|
2425
|
-
totalBySymbol() {
|
|
2426
|
-
const totals = /* @__PURE__ */ new Map();
|
|
2427
|
-
for (const position of this.filtered) {
|
|
2428
|
-
for (const [symbol, signedExposure] of Object.entries(position.exposure)) {
|
|
2429
|
-
if (signedExposure) {
|
|
2430
|
-
const newExposure = add(totals.get(symbol) ?? ZERO, signedExposure);
|
|
2431
|
-
totals.set(symbol, newExposure);
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
return totals;
|
|
2436
|
-
}
|
|
2437
|
-
/**
|
|
2438
|
-
* Which open DB position holds each active asset — the fallback target for a
|
|
2439
|
-
* synthetic when no matched position already carries that asset. First open
|
|
2440
|
-
* position with a non-zero leg wins per symbol.
|
|
2441
|
-
*/
|
|
2442
|
-
positionBySymbol() {
|
|
2443
|
-
const existingBySymbol = /* @__PURE__ */ new Map();
|
|
2444
|
-
for (const position of this.filtered) {
|
|
2445
|
-
for (const [symbol, signedQty] of Object.entries(position.exposure)) {
|
|
2446
|
-
const legIsZero = isZero(signedQty);
|
|
2447
|
-
const exists = existingBySymbol.has(symbol);
|
|
2448
|
-
if (!legIsZero && !exists) {
|
|
2449
|
-
existingBySymbol.set(symbol, position);
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
return existingBySymbol;
|
|
2454
|
-
}
|
|
2455
|
-
};
|
|
2456
|
-
|
|
2457
|
-
// src/sync/applicables/index.ts
|
|
2458
|
-
function buildApplicables(args) {
|
|
2459
|
-
const { tradeAccountId, connector, orphanFills, openPositions, entryBySymbol, exchangePositions } = args;
|
|
2460
|
-
const snapshot = ExchangeSnapshot.from(exchangePositions);
|
|
2461
|
-
const pear = PearHoldings.from({
|
|
2462
|
-
openPositions,
|
|
2463
|
-
connector
|
|
2464
|
-
});
|
|
2465
|
-
const now = Date.now();
|
|
2466
|
-
const fills = addFillsForMissedHistory({
|
|
2467
|
-
orphanFills,
|
|
2468
|
-
pear,
|
|
2469
|
-
connector,
|
|
2470
|
-
entryBySymbol,
|
|
2471
|
-
snapshot
|
|
2472
|
-
});
|
|
2473
|
-
const matched = matchPositions({
|
|
2474
|
-
tradeAccountId,
|
|
2475
|
-
orphanFills: fills,
|
|
2476
|
-
pear
|
|
2477
|
-
});
|
|
2478
|
-
const { positions, synthByPosition } = alignPositionsWithExchange({
|
|
2479
|
-
positions: matched,
|
|
2480
|
-
pear,
|
|
2481
|
-
snapshot,
|
|
2482
|
-
tradeAccountId
|
|
2483
|
-
});
|
|
2484
|
-
return assembleApplicables({
|
|
2485
|
-
positions,
|
|
2486
|
-
connector,
|
|
2487
|
-
synthByPosition,
|
|
2488
|
-
now
|
|
2489
|
-
});
|
|
2490
|
-
}
|
|
2491
|
-
__name(buildApplicables, "buildApplicables");
|
|
2492
|
-
|
|
2493
|
-
// src/sync/cutoff.ts
|
|
2494
|
-
function selectFirstSyncCutoff(args) {
|
|
2495
|
-
const { fills, exchangePositions, connector } = args;
|
|
2496
|
-
if (fills.length === 0) {
|
|
2497
|
-
return null;
|
|
2498
|
-
}
|
|
2499
|
-
const snapshot = ExchangeSnapshot.from(exchangePositions);
|
|
2500
|
-
const book = Exposure.empty();
|
|
2501
|
-
for (const symbol of snapshot.symbols()) {
|
|
2502
|
-
book.addDelta(symbol, snapshot.signedSize(symbol));
|
|
2503
|
-
}
|
|
2504
|
-
const fillSymbols = new Set(fills.map((fill) => fill.symbol));
|
|
2505
|
-
for (const symbol of snapshot.symbols()) {
|
|
2506
|
-
if (!snapshot.signedSize(symbol).isZero() && !fillSymbols.has(symbol)) {
|
|
2507
|
-
return null;
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
const pearIndex = connector === "hyperliquid" ? findFirstPearFillIndex(connector, fills) : null;
|
|
2511
|
-
for (let boundary = fills.length; boundary >= 0; boundary--) {
|
|
2512
|
-
const withinAnchor = pearIndex === null || boundary <= pearIndex;
|
|
2513
|
-
if (withinAnchor && book.isAllZero() && isStrictTimestampBoundary(fills, boundary)) {
|
|
2514
|
-
return boundary === 0 ? null : {
|
|
2515
|
-
keepFromIndex: boundary
|
|
2516
|
-
};
|
|
2517
|
-
}
|
|
2518
|
-
const preceding = fills[boundary - 1];
|
|
2519
|
-
if (preceding) {
|
|
2520
|
-
book.addDelta(preceding.symbol, signedBySide(preceding.quantity, preceding.side).negated());
|
|
2521
|
-
}
|
|
2522
|
-
}
|
|
2523
|
-
return null;
|
|
2524
|
-
}
|
|
2525
|
-
__name(selectFirstSyncCutoff, "selectFirstSyncCutoff");
|
|
2526
|
-
function findFirstPearFillIndex(connector, fills) {
|
|
2527
|
-
const index = fills.findIndex((fill) => isPearCloid(connector, fill.cloid));
|
|
2528
|
-
if (index === -1) {
|
|
2529
|
-
return null;
|
|
2530
|
-
}
|
|
2531
|
-
return index;
|
|
2532
|
-
}
|
|
2533
|
-
__name(findFirstPearFillIndex, "findFirstPearFillIndex");
|
|
2534
|
-
function isStrictTimestampBoundary(fills, boundary) {
|
|
2535
|
-
const before = fills[boundary - 1];
|
|
2536
|
-
const after = fills[boundary];
|
|
2537
|
-
if (!before || !after) {
|
|
2538
|
-
return true;
|
|
2539
|
-
}
|
|
2540
|
-
return before.timestamp < after.timestamp;
|
|
2541
|
-
}
|
|
2542
|
-
__name(isStrictTimestampBoundary, "isStrictTimestampBoundary");
|
|
2543
|
-
|
|
2544
|
-
// src/sync/compute.ts
|
|
2545
|
-
async function computeSyncPayload(args) {
|
|
2546
|
-
const { tradeAccountId, connector, openPositions, entryBySymbol, provisionalFills, confirmedExchangeFillIds, externalFills, exchangePositions, neverSynced } = args;
|
|
2547
|
-
const fillsToProcess = _prepareExternalFills(externalFills, confirmedExchangeFillIds);
|
|
2548
|
-
const orphans = _excludeClaimedFills(provisionalFills.filter((fill) => fill.tradeAccountId === tradeAccountId && fill.connector === connector), fillsToProcess);
|
|
2549
|
-
const { keptOrphans, firstSyncTrim } = applyFirstSyncCutoff({
|
|
2550
|
-
neverSynced,
|
|
2551
|
-
fillsToProcess,
|
|
2552
|
-
orphans,
|
|
2553
|
-
exchangePositions,
|
|
2554
|
-
connector
|
|
2555
|
-
});
|
|
2556
|
-
const { fills, applicables } = buildApplicables({
|
|
2557
|
-
tradeAccountId,
|
|
2558
|
-
connector,
|
|
2559
|
-
orphanFills: keptOrphans,
|
|
2560
|
-
openPositions,
|
|
2561
|
-
entryBySymbol,
|
|
2562
|
-
exchangePositions
|
|
2563
|
-
});
|
|
2564
|
-
const mustPostFirstSyncCheckpoint = neverSynced;
|
|
2565
|
-
if (!mustPostFirstSyncCheckpoint && applicables.length === 0) {
|
|
2566
|
-
return null;
|
|
2567
|
-
}
|
|
2568
|
-
const externalMaxTs = latestFillTimestamp(fillsToProcess);
|
|
2569
|
-
return {
|
|
2570
|
-
fills,
|
|
2571
|
-
applicables,
|
|
2572
|
-
exchangePositions,
|
|
2573
|
-
meta: {
|
|
2574
|
-
externalMaxTs
|
|
2575
|
-
},
|
|
2576
|
-
firstSyncTrim
|
|
2577
|
-
};
|
|
2578
|
-
}
|
|
2579
|
-
__name(computeSyncPayload, "computeSyncPayload");
|
|
2580
|
-
function _prepareExternalFills(externalFills, confirmedExchangeFillIdsInput) {
|
|
2581
|
-
const dedupedByExchangeFillId = /* @__PURE__ */ new Map();
|
|
2582
|
-
const confirmedExchangeFillIds = new Set(Array.from(confirmedExchangeFillIdsInput).map((exchangeFillId) => _normalizeExchangeFillId(exchangeFillId)).filter((exchangeFillId) => exchangeFillId !== void 0));
|
|
2583
|
-
const candidates = externalFills.reduce((acc, fill) => {
|
|
2584
|
-
const normalizedExchangeFillId = _normalizeExchangeFillId(fill.exchangeFillId);
|
|
2585
|
-
if (!normalizedExchangeFillId) {
|
|
2586
|
-
return acc;
|
|
2587
|
-
}
|
|
2588
|
-
if (!_isValidExternalQuantity(fill.quantity)) {
|
|
2589
|
-
return acc;
|
|
2590
|
-
}
|
|
2591
|
-
if (!_isValidExternalTimestamp(fill.timestamp)) {
|
|
2592
|
-
return acc;
|
|
2593
|
-
}
|
|
2594
|
-
if (confirmedExchangeFillIds.has(normalizedExchangeFillId)) {
|
|
2595
|
-
return acc;
|
|
2596
|
-
}
|
|
2597
|
-
acc.push({
|
|
2598
|
-
...fill,
|
|
2599
|
-
exchangeFillId: normalizedExchangeFillId
|
|
2600
|
-
});
|
|
2601
|
-
return acc;
|
|
2602
|
-
}, []);
|
|
2603
|
-
for (const fill of candidates) {
|
|
2604
|
-
const existing = dedupedByExchangeFillId.get(fill.exchangeFillId);
|
|
2605
|
-
if (!existing || fill.timestamp < existing.timestamp) {
|
|
2606
|
-
dedupedByExchangeFillId.set(fill.exchangeFillId, fill);
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
return Array.from(dedupedByExchangeFillId.values()).sort((a, b) => a.timestamp - b.timestamp);
|
|
2610
|
-
}
|
|
2611
|
-
__name(_prepareExternalFills, "_prepareExternalFills");
|
|
2612
|
-
function _excludeClaimedFills(provisionalFills, externalFills) {
|
|
2613
|
-
const ownedCloids = /* @__PURE__ */ new Set();
|
|
2614
|
-
for (const fill of provisionalFills) {
|
|
2615
|
-
const cloid = _normalizeCloid(fill.cloid);
|
|
2616
|
-
if (cloid) {
|
|
2617
|
-
ownedCloids.add(`${fill.connector}:${cloid}`);
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
return externalFills.filter((fill) => {
|
|
2621
|
-
const cloid = _normalizeCloid(fill.cloid);
|
|
2622
|
-
return cloid === void 0 || !ownedCloids.has(`${fill.connector}:${cloid}`);
|
|
2623
|
-
});
|
|
2624
|
-
}
|
|
2625
|
-
__name(_excludeClaimedFills, "_excludeClaimedFills");
|
|
2626
|
-
function _normalizeCloid(cloid) {
|
|
2627
|
-
const value = cloid?.trim();
|
|
2628
|
-
if (!value) {
|
|
2629
|
-
return void 0;
|
|
2630
|
-
}
|
|
2631
|
-
return value;
|
|
2632
|
-
}
|
|
2633
|
-
__name(_normalizeCloid, "_normalizeCloid");
|
|
2634
|
-
function _normalizeExchangeFillId(exchangeFillId) {
|
|
2635
|
-
const value = exchangeFillId?.trim().toLowerCase();
|
|
2636
|
-
if (!value) {
|
|
2637
|
-
return void 0;
|
|
2638
|
-
}
|
|
2639
|
-
return value;
|
|
2640
|
-
}
|
|
2641
|
-
__name(_normalizeExchangeFillId, "_normalizeExchangeFillId");
|
|
2642
|
-
function _isValidExternalQuantity(quantity) {
|
|
2643
|
-
if (/e/i.test(quantity)) {
|
|
2644
|
-
return false;
|
|
2645
|
-
}
|
|
2646
|
-
if (!isFiniteDecimal(quantity)) {
|
|
2647
|
-
return false;
|
|
2648
|
-
}
|
|
2649
|
-
return isGreaterThan(quantity, ZERO);
|
|
2650
|
-
}
|
|
2651
|
-
__name(_isValidExternalQuantity, "_isValidExternalQuantity");
|
|
2652
|
-
function _isValidExternalTimestamp(timestamp) {
|
|
2653
|
-
if (!Number.isFinite(timestamp) || timestamp < 0) {
|
|
2654
|
-
return false;
|
|
2655
|
-
}
|
|
2656
|
-
return timestamp <= Date.now() + WATERMARK_FUTURE_SKEW_MS;
|
|
2657
|
-
}
|
|
2658
|
-
__name(_isValidExternalTimestamp, "_isValidExternalTimestamp");
|
|
2659
|
-
function latestFillTimestamp(fills) {
|
|
2660
|
-
const last = fills[fills.length - 1];
|
|
2661
|
-
if (!last) {
|
|
2662
|
-
return null;
|
|
2663
|
-
}
|
|
2664
|
-
return last.timestamp;
|
|
2665
|
-
}
|
|
2666
|
-
__name(latestFillTimestamp, "latestFillTimestamp");
|
|
2667
|
-
function applyFirstSyncCutoff(args) {
|
|
2668
|
-
const { neverSynced, fillsToProcess, orphans, exchangePositions, connector } = args;
|
|
2669
|
-
if (!neverSynced || fillsToProcess.length === 0) {
|
|
2670
|
-
return {
|
|
2671
|
-
keptOrphans: orphans,
|
|
2672
|
-
firstSyncTrim: null
|
|
2673
|
-
};
|
|
2674
|
-
}
|
|
2675
|
-
const cutoff = selectFirstSyncCutoff({
|
|
2676
|
-
fills: fillsToProcess,
|
|
2677
|
-
exchangePositions,
|
|
2678
|
-
connector
|
|
2679
|
-
});
|
|
2680
|
-
if (!cutoff) {
|
|
2681
|
-
return {
|
|
2682
|
-
keptOrphans: orphans,
|
|
2683
|
-
firstSyncTrim: null
|
|
2684
|
-
};
|
|
2685
|
-
}
|
|
2686
|
-
const keptIds = new Set(fillsToProcess.slice(cutoff.keepFromIndex).map((fill) => fill.exchangeFillId));
|
|
2687
|
-
const keptOrphans = [];
|
|
2688
|
-
const droppedOrphans = [];
|
|
2689
|
-
for (const orphan of orphans) {
|
|
2690
|
-
(keptIds.has(orphan.exchangeFillId) ? keptOrphans : droppedOrphans).push(orphan);
|
|
2691
|
-
}
|
|
2692
|
-
if (droppedOrphans.length === 0) {
|
|
2693
|
-
return {
|
|
2694
|
-
keptOrphans,
|
|
2695
|
-
firstSyncTrim: null
|
|
2696
|
-
};
|
|
2697
|
-
}
|
|
2698
|
-
const firstKeptFill = fillsToProcess[cutoff.keepFromIndex];
|
|
2699
|
-
return {
|
|
2700
|
-
keptOrphans,
|
|
2701
|
-
firstSyncTrim: {
|
|
2702
|
-
cutoffTs: firstKeptFill ? firstKeptFill.timestamp : null,
|
|
2703
|
-
trimmedCount: droppedOrphans.length,
|
|
2704
|
-
trimmedMaxTs: droppedOrphans.reduce((max, fill) => fill.timestamp > max ? fill.timestamp : max, 0)
|
|
2705
|
-
}
|
|
2706
|
-
};
|
|
2707
|
-
}
|
|
2708
|
-
__name(applyFirstSyncCutoff, "applyFirstSyncCutoff");
|
|
2709
|
-
|
|
2710
|
-
// src/validate-leverage.ts
|
|
2711
|
-
function validateLeverage(leverage, instrument) {
|
|
2712
|
-
if (leverage < 1 || leverage > 100) {
|
|
2713
|
-
return false;
|
|
2714
|
-
}
|
|
2715
|
-
if (leverage > instrument.leverage) {
|
|
2716
|
-
return false;
|
|
2717
|
-
}
|
|
2718
|
-
return true;
|
|
1537
|
+
return true;
|
|
2719
1538
|
}
|
|
2720
1539
|
__name(validateLeverage, "validateLeverage");
|
|
2721
1540
|
var VAULT_ACTION_DOMAIN = {
|
|
@@ -2800,6 +1619,26 @@ var VAULT_ACTION_TYPES = {
|
|
|
2800
1619
|
}
|
|
2801
1620
|
]
|
|
2802
1621
|
},
|
|
1622
|
+
"set-account-mode": {
|
|
1623
|
+
SetAccountMode: [
|
|
1624
|
+
{
|
|
1625
|
+
name: "vault",
|
|
1626
|
+
type: "address"
|
|
1627
|
+
},
|
|
1628
|
+
{
|
|
1629
|
+
name: "mode",
|
|
1630
|
+
type: "string"
|
|
1631
|
+
},
|
|
1632
|
+
{
|
|
1633
|
+
name: "nonce",
|
|
1634
|
+
type: "string"
|
|
1635
|
+
},
|
|
1636
|
+
{
|
|
1637
|
+
name: "deadline",
|
|
1638
|
+
type: "uint256"
|
|
1639
|
+
}
|
|
1640
|
+
]
|
|
1641
|
+
},
|
|
2803
1642
|
"update-metadata": {
|
|
2804
1643
|
UpdateVaultMetadata: [
|
|
2805
1644
|
{
|
|
@@ -2913,6 +1752,17 @@ function buildLeaderActionTypedData(action, vaultAddress, body) {
|
|
|
2913
1752
|
deadline
|
|
2914
1753
|
}
|
|
2915
1754
|
};
|
|
1755
|
+
case "set-account-mode":
|
|
1756
|
+
return {
|
|
1757
|
+
types: VAULT_ACTION_TYPES[action],
|
|
1758
|
+
primaryType: "SetAccountMode",
|
|
1759
|
+
message: {
|
|
1760
|
+
vault,
|
|
1761
|
+
mode: String(body.mode ?? ""),
|
|
1762
|
+
nonce,
|
|
1763
|
+
deadline
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
2916
1766
|
case "update-metadata":
|
|
2917
1767
|
return {
|
|
2918
1768
|
types: VAULT_ACTION_TYPES[action],
|
|
@@ -3029,4 +1879,4 @@ function takeVerifier(handoffId, storage) {
|
|
|
3029
1879
|
}
|
|
3030
1880
|
__name(takeVerifier, "takeVerifier");
|
|
3031
1881
|
|
|
3032
|
-
export { BIPS, CREATE_VAULT_TYPES, InvalidPriceError, VAULT_ACTION_DOMAIN, VAULT_ACTION_TYPES, ZERO, abs, add, aggregateRealizedPositionState, baseToContracts,
|
|
1882
|
+
export { BIPS, CREATE_VAULT_TYPES, InvalidPriceError, VAULT_ACTION_DOMAIN, VAULT_ACTION_TYPES, ZERO, abs, add, aggregateRealizedPositionState, baseToContracts, buildCreateVaultMessage, buildLeaderActionTypedData, compareValue, computeAssetEntryNotional, computeAssetGrossRealizedPnL, computeAssetRealizedPnLs, computeAssetUndeterminedSizes, computeAssetUnrealizedPnL, computeAssetUnrealizedPnLs, computeBasketWeightedRatioV1, computeDeployedCapital, computeDeployedCapitalState, computeEntryEquityFraction, computeEntryPriceForAsset, computeEquityFraction, computeFillTransitions, computeFilledEntrySizing, computeMaxBasketSize, computeMultiQuoteBasketSize, computeNotionalEquityFraction, computePositionEntryPrices, computeRealizedPnlBySymbol, computeRealizedPnlFromFills, computeRealizedPositionState, computeUnrealizedPositionState, computeUnrealizedPositionStateFromEntryPrices, configureProxy, contractsToBase, countDecimals, deriveBasketKey, divide, exponentiate, findWholeCloseQuantity, finishXHandoff, generateDeterministicPositionKey, groupFillsByAsset, groupFillsByPosition, isEqualTo, isFiniteDecimal, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual, isNegative, isPositive, isRefusedBelowMinimum, isZero, marketPricingFor, min, multiply, negate, parse, precisePrice, preciseQuantity, preciseQuantityWithMode, proxyFetch, requestedBaseQuantity, resolveMarketLimitPrice, resolveMarketReference, sideSign, sign, signed, signedBySide, sizeBasket, sizeBasketByLeverageMode, sizeBasketPerAssetLeverage, startXHandoff, stripXReturn, subtract, sumAttributedFunding, toDecimalString, validateLeverage, validateQuantity, walkPositionLedger, walkPositionTransitions };
|