Skip to content

Commit

Permalink
order calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
tyleroooo committed Dec 13, 2024
1 parent eadb0b4 commit e7d5a2a
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 14 deletions.
159 changes: 159 additions & 0 deletions src/abacus-ts/calculators/orders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { IndexerBestEffortOpenedStatus, IndexerOrderStatus } from '@/types/indexer/indexerApiGen';
import { IndexerCompositeOrderObject } from '@/types/indexer/indexerManual';
import { maxBy, pickBy } from 'lodash';

import { SubaccountOrder } from '@/constants/abacus';

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

import { Loadable } from '../lib/loadable';
import { OrdersData } from '../rawTypes';
import { OrderStatus } from '../summaryTypes';

const BN_0 = MustBigNumber(0);

Check failure on line 14 in src/abacus-ts/calculators/orders.ts

View workflow job for this annotation

GitHub Actions / lint

'BN_0' is assigned a value but never used
const BN_1 = MustBigNumber(1);

Check failure on line 15 in src/abacus-ts/calculators/orders.ts

View workflow job for this annotation

GitHub Actions / lint

'BN_1' is assigned a value but never used

// todo these are calculating the same thing twice pasically
function calculateOpenOrders(liveOrders: Loadable<OrdersData>, restOrders: Loadable<OrdersData>) {

Check failure on line 18 in src/abacus-ts/calculators/orders.ts

View workflow job for this annotation

GitHub Actions / lint

'calculateOpenOrders' is defined but never used
const getOpenOrders = (data: Loadable<OrdersData>) =>
mapLoadableData(data, (d) =>
pickBy(
d,
(order) =>
getSimpleOrderStatus(calculateOrderStatus(order) ?? OrderStatus.Open) === OrderStatus.Open
)
);
return calculateMergedOrders(getOpenOrders(liveOrders), getOpenOrders(restOrders));
}

function calculateOrderHistory(liveOrders: Loadable<OrdersData>, restOrders: Loadable<OrdersData>) {

Check failure on line 30 in src/abacus-ts/calculators/orders.ts

View workflow job for this annotation

GitHub Actions / lint

'calculateOrderHistory' is defined but never used
const getNonOpenOrders = (data: Loadable<OrdersData>) =>
mapLoadableData(data, (d) =>
pickBy(
d,
(order) =>
getSimpleOrderStatus(calculateOrderStatus(order) ?? OrderStatus.Open) !== OrderStatus.Open
)
);
return calculateMergedOrders(getNonOpenOrders(liveOrders), getNonOpenOrders(restOrders));
}

function calculateSubaccountOrder(

Check failure on line 42 in src/abacus-ts/calculators/orders.ts

View workflow job for this annotation

GitHub Actions / lint

'calculateSubaccountOrder' is defined but never used
order: IndexerCompositeOrderObject,

Check failure on line 43 in src/abacus-ts/calculators/orders.ts

View workflow job for this annotation

GitHub Actions / lint

'order' is defined but never used. Allowed unused args must match /^_/u
protocolHeight: number

Check failure on line 44 in src/abacus-ts/calculators/orders.ts

View workflow job for this annotation

GitHub Actions / lint

'protocolHeight' is defined but never used. Allowed unused args must match /^_/u
): SubaccountOrder {}

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);
return OrderStatus.Open;
}
}

function calculateBaseOrderStatus(order: IndexerCompositeOrderObject): OrderStatus | undefined {

Check failure on line 66 in src/abacus-ts/calculators/orders.ts

View workflow job for this annotation

GitHub Actions / lint

'calculateBaseOrderStatus' is defined but never used
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:
return OrderStatus.Canceling;
case IndexerOrderStatus.UNTRIGGERED:
return OrderStatus.Untriggered;
default:
assertNever(status);
return undefined;
}
}

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

function mapLoadableData<T, R>(load: Loadable<T>, map: (obj: T) => R): Loadable<R> {
return {
...load,
data: load.data != null ? map(load.data) : undefined,
} as Loadable<R>;
}

type SimpleMap<T> = { [key: string]: T };
function mergeObjects<T>(one: SimpleMap<T>, two: SimpleMap<T>, merge: (a: T, b: T) => T) {
const finalObj: SimpleMap<T> = {};

[...Object.keys(one), ...Object.keys(two)].forEach((key) => {
if (finalObj[key] != null) {
return;
}
const obj = one[key];
const otherObj = two[key];
if (obj != null && otherObj != null) {
finalObj[key] = merge(obj, otherObj);
} else if (obj == null && otherObj == null) {
// do nothing
} else {
// we know one of them is non-null
finalObj[key] = (obj ?? otherObj)!;
}
});
return finalObj;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
IndexerPerpetualMarketResponseObject,
IndexerPerpetualPositionResponseObject,
IndexerPerpetualPositionStatus,
IndexerPositionSide,
} from '@/types/indexer/indexerApiGen';
import BigNumber from 'bignumber.js';
Expand Down Expand Up @@ -37,6 +38,7 @@ export function calculateParentSubaccountPositions(
const subaccount = calculateSubaccountSummary(child, markets);
return Object.values(child.openPerpetualPositions)
.filter(isPresent)
.filter((p) => p.status === IndexerPerpetualPositionStatus.OPEN)
.map((perp) => calculateSubaccountPosition(subaccount, perp, markets[perp.market]));
});
}
Expand All @@ -63,7 +65,7 @@ export function calculateParentSubaccountSummary(
};
}

function calculateSubaccountSummary(
export function calculateSubaccountSummary(
subaccountData: ChildSubaccountData,
markets: MarketsData
): SubaccountSummary {
Expand Down
3 changes: 2 additions & 1 deletion src/abacus-ts/rawTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@/types/indexer/indexerManual';

export type MarketsData = { [marketId: string]: IndexerPerpetualMarketResponseObject };
export type OrdersData = { [orderId: string]: IndexerCompositeOrderObject };

export type OrderbookData = {
bids: { [price: string]: string };
Expand All @@ -28,7 +29,7 @@ export interface ParentSubaccountData {
ephemeral: {
tradingRewards?: IndexerHistoricalBlockTradingReward[];
fills?: IndexerCompositeFillObject[];
orders?: { [orderId: string]: IndexerCompositeOrderObject };
orders?: OrdersData;
transfers?: IndexerTransferResponseObject[];
};
}
Expand Down
53 changes: 51 additions & 2 deletions src/abacus-ts/summaryTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { IndexerPerpetualPositionResponseObject } from '@/types/indexer/indexerApiGen';
import {
IndexerAPITimeInForce,
IndexerOrderSide,
IndexerOrderType,
IndexerPerpetualPositionResponseObject,
} from '@/types/indexer/indexerApiGen';
import { type BigNumber } from 'bignumber.js';

type ReplaceBigNumberInUnion<T> = T extends string ? BigNumber : T;
Expand Down Expand Up @@ -47,8 +52,10 @@ export type SubaccountPositionBase = ConvertStringToBigNumber<
| 'exitPrice'
>;

export type MarginMode = 'ISOLATED' | 'CROSS';

export type SubaccountPositionDerivedCore = {
marginMode: 'ISOLATED' | 'CROSS';
marginMode: MarginMode;

signedSize: BigNumber; // indexer size is signed by default but we make it obvious here
unsignedSize: BigNumber; // always positive
Expand Down Expand Up @@ -80,3 +87,45 @@ export type SubaccountPositionDerivedExtra = {
export type SubaccountPosition = SubaccountPositionBase &
SubaccountPositionDerivedCore &
SubaccountPositionDerivedExtra;

export enum OrderStatus {
Canceled = 'CANCELED',
Canceling = 'BEST_EFFORT_CANCELED',
Filled = 'FILLED',
Open = 'OPEN',
Pending = 'PENDING',
Untriggered = 'UNTRIGGERED',
PartiallyFilled = 'PARTIALLY_FILLED',
PartiallyCanceled = 'PARTIALLY_CANCELED',
}

export type SubaccountOrder = {
subaccountNumber: number;
id: string;
clientId: string | null;
type: IndexerOrderType;
side: IndexerOrderSide;
status: OrderStatus;
timeInForce: IndexerAPITimeInForce | null;
marketId: string;
displayId: string;
clobPairId: number | null;
orderFlags: string | null;
price: BigNumber;
triggerPrice: BigNumber | null;
trailingPercent: BigNumber | null;
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;
postOnly: boolean;
reduceOnly: boolean;
cancelReason: string | null;
marginMode: MarginMode | null;
};
14 changes: 4 additions & 10 deletions src/state/raw.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { Loadable, loadableIdle } from '@/abacus-ts/lib/loadable';
import { MarketsData, OrderbookData, ParentSubaccountData } from '@/abacus-ts/rawTypes';
import { MarketsData, OrderbookData, OrdersData, ParentSubaccountData } from '@/abacus-ts/rawTypes';
import {
IndexerHistoricalBlockTradingRewardsResponse,
IndexerParentSubaccountTransferResponse,
} from '@/types/indexer/indexerApiGen';
import {
IndexerCompositeFillResponse,
IndexerCompositeOrderObject,
} from '@/types/indexer/indexerManual';
import { IndexerCompositeFillResponse } from '@/types/indexer/indexerManual';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

export interface RawDataState {
Expand All @@ -18,7 +15,7 @@ export interface RawDataState {
account: {
parentSubaccount: Loadable<ParentSubaccountData>;
fills: Loadable<IndexerCompositeFillResponse>;
orders: Loadable<{ [id: string]: IndexerCompositeOrderObject }>;
orders: Loadable<OrdersData>;
transfers: Loadable<IndexerParentSubaccountTransferResponse>;
blockTradingRewards: Loadable<IndexerHistoricalBlockTradingRewardsResponse>;
};
Expand Down Expand Up @@ -66,10 +63,7 @@ export const rawSlice = createSlice({
) => {
state.account.blockTradingRewards = action.payload;
},
setAccountOrdersRaw: (
state,
action: PayloadAction<Loadable<{ [id: string]: IndexerCompositeOrderObject }>>
) => {
setAccountOrdersRaw: (state, action: PayloadAction<Loadable<OrdersData>>) => {
state.account.orders = action.payload;
},
},
Expand Down

0 comments on commit e7d5a2a

Please sign in to comment.