Skip to content

Commit

Permalink
fix order calcs
Browse files Browse the repository at this point in the history
  • Loading branch information
tyleroooo committed Dec 17, 2024
1 parent 32beb8b commit 4882993
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 76 deletions.
144 changes: 110 additions & 34 deletions src/abacus-ts/calculators/orders.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,87 @@
import { IndexerBestEffortOpenedStatus, IndexerOrderStatus } from '@/types/indexer/indexerApiGen';
import { IndexerCompositeOrderObject } from '@/types/indexer/indexerManual';
import { maxBy, pickBy } from 'lodash';

import { SubaccountOrder } from '@/constants/abacus';
import { HeightResponse } from '@dydxprotocol/v4-client-js';
import { mapValues, maxBy, pickBy } from 'lodash';

import { assertNever } from '@/lib/assertNever';
import { MustBigNumber } from '@/lib/numbers';
import { getDisplayableTickerFromMarket } from '@/lib/assetUtils';
import { mapIfPresent } from '@/lib/do';
import { MaybeBigNumber, MustBigNumber } from '@/lib/numbers';

import { Loadable } from '../lib/loadable';
import { mapLoadableData } from '../lib/mapLoadable';
import { mapLoadableData, mergeLoadableData } from '../lib/mapLoadable';
import { mergeObjects } from '../lib/mergeObjects';
import { OrdersData } from '../rawTypes';
import { OrderStatus } from '../summaryTypes';

// todo these are calculating the same thing twice pasically
function calculateOpenOrders(liveOrders: Loadable<OrdersData>, restOrders: Loadable<OrdersData>) {
const getOpenOrders = (data: Loadable<OrdersData>) =>
mapLoadableData(data, (d) =>
pickBy(
d,
(order) =>
getSimpleOrderStatus(calculateOrderStatus(order) ?? OrderStatus.Open) === OrderStatus.Open
)
);
return calculateMergedOrders(getOpenOrders(liveOrders), getOpenOrders(restOrders));
import { OrderStatus, SubaccountOrder, SubaccountOrdersData } from '../summaryTypes';

export function calculateOpenOrders(orders: Loadable<SubaccountOrdersData>) {
return mapLoadableData(orders, (d) =>
pickBy(
d,
(order) => getSimpleOrderStatus(order.status ?? OrderStatus.Open) === OrderStatus.Open
)
);
}

export function calculateOrderHistory(orders: Loadable<SubaccountOrdersData>) {
return mapLoadableData(orders, (d) =>
pickBy(
d,
(order) => getSimpleOrderStatus(order.status ?? OrderStatus.Open) !== OrderStatus.Open
)
);
}

function calculateOrderHistory(liveOrders: Loadable<OrdersData>, restOrders: Loadable<OrdersData>) {
const getNonOpenOrders = (data: Loadable<OrdersData>) =>
mapLoadableData(data, (d) =>
pickBy(
d,
(order) =>
getSimpleOrderStatus(calculateOrderStatus(order) ?? OrderStatus.Open) !== OrderStatus.Open
)
);
return calculateMergedOrders(getNonOpenOrders(liveOrders), getNonOpenOrders(restOrders));
export function calculateAllOrders(
liveOrders: Loadable<OrdersData>,
restOrders: Loadable<OrdersData>,
height: HeightResponse
): Loadable<SubaccountOrdersData> {
const merged = mergeLoadableData(liveOrders, restOrders);
const actuallyMerged = mapLoadableData(merged, ([a, b]) =>
calculateMergedOrders(a ?? {}, b ?? {})
);
const mapped = mapLoadableData(actuallyMerged, (d) =>
mapValues(d, (order) => calculateSubaccountOrder(order, height))
);
return mapped;
}

function calculateSubaccountOrder(
order: IndexerCompositeOrderObject,
protocolHeight: number
): SubaccountOrder {}
base: IndexerCompositeOrderObject,
protocolHeight: HeightResponse
): SubaccountOrder {
let order: SubaccountOrder = {
marketId: base.ticker,
status: calculateBaseOrderStatus(base),
displayId: getDisplayableTickerFromMarket(base.ticker),
expiresAtMilliseconds: mapIfPresent(base.goodTilBlockTime, (u) => new Date(u).valueOf()),
updatedAtMilliseconds: mapIfPresent(base.updatedAt, (u) => new Date(u).valueOf()),
updatedAtHeight: MaybeBigNumber(base.updatedAtHeight)?.toNumber(),
marginMode: undefined,
subaccountNumber: base.subaccountNumber,
id: base.id,
clientId: base.clientId,
type: base.type,
side: base.side,
timeInForce: base.timeInForce,
clobPairId: MaybeBigNumber(base.clobPairId)?.toNumber(),
orderFlags: base.orderFlags,
price: MustBigNumber(base.price),
triggerPrice: MaybeBigNumber(base.triggerPrice),
size: MustBigNumber(base.size),
totalFilled: MustBigNumber(base.totalFilled),
goodTilBlock: MaybeBigNumber(base.goodTilBlock)?.toNumber(),
goodTilBlockTime: mapIfPresent(base.goodTilBlockTime, (u) => new Date(u).valueOf()),
createdAtHeight: MaybeBigNumber(base.createdAtHeight)?.toNumber(),
postOnly: !!base.postOnly,
reduceOnly: !!base.reduceOnly,
remainingSize: MustBigNumber(base.size).minus(MustBigNumber(base.totalFilled)),
removalReason: base.removalReason,
};
order = maybeUpdateOrderIfExpired(order, protocolHeight);
return order;
}

function getSimpleOrderStatus(status: OrderStatus) {
switch (status) {
Expand All @@ -63,6 +103,44 @@ function getSimpleOrderStatus(status: OrderStatus) {
}
}

function maybeUpdateOrderIfExpired(
order: SubaccountOrder,
height: HeightResponse
): SubaccountOrder {
if (order.status == null) {
return order;
}
// todo: why not handle Open?
if (
![OrderStatus.Pending, OrderStatus.Canceling, OrderStatus.PartiallyFilled].includes(
order.status
)
) {
return order;
}

// Check if order has expired based on goodTilBlock
if (order.goodTilBlock && order.goodTilBlock !== 0 && height.height >= order.goodTilBlock) {
let status = OrderStatus.Canceled;

// Check for partial fills
if (order.totalFilled != null && order.totalFilled.gt(0)) {
const remainingSize = order.size.minus(order.totalFilled);
if (order.totalFilled.gt(0) && remainingSize.gt(0)) {
status = OrderStatus.PartiallyCanceled;
}
}

return {
...order,
status,
updatedAtMilliseconds: new Date(height.time).valueOf(),
};
}

return order;
}

function calculateBaseOrderStatus(order: IndexerCompositeOrderObject): OrderStatus | undefined {
const status = order.status;
if (status == null) {
Expand Down Expand Up @@ -118,9 +196,7 @@ function calculateBaseOrderStatus(order: IndexerCompositeOrderObject): OrderStat
}
}

function calculateMergedOrders(liveOrders: Loadable<OrdersData>, restOrders: Loadable<OrdersData>) {
const liveData = liveOrders.data ?? {};
const restData = restOrders.data ?? {};
function calculateMergedOrders(liveData: OrdersData, restData: OrdersData) {
return mergeObjects(
liveData,
restData,
Expand Down
33 changes: 17 additions & 16 deletions src/abacus-ts/calculators/subaccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { mapValues } from 'lodash';
import { NUM_PARENT_SUBACCOUNTS } from '@/constants/account';

import { calc } from '@/lib/do';
import { MaybeBigNumber, MustBigNumber, ToBigNumber } from '@/lib/numbers';
import { BIG_NUMBERS, MaybeBigNumber, MustBigNumber, ToBigNumber } from '@/lib/numbers';
import { isPresent } from '@/lib/typeUtils';

import { ChildSubaccountData, MarketsData, ParentSubaccountData } from '../rawTypes';
Expand All @@ -25,9 +25,6 @@ import {
SubaccountSummaryDerived,
} from '../summaryTypes';

const BN_0 = MustBigNumber(0);
const BN_1 = MustBigNumber(1);

export function calculateParentSubaccountPositions(
parent: Omit<ParentSubaccountData, 'ephemeral'>,
markets: MarketsData
Expand Down Expand Up @@ -61,7 +58,7 @@ export function calculateParentSubaccountSummary(
equity: Object.values(summaries)
.filter(isPresent)
.map((s) => s.equity)
.reduce((a, b) => a.plus(b), BN_0),
.reduce((a, b) => a.plus(b), BIG_NUMBERS.ZERO),
};
}

Expand All @@ -82,7 +79,7 @@ function calculateSubaccountSummaryCore(
): SubaccountSummaryCore {
const quoteBalance = calc(() => {
const usdcPosition = subaccountData.assetPositions.USDC;
if (!usdcPosition?.size) return BN_0;
if (!usdcPosition?.size) return BIG_NUMBERS.ZERO;

const size = MustBigNumber(usdcPosition.size);
return usdcPosition.side === IndexerPositionSide.LONG ? size : size.negated();
Expand Down Expand Up @@ -111,10 +108,10 @@ function calculateSubaccountSummaryCore(
};
},
{
valueTotal: BN_0,
notionalTotal: BN_0,
initialRiskTotal: BN_0,
maintenanceRiskTotal: BN_0,
valueTotal: BIG_NUMBERS.ZERO,
notionalTotal: BIG_NUMBERS.ZERO,
initialRiskTotal: BIG_NUMBERS.ZERO,
maintenanceRiskTotal: BIG_NUMBERS.ZERO,
}
);

Expand All @@ -138,7 +135,7 @@ function calculateSubaccountSummaryDerived(core: SubaccountSummaryCore): Subacco

if (equity.gt(0)) {
leverage = notionalTotal.div(equity);
marginUsage = BN_1.minus(freeCollateral.div(equity));
marginUsage = BIG_NUMBERS.ONE.minus(freeCollateral.div(equity));
}

return {
Expand Down Expand Up @@ -185,12 +182,14 @@ function calculateDerivedPositionCore(
): SubaccountPositionDerivedCore {
const marginMode = position.subaccountNumber < NUM_PARENT_SUBACCOUNTS ? 'CROSS' : 'ISOLATED';
const effectiveImf =
market != null ? getMarketEffectiveInitialMarginForMarket(market) ?? BN_0 : BN_0;
const effectiveMmf = MaybeBigNumber(market?.maintenanceMarginFraction) ?? BN_0;
market != null
? getMarketEffectiveInitialMarginForMarket(market) ?? BIG_NUMBERS.ZERO
: BIG_NUMBERS.ZERO;
const effectiveMmf = MaybeBigNumber(market?.maintenanceMarginFraction) ?? BIG_NUMBERS.ZERO;

// indexer position size is already signed I think but we will be extra sure
const unsignedSize = position.size.abs();
const oracle = MaybeBigNumber(market?.oraclePrice) ?? BN_0;
const oracle = MaybeBigNumber(market?.oraclePrice) ?? BIG_NUMBERS.ZERO;
const signedSize =
position.side === IndexerPositionSide.SHORT ? unsignedSize.negated() : unsignedSize;

Expand All @@ -211,7 +210,7 @@ function calculateDerivedPositionCore(
if (effectiveImf.isZero()) {
return null;
}
return BN_1.div(effectiveImf);
return BIG_NUMBERS.ONE.div(effectiveImf);
}),
baseEntryPrice: position.entryPrice,
baseNetFunding: position.netFunding,
Expand Down Expand Up @@ -254,7 +253,9 @@ function calculatePositionDerivedExtra(
const entryValue = signedSize.multipliedBy(MustBigNumber(position.baseEntryPrice));
const unrealizedPnlInner = value.minus(entryValue).plus(MustBigNumber(position.baseNetFunding));

const scaledLeverage = leverage ? BigNumber.max(leverage.abs(), BN_1) : BN_1;
const scaledLeverage = leverage
? BigNumber.max(leverage.abs(), BIG_NUMBERS.ONE)
: BIG_NUMBERS.ONE;

const unrealizedPnlPercentInner = !entryValue.isZero()
? unrealizedPnlInner.dividedBy(entryValue.abs()).multipliedBy(scaledLeverage)
Expand Down
12 changes: 12 additions & 0 deletions src/abacus-ts/lib/mapLoadable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,15 @@ export function mapLoadableData<T, R>(load: Loadable<T>, map: (obj: T) => R): Lo
data: load.data != null ? map(load.data) : undefined,
} as Loadable<R>;
}

export function mergeLoadableData<T, R>(
one: Loadable<T>,
two: Loadable<R>
): Loadable<[T | undefined, R | undefined]> {
const priority = ['pending', 'error', 'success', 'idle'] as const;
return {
status: priority[Math.min(priority.indexOf(one.status), priority.indexOf(two.status))]!,
error: (one as any).error ?? (two as any).error ?? undefined,
data: [one.data, two.data],
} as any;
}
35 changes: 17 additions & 18 deletions src/abacus-ts/summaryTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,30 +102,29 @@ export enum OrderStatus {
export type SubaccountOrder = {
subaccountNumber: number;
id: string;
clientId: string | null;
clientId: string | undefined;
type: IndexerOrderType;
side: IndexerOrderSide;
status: OrderStatus;
timeInForce: IndexerAPITimeInForce | null;
status: OrderStatus | undefined;
timeInForce: IndexerAPITimeInForce | undefined;
marketId: string;
displayId: string;
clobPairId: number | null;
orderFlags: string | null;
clobPairId: number | undefined;
orderFlags: string | undefined;
price: BigNumber;
triggerPrice: BigNumber | null;
trailingPercent: BigNumber | null;
triggerPrice: BigNumber | undefined;
size: BigNumber;
remainingSize: BigNumber | null;
totalFilled: BigNumber | null;
goodTilBlock: number | null;
goodTilBlockTime: number | null;
createdAtHeight: number | null;
createdAtMilliseconds: number | null;
unfillableAtMilliseconds: number | null;
expiresAtMilliseconds: number | null;
updatedAtMilliseconds: number | null;
remainingSize: BigNumber | undefined;
totalFilled: BigNumber | undefined;
goodTilBlock: number | undefined;
goodTilBlockTime: number | undefined;
createdAtHeight: number | undefined;
expiresAtMilliseconds: number | undefined;
updatedAtMilliseconds: number | undefined;
updatedAtHeight: number | undefined;
postOnly: boolean;
reduceOnly: boolean;
cancelReason: string | null;
marginMode: MarginMode | null;
removalReason: string | undefined;
marginMode: MarginMode | undefined;
};
export type SubaccountOrdersData = { [orderId: string]: SubaccountOrder };
16 changes: 8 additions & 8 deletions src/types/indexer/indexerManual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ export interface IndexerCompositeFillResponse {
}

export interface IndexerCompositeOrderObject {
id?: string;
id: string;
subaccountId?: string;
clientId?: string;
clobPairId?: string;
side?: IndexerOrderSide;
size?: string;
totalFilled?: string;
price?: string;
type?: IndexerOrderType;
side: IndexerOrderSide;
size: string;
totalFilled: string;
price: string;
type: IndexerOrderType;
reduceOnly?: boolean;
orderFlags?: string;
goodTilBlock?: string | null;
Expand All @@ -44,10 +44,10 @@ export interface IndexerCompositeOrderObject {
timeInForce?: IndexerAPITimeInForce;
status?: IndexerAPIOrderStatus;
postOnly?: boolean;
ticker?: string;
ticker: string;
updatedAt?: IndexerIsoString | null;
updatedAtHeight?: string | null;
subaccountNumber?: number;
subaccountNumber: number;
removalReason?: string;
totalOptimisticFilled?: string;
}
Expand Down

0 comments on commit 4882993

Please sign in to comment.