Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: abacus-ts calculations flow #1365

Merged
merged 81 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
d2f3489
abacus-ts-types
tyleroooo Nov 27, 2024
f887e6c
add codegen and helpers and manual types
tyleroooo Nov 27, 2024
94ef1e3
Merge remote-tracking branch 'origin/main' into tu/abacus-ts-types
tyleroooo Nov 27, 2024
71f8dd8
fixes
tyleroooo Nov 27, 2024
e0e05ff
fix
tyleroooo Nov 27, 2024
6ddeb68
add websocket layer
tyleroooo Dec 2, 2024
7c25ca7
fix
tyleroooo Dec 2, 2024
8305f2e
fix
tyleroooo Dec 2, 2024
202f169
add market tracker and helpers
tyleroooo Dec 3, 2024
27acae5
fix
tyleroooo Dec 3, 2024
7cd5506
fix
tyleroooo Dec 3, 2024
595e1a0
bunch of fixes and refactors
tyleroooo Dec 4, 2024
306b31b
fix
tyleroooo Dec 5, 2024
7a85a86
Merge remote-tracking branch 'origin/main' into tu/abacus-ts-types
tyleroooo Dec 5, 2024
91f1196
fix
tyleroooo Dec 6, 2024
15621c1
add all new
tyleroooo Dec 6, 2024
d0855de
Merge remote-tracking branch 'origin/main' into tu/abacus-ts-types
tyleroooo Dec 6, 2024
fd1a39d
fixes
tyleroooo Dec 6, 2024
0da5633
add more data
tyleroooo Dec 6, 2024
3cadec2
went wild
tyleroooo Dec 9, 2024
1c516a3
fixes
tyleroooo Dec 9, 2024
f0cfc7a
fix
tyleroooo Dec 9, 2024
0198c59
move files around, fix logging
tyleroooo Dec 10, 2024
6189fa1
fix
tyleroooo Dec 10, 2024
dcc53a8
fix
tyleroooo Dec 10, 2024
75ff184
fix
tyleroooo Dec 10, 2024
6856775
fix
tyleroooo Dec 10, 2024
88cb956
fix
tyleroooo Dec 10, 2024
4f4e061
fix
tyleroooo Dec 10, 2024
e965fb5
fix
tyleroooo Dec 10, 2024
74f78ef
start
tyleroooo Dec 10, 2024
5172c36
fix
tyleroooo Dec 10, 2024
134567d
Merge branch 'tu/abacus-ts-types' into tu/abacus-ts-2
tyleroooo Dec 10, 2024
5486c65
fix
tyleroooo Dec 10, 2024
a6fe44f
fix
tyleroooo Dec 10, 2024
07ce170
Merge branch 'tu/abacus-ts-types' into tu/abacus-ts-2
tyleroooo Dec 10, 2024
13e0acb
more types
tyleroooo Dec 11, 2024
59a27a0
updates
tyleroooo Dec 12, 2024
bfc405e
fix
tyleroooo Dec 12, 2024
917832c
fixes
tyleroooo Dec 12, 2024
9772e4d
merge
tyleroooo Dec 12, 2024
41bb0af
fix
tyleroooo Dec 12, 2024
e27402c
Merge branch 'tu/abacus-ts-types' into tu/abacus-ts-2
tyleroooo Dec 12, 2024
73d52e8
fix
tyleroooo Dec 12, 2024
2487d1d
fix horrible stupid mistake
tyleroooo Dec 12, 2024
a58d832
Merge branch 'tu/abacus-ts-types' into tu/abacus-ts-2
tyleroooo Dec 12, 2024
c419034
more calc
tyleroooo Dec 12, 2024
eadb0b4
fixes
tyleroooo Dec 13, 2024
e7d5a2a
order calculations
tyleroooo Dec 13, 2024
2c7563a
fix
tyleroooo Dec 13, 2024
dc2500a
move stuff around
tyleroooo Dec 13, 2024
d1e187c
fix
tyleroooo Dec 16, 2024
2bbe7af
add validator calls
tyleroooo Dec 16, 2024
32beb8b
Merge branch 'tu/abacus-ts-types' into tu/abacus-ts-2
tyleroooo Dec 16, 2024
4882993
fix order calcs
tyleroooo Dec 17, 2024
75c9297
Merge remote-tracking branch 'origin/main' into tu/abacus-ts-types
tyleroooo Dec 17, 2024
4399e36
dedupe
tyleroooo Dec 17, 2024
3c7a33c
Merge remote-tracking branch 'origin/tu/abacus-ts-types' into tu/abac…
tyleroooo Dec 17, 2024
a226648
fix
tyleroooo Dec 17, 2024
684de6b
fix
tyleroooo Dec 17, 2024
5fbe076
add
tyleroooo Dec 17, 2024
f3ec57f
Merge remote-tracking branch 'origin/tu/abacus-ts-types' into tu/abac…
tyleroooo Dec 17, 2024
517bd47
fix
tyleroooo Dec 17, 2024
c868496
fix
tyleroooo Dec 17, 2024
d628fd0
fix
tyleroooo Dec 17, 2024
1a9928c
fix
tyleroooo Dec 17, 2024
6e2dff3
fix
tyleroooo Dec 17, 2024
3f02331
fix
tyleroooo Dec 17, 2024
2c3419d
small fix
tyleroooo Dec 17, 2024
bd867e9
assets, immerjs, more changes
tyleroooo Dec 17, 2024
2ee4069
fix
tyleroooo Dec 17, 2024
0c567b7
Merge remote-tracking branch 'origin/tu/abacus-ts-types' into tu/abac…
tyleroooo Dec 17, 2024
fcc5306
fix
tyleroooo Dec 17, 2024
c9b8375
fix
tyleroooo Dec 17, 2024
7d7d7cb
Merge remote-tracking branch 'origin/tu/abacus-ts-types' into tu/abac…
tyleroooo Dec 17, 2024
ce5abdf
more fixes
tyleroooo Dec 18, 2024
266764f
Merge remote-tracking branch 'origin/main' into tu/abacus-ts-2
tyleroooo Dec 18, 2024
1c25c01
add markets calculation
tyleroooo Dec 18, 2024
57f6c84
rename to live
tyleroooo Dec 18, 2024
69677c2
fix
tyleroooo Dec 18, 2024
10356cf
fix
tyleroooo Dec 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/abacus-ts/calculators/blockRewards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IndexerHistoricalBlockTradingReward } from '@/types/indexer/indexerApiGen';
import { keyBy, maxBy } from 'lodash';

import { MustBigNumber } from '@/lib/numbers';

import { Loadable } from '../lib/loadable';
import { mapLoadableData } from '../lib/mapLoadable';
import { mergeObjects } from '../lib/mergeObjects';

export function calculateBlockRewards(
liveTransfers: Loadable<IndexerHistoricalBlockTradingReward[]>,
restTransfers: Loadable<IndexerHistoricalBlockTradingReward[]>
) {
const getRewardsById = (data: Loadable<IndexerHistoricalBlockTradingReward[]>) =>
mapLoadableData(data, (d) => keyBy(d, (reward) => reward.createdAtHeight));
return mergeObjects(
getRewardsById(liveTransfers).data ?? {},
getRewardsById(restTransfers).data ?? {},
(first, second) => maxBy([first, second], (f) => MustBigNumber(f.createdAtHeight).toNumber())!
tyleroooo marked this conversation as resolved.
Show resolved Hide resolved
);
}
21 changes: 21 additions & 0 deletions src/abacus-ts/calculators/fills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IndexerCompositeFillObject } from '@/types/indexer/indexerManual';
import { keyBy, maxBy, orderBy } from 'lodash';

import { EMPTY_ARR } from '@/constants/objects';

import { MustBigNumber } from '@/lib/numbers';

import { mergeObjects } from '../lib/mergeObjects';

export function calculateFills(
liveFills: IndexerCompositeFillObject[] | undefined,
tyleroooo marked this conversation as resolved.
Show resolved Hide resolved
restFills: IndexerCompositeFillObject[] | undefined
) {
const getFillsById = (data: IndexerCompositeFillObject[]) => keyBy(data, (fill) => fill.id ?? '');
const merged = mergeObjects(
getFillsById(liveFills ?? EMPTY_ARR),
getFillsById(restFills ?? EMPTY_ARR),
(first, second) => maxBy([first, second], (f) => MustBigNumber(f.createdAtHeight).toNumber())!
);
return orderBy(Object.values(merged), [(f) => f.createdAtHeight], ['desc']);
}
25 changes: 25 additions & 0 deletions src/abacus-ts/calculators/markets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { IndexerPerpetualMarketResponseObject } from '@/types/indexer/indexerApiGen';
import { mapValues } from 'lodash';
import { weakMapMemoize } from 'reselect';

import { TOKEN_DECIMALS, USD_DECIMALS } from '@/constants/numbers';

import { MaybeBigNumber } from '@/lib/numbers';

import { MarketsData } from '../rawTypes';
import { MarketInfo, MarketsInfo } from '../summaryTypes';

export function calculateAllMarkets(markets: MarketsData | undefined): MarketsInfo | undefined {
if (markets == null) {
return markets;
}
return mapValues(markets, calculateMarket);
}

const calculateMarket = weakMapMemoize(
(market: IndexerPerpetualMarketResponseObject): MarketInfo => ({
...market,
stepSizeDecimals: MaybeBigNumber(market.stepSize)?.decimalPlaces() ?? TOKEN_DECIMALS,
tyleroooo marked this conversation as resolved.
Show resolved Hide resolved
tickSizeDecimals: MaybeBigNumber(market.tickSize)?.decimalPlaces() ?? USD_DECIMALS,
})
);
196 changes: 196 additions & 0 deletions src/abacus-ts/calculators/orders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { IndexerBestEffortOpenedStatus, IndexerOrderStatus } from '@/types/indexer/indexerApiGen';
import { IndexerCompositeOrderObject } from '@/types/indexer/indexerManual';
import { HeightResponse } from '@dydxprotocol/v4-client-js';
import { mapValues, maxBy, orderBy } from 'lodash';

import { NUM_PARENT_SUBACCOUNTS } from '@/constants/account';

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

import { mergeObjects } from '../lib/mergeObjects';
import { OrdersData } from '../rawTypes';
import { OrderStatus, SubaccountOrder } from '../summaryTypes';

export function calculateOpenOrders(orders: SubaccountOrder[]) {
return orders.filter(
(order) => order.status == null || getSimpleOrderStatus(order.status) === OrderStatus.Open
);
}

export function calculateOrderHistory(orders: SubaccountOrder[]) {
return orders.filter(
(order) => order.status != null && getSimpleOrderStatus(order.status) !== OrderStatus.Open
);
}

export function calculateAllOrders(
liveOrders: OrdersData | undefined,
restOrders: OrdersData | undefined,
height: HeightResponse
): SubaccountOrder[] {
const actuallyMerged = calculateMergedOrders(liveOrders ?? {}, restOrders ?? {});
const mapped = mapValues(actuallyMerged, (order) => calculateSubaccountOrder(order, height));
return orderBy(Object.values(mapped), [(o) => o.updatedAtHeight], ['desc']);
}

function calculateSubaccountOrder(
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: base.subaccountNumber >= NUM_PARENT_SUBACCOUNTS ? 'ISOLATED' : 'CROSS',
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;
}

export function getSimpleOrderStatus(status: OrderStatus) {
switch (status) {
case OrderStatus.Open:
case OrderStatus.Pending:
case OrderStatus.PartiallyFilled:
case OrderStatus.Untriggered:
case OrderStatus.Canceling:
return OrderStatus.Open;
case OrderStatus.Canceled:
case OrderStatus.PartiallyCanceled:
return OrderStatus.Canceled;
case OrderStatus.Filled:
return OrderStatus.Filled;
default:
assertNever(status);
// should never happen since we made OrderStatus manually
return OrderStatus.Open;
}
}

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(),
updatedAtHeight: height.height,
};
}

return order;
}

function calculateBaseOrderStatus(order: IndexerCompositeOrderObject): OrderStatus | undefined {
const status = order.status;
if (status == null) {
return undefined;
}

if (status === IndexerBestEffortOpenedStatus.BESTEFFORTOPENED) {
return OrderStatus.Pending;
}

// Calculate filled amounts
const size = MustBigNumber(order.size);
const totalFilled = MustBigNumber(order.totalFilled);
const remainingSize = size.minus(totalFilled);
const hasPartialFill = totalFilled.gt(0) && remainingSize.gt(0);

// Handle partial fills
if (hasPartialFill) {
if (status === IndexerOrderStatus.OPEN) {
return OrderStatus.PartiallyFilled;
}
if (status === IndexerOrderStatus.CANCELED) {
return OrderStatus.PartiallyCanceled;
}
}

// Handle short-term order edge case
const isShortTermOrder = order.orderFlags === '0';
const isBestEffortCanceled = status === IndexerOrderStatus.BESTEFFORTCANCELED;
const isUserCanceled =
order.removalReason === 'USER_CANCELED' ||
order.removalReason === 'ORDER_REMOVAL_REASON_USER_CANCELED';

if (isShortTermOrder && isBestEffortCanceled && !isUserCanceled) {
return OrderStatus.Pending;
}

// Direct mapping for remaining cases
switch (status) {
case IndexerOrderStatus.OPEN:
return OrderStatus.Open;
case IndexerOrderStatus.FILLED:
return OrderStatus.Filled;
case IndexerOrderStatus.CANCELED:
return OrderStatus.Canceled;
case IndexerOrderStatus.BESTEFFORTCANCELED:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omg why is this enum not snake cased hahaha

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this an auto-gen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it's generated :/

return OrderStatus.Canceling;
case IndexerOrderStatus.UNTRIGGERED:
return OrderStatus.Untriggered;
default:
assertNever(status);
return undefined;
}
}

function calculateMergedOrders(liveData: OrdersData, restData: OrdersData) {
return mergeObjects(
liveData,
restData,
(a, b) =>
maxBy([a, b], (o) => MustBigNumber(o.updatedAtHeight ?? o.createdAtHeight).toNumber())!
);
}
Loading
Loading