Skip to content

Commit

Permalink
[TRA-102] Add API to get asset positions for parent subaccount (#1243)
Browse files Browse the repository at this point in the history
Signed-off-by: Shrenuj Bansal <[email protected]>
  • Loading branch information
shrenujb authored Mar 26, 2024
1 parent 5722446 commit 9dd90ff
Show file tree
Hide file tree
Showing 6 changed files with 518 additions and 42 deletions.
38 changes: 38 additions & 0 deletions indexer/packages/postgres/__tests__/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,28 @@ export const defaultPerpetualPositionId: string = PerpetualPositionTable.uuid(
defaultPerpetualPosition.openEventId,
);

export const isolatedPerpetualPosition: PerpetualPositionCreateObject = {
subaccountId: isolatedSubaccountId,
perpetualId: isolatedPerpetualMarket.id,
side: PositionSide.LONG,
status: PerpetualPositionStatus.OPEN,
size: '10',
maxSize: '25',
entryPrice: '20000',
sumOpen: '10',
sumClose: '0',
createdAt: createdDateTime.toISO(),
createdAtHeight: createdHeight,
openEventId: defaultTendermintEventId,
lastEventId: defaultTendermintEventId2,
settledFunding: '200000',
};

export const isolatedPerpetualPositionId: string = PerpetualPositionTable.uuid(
isolatedPerpetualPosition.subaccountId,
isolatedPerpetualPosition.openEventId,
);

// ============== Fills ==============

export const defaultFill: FillCreateObject = {
Expand Down Expand Up @@ -720,6 +742,22 @@ export const defaultFundingIndexUpdateId: string = FundingIndexUpdatesTable.uuid
defaultFundingIndexUpdate.perpetualId,
);

export const isolatedMarketFundingIndexUpdate: FundingIndexUpdatesCreateObject = {
perpetualId: isolatedPerpetualMarket.id,
eventId: defaultTendermintEventId,
rate: '0.0004',
oraclePrice: '10000',
fundingIndex: '10200',
effectiveAt: createdDateTime.toISO(),
effectiveAtHeight: createdHeight,
};

export const isolatedMarketFundingIndexUpdateId: string = FundingIndexUpdatesTable.uuid(
isolatedMarketFundingIndexUpdate.effectiveAtHeight,
isolatedMarketFundingIndexUpdate.eventId,
isolatedMarketFundingIndexUpdate.perpetualId,
);

// ========= Compliance Data ==========

export const blockedComplianceData: ComplianceDataCreateObject = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('asset-positions-controller#V4', () => {
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/assetPositions?address=${testConstants.defaultAddress}` +
`&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`,
`&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`,
});

const expectedAssetPosition: AssetPositionResponseObject = {
Expand All @@ -58,7 +58,7 @@ describe('asset-positions-controller#V4', () => {
);
});

it('Get /assetPositions gets short asset and perpetual positions', async () => {
it('Get /assetPositions gets short asset positions', async () => {
await testMocks.seedData();
await AssetPositionTable.upsert({
...testConstants.defaultAssetPosition,
Expand All @@ -68,7 +68,7 @@ describe('asset-positions-controller#V4', () => {
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/assetPositions?address=${testConstants.defaultAddress}` +
`&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`,
`&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`,
});

const expectedAssetPosition: AssetPositionResponseObject = {
Expand Down Expand Up @@ -103,7 +103,7 @@ describe('asset-positions-controller#V4', () => {
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/assetPositions?address=${testConstants.defaultAddress}` +
`&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`,
`&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`,
});

expect(response.body.positions).toEqual(
Expand Down Expand Up @@ -133,11 +133,13 @@ describe('asset-positions-controller#V4', () => {
subaccountId: testConstants.defaultSubaccountId,
size: '0',
}),
// Funding index at height 0 is 10000
FundingIndexUpdatesTable.create({
...testConstants.defaultFundingIndexUpdate,
fundingIndex: '10000',
effectiveAtHeight: testConstants.createdHeight,
}),
// Funding index at height 3 is 10050
FundingIndexUpdatesTable.create({
...testConstants.defaultFundingIndexUpdate,
eventId: testConstants.defaultTendermintEventId2,
Expand All @@ -148,18 +150,164 @@ describe('asset-positions-controller#V4', () => {
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/assetPositions?address=${testConstants.defaultAddress}` +
`&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`,
`&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`,
});

expect(response.body.positions).toEqual(
[{
symbol: testConstants.defaultAsset.symbol,
// funding index difference = 10050 (height 3) - 10000 (height 0) = 50
// size = 10000 (initial size) - 50 (funding index diff) * 10(position size)
size: '9500',
side: PositionSide.LONG,
assetId: testConstants.defaultAssetPosition.assetId,
subaccountNumber: testConstants.defaultSubaccount.subaccountNumber,
}],
);
});

it('Get /assetPositions/parentSubaccountNumber gets long and short asset positions across subaccounts', async () => {
await testMocks.seedData();
await Promise.all([
AssetPositionTable.upsert(testConstants.defaultAssetPosition),
AssetPositionTable.upsert({
...testConstants.isolatedSubaccountAssetPosition,
isLong: false,
}),
]);

const parentSubaccountNumber: number = 0;
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/assetPositions/parentSubaccountNumber?address=${testConstants.defaultAddress}` +
`&parentSubaccountNumber=${parentSubaccountNumber}`,
});

const expectedAssetPosition: AssetPositionResponseObject = {
symbol: testConstants.defaultAsset.symbol,
side: PositionSide.LONG,
size: testConstants.defaultAssetPosition.size,
assetId: testConstants.defaultAssetPosition.assetId,
subaccountNumber: testConstants.defaultSubaccount.subaccountNumber,
};
const expectedIsolatedAssetPosition: AssetPositionResponseObject = {
symbol: testConstants.defaultAsset.symbol,
side: PositionSide.SHORT,
size: testConstants.isolatedSubaccountAssetPosition.size,
assetId: testConstants.isolatedSubaccountAssetPosition.assetId,
subaccountNumber: testConstants.isolatedSubaccount.subaccountNumber,
};

expect(response.body.positions).toEqual(
expect.arrayContaining([
expect.objectContaining({
...expectedAssetPosition,
}),
expect.objectContaining({
...expectedIsolatedAssetPosition,
}),
]),
);
});

it('Get /assetPositions/parentSubaccountNumber does not get asset positions with 0 size', async () => {
await testMocks.seedData();

await Promise.all([
await AssetPositionTable.upsert(testConstants.defaultAssetPosition),
await AssetPositionTable.upsert({
...testConstants.isolatedSubaccountAssetPosition,
size: '0',
}),
]);

const parentSubaccountNumber: number = 0;
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/assetPositions/parentSubaccountNumber?address=${testConstants.defaultAddress}` +
`&parentSubaccountNumber=${parentSubaccountNumber}`,
});

expect(response.body.positions).toEqual(
[{
symbol: testConstants.defaultAsset.symbol,
size: testConstants.defaultAssetPosition.size,
side: PositionSide.LONG,
assetId: testConstants.defaultAssetPosition.assetId,
subaccountNumber: testConstants.defaultSubaccount.subaccountNumber,
}],
);
});

it('Get /assetPositions/parentSubaccountNumber gets USDC asset positions adjusted by unsettled funding', async () => {
await testMocks.seedData();
await BlockTable.create({
...testConstants.defaultBlock,
blockHeight: '3',
});
await Promise.all([
PerpetualPositionTable.create(testConstants.defaultPerpetualPosition),
PerpetualPositionTable.create(testConstants.isolatedPerpetualPosition),
AssetPositionTable.upsert(testConstants.defaultAssetPosition),
AssetPositionTable.upsert(testConstants.isolatedSubaccountAssetPosition),
FundingIndexUpdatesTable.create({
...testConstants.defaultFundingIndexUpdate,
fundingIndex: '10000',
effectiveAtHeight: testConstants.createdHeight,
}),
FundingIndexUpdatesTable.create({
...testConstants.defaultFundingIndexUpdate,
eventId: testConstants.defaultTendermintEventId2,
effectiveAtHeight: '3',
}),
FundingIndexUpdatesTable.create({
...testConstants.isolatedMarketFundingIndexUpdate,
fundingIndex: '10000',
effectiveAtHeight: testConstants.createdHeight,
}),
FundingIndexUpdatesTable.create({
...testConstants.isolatedMarketFundingIndexUpdate,
eventId: testConstants.defaultTendermintEventId2,
effectiveAtHeight: '3',
}),
]);

const parentSubaccountNumber: number = 0;
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/assetPositions/parentSubaccountNumber?address=${testConstants.defaultAddress}` +
`&parentSubaccountNumber=${parentSubaccountNumber}`,
});

const expectedAssetPosition: AssetPositionResponseObject = {
symbol: testConstants.defaultAsset.symbol,
side: PositionSide.LONG,
// funding index difference = 10050 (height 3) - 10000 (height 0) = 50
// size = 10000 (initial size) - 50 (funding index diff) * 10(position size)
size: '9500',
assetId: testConstants.defaultAssetPosition.assetId,
subaccountNumber: testConstants.defaultSubaccount.subaccountNumber,
};
const expectedIsolatedAssetPosition: AssetPositionResponseObject = {
symbol: testConstants.defaultAsset.symbol,
side: PositionSide.LONG,
// funding index difference = 10200 (height 3) - 10000 (height 0) = 200
// size = 5000 (initial size) - 200 (funding index diff) * 10(position size)
size: '3000',
assetId: testConstants.isolatedSubaccountAssetPosition.assetId,
subaccountNumber: testConstants.isolatedSubaccount.subaccountNumber,
};

expect(response.body.positions).toEqual(
expect.arrayContaining([
expect.objectContaining({
...expectedAssetPosition,
}),
expect.objectContaining({
...expectedIsolatedAssetPosition,
}),
]),
);
});
});
});
77 changes: 77 additions & 0 deletions indexer/services/comlink/public/api-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,83 @@ fetch('https://dydx-testnet.imperator.co/v4/assetPositions?address=string&subacc
This operation does not require authentication
</aside>

## GetAssetPositionsForParentSubaccount

<a id="opIdGetAssetPositionsForParentSubaccount"></a>

> Code samples
```python
import requests
headers = {
'Accept': 'application/json'
}

r = requests.get('https://dydx-testnet.imperator.co/v4/assetPositions/parentSubaccountNumber', params={
'address': 'string', 'parentSubaccountNumber': '0'
}, headers = headers)

print(r.json())

```

```javascript

const headers = {
'Accept':'application/json'
};

fetch('https://dydx-testnet.imperator.co/v4/assetPositions/parentSubaccountNumber?address=string&parentSubaccountNumber=0',
{
method: 'GET',

headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});

```

`GET /assetPositions/parentSubaccountNumber`

### Parameters

|Name|In|Type|Required|Description|
|---|---|---|---|---|
|address|query|string|true|none|
|parentSubaccountNumber|query|number(double)|true|none|

> Example responses
> 200 Response
```json
{
"positions": [
{
"symbol": "string",
"side": "LONG",
"size": "string",
"assetId": "string",
"subaccountNumber": 0
}
]
}
```

### Responses

|Status|Meaning|Description|Schema|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Ok|[AssetPositionResponse](#schemaassetpositionresponse)|

<aside class="success">
This operation does not require authentication
</aside>

## GetCandles

<a id="opIdGetCandles"></a>
Expand Down
37 changes: 37 additions & 0 deletions indexer/services/comlink/public/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,43 @@
]
}
},
"/assetPositions/parentSubaccountNumber": {
"get": {
"operationId": "GetAssetPositionsForParentSubaccount",
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssetPositionResponse"
}
}
}
}
},
"security": [],
"parameters": [
{
"in": "query",
"name": "address",
"required": true,
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "parentSubaccountNumber",
"required": true,
"schema": {
"format": "double",
"type": "number"
}
}
]
}
},
"/candles/perpetualMarkets/{ticker}": {
"get": {
"operationId": "GetCandles",
Expand Down
Loading

0 comments on commit 9dd90ff

Please sign in to comment.