-
Notifications
You must be signed in to change notification settings - Fork 58
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
[TS] [CLOB-895] short term place orders for TS #39
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
42 changes: 42 additions & 0 deletions
42
v4-client-js/examples/human_readable_short_term_orders.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
[ | ||
{ | ||
"timeInForce": "DEFAULT", | ||
"side": "BUY", | ||
"price": 40000 | ||
}, | ||
{ | ||
"timeInForce": "DEFAULT", | ||
"side": "SELL", | ||
"price": 1000 | ||
}, | ||
{ | ||
"timeInForce": "FOK", | ||
"side": "BUY", | ||
"price": 1000 | ||
}, | ||
{ | ||
"timeInForce": "FOK", | ||
"side": "SELL", | ||
"price": 40000 | ||
}, | ||
{ | ||
"timeInForce": "IOC", | ||
"side": "BUY", | ||
"price": 40000 | ||
}, | ||
{ | ||
"timeInForce": "IOC", | ||
"side": "SELL", | ||
"price": 1000 | ||
}, | ||
{ | ||
"timeInForce": "POST_ONLY", | ||
"side": "BUY", | ||
"price": 1000 | ||
}, | ||
{ | ||
"timeInForce": "POST_ONLY", | ||
"side": "SELL", | ||
"price": 40000 | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { Order_TimeInForce } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/clob/order'; | ||
|
||
import { BECH32_PREFIX } from '../src'; | ||
import { CompositeClient } from '../src/clients/composite-client'; | ||
import { | ||
Network, OrderExecution, OrderSide, | ||
} from '../src/clients/constants'; | ||
import LocalWallet from '../src/clients/modules/local-wallet'; | ||
import { Subaccount } from '../src/clients/subaccount'; | ||
import { randomInt } from '../src/lib/utils'; | ||
import { DYDX_TEST_MNEMONIC } from './constants'; | ||
import ordersParams from './human_readable_short_term_orders.json'; | ||
|
||
async function sleep(ms: number): Promise<void> { | ||
return new Promise((resolve) => setTimeout(resolve, ms)); | ||
} | ||
|
||
async function test(): Promise<void> { | ||
const wallet = await LocalWallet.fromMnemonic(DYDX_TEST_MNEMONIC, BECH32_PREFIX); | ||
console.log(wallet); | ||
const network = Network.staging(); | ||
const client = await CompositeClient.connect(network); | ||
console.log('**Client**'); | ||
console.log(client); | ||
const subaccount = new Subaccount(wallet, 0); | ||
for (const orderParams of ordersParams) { | ||
try { | ||
const side = OrderSide[orderParams.side as keyof typeof OrderSide]; | ||
const price = orderParams.price ?? 1350; | ||
|
||
const currentBlock = await client.validatorClient.get.latestBlockHeight(); | ||
const nextValidBlockHeight = currentBlock + 1; | ||
// Note, you can change this to any number between `next_valid_block_height` | ||
// to `next_valid_block_height + SHORT_BLOCK_WINDOW` | ||
const goodTilBlock = nextValidBlockHeight + 10; | ||
|
||
const timeInForce = orderExecutionToTimeInForce(orderParams.timeInForce); | ||
|
||
// uint32 | ||
const clientId = randomInt(2 ** 32 - 1); | ||
|
||
const tx = await client.placeShortTermOrder( | ||
subaccount, | ||
'ETH-USD', | ||
side, | ||
price, | ||
0.01, | ||
clientId, | ||
timeInForce, | ||
goodTilBlock, | ||
false, | ||
); | ||
console.log('**Order Tx**'); | ||
console.log(tx.hash.toString()); | ||
} catch (error) { | ||
console.log(error.message); | ||
} | ||
|
||
await sleep(5000); // wait for placeOrder to complete | ||
} | ||
} | ||
|
||
function orderExecutionToTimeInForce(orderExecution: string): Order_TimeInForce { | ||
switch (orderExecution) { | ||
case OrderExecution.DEFAULT: | ||
return Order_TimeInForce.TIME_IN_FORCE_UNSPECIFIED; | ||
case OrderExecution.FOK: | ||
return Order_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL; | ||
case OrderExecution.IOC: | ||
return Order_TimeInForce.TIME_IN_FORCE_IOC; | ||
case OrderExecution.POST_ONLY: | ||
return Order_TimeInForce.TIME_IN_FORCE_POST_ONLY; | ||
default: | ||
throw new Error('Unrecognized order execution'); | ||
} | ||
} | ||
|
||
test().then(() => { | ||
}).catch((error) => { | ||
console.log(error.message); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,15 @@ | ||
import { EncodeObject } from '@cosmjs/proto-signing'; | ||
import { GasPrice, IndexedTx, StdFee } from '@cosmjs/stargate'; | ||
import { BroadcastTxAsyncResponse, BroadcastTxSyncResponse } from '@cosmjs/tendermint-rpc/build/tendermint37'; | ||
import { Order_ConditionType, Order_TimeInForce } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/clob/order'; | ||
import Long from 'long'; | ||
import protobuf from 'protobufjs'; | ||
|
||
import { OrderFlags } from '../types'; | ||
import { | ||
DYDX_DENOM, | ||
GAS_PRICE, | ||
Network, OrderExecution, OrderSide, OrderTimeInForce, OrderType, | ||
Network, OrderExecution, OrderSide, OrderTimeInForce, OrderType, SHORT_BLOCK_WINDOW, | ||
} from './constants'; | ||
import { | ||
calculateQuantums, | ||
|
@@ -21,6 +22,7 @@ import { | |
calculateConditionalOrderTriggerSubticks, | ||
} from './helpers/chain-helpers'; | ||
import { IndexerClient } from './indexer-client'; | ||
import { UserError } from './lib/errors'; | ||
import LocalWallet from './modules/local-wallet'; | ||
import { Subaccount } from './subaccount'; | ||
import { ValidatorClient } from './validator-client'; | ||
|
@@ -157,6 +159,27 @@ export class CompositeClient { | |
return height + 3; | ||
} | ||
|
||
/** | ||
* @description Calculate the goodTilBlock value for a SHORT_TERM order | ||
* | ||
* @param goodTilBlock Number of blocks from the current block height the order will | ||
* be valid for. | ||
* | ||
* @throws UnexpectedClientError if a malformed response is returned with no GRPC error | ||
* at any point. | ||
*/ | ||
private async validateGoodTilBlock(goodTilBlock: number): Promise<void> { | ||
const height = await this.validatorClient.get.latestBlockHeight(); | ||
const nextValidBlockHeight = height + 1; | ||
const lowerBound = nextValidBlockHeight; | ||
const upperBound = nextValidBlockHeight + SHORT_BLOCK_WINDOW; | ||
if (goodTilBlock < lowerBound || goodTilBlock > upperBound) { | ||
throw new UserError(`Invalid Short-Term order GoodTilBlock. | ||
Should be greater-than-or-equal-to ${lowerBound} and less-than-or-equal-to ${upperBound}. | ||
Provided good til block: ${goodTilBlock}`); | ||
} | ||
} | ||
|
||
/** | ||
* @description Calculate the goodTilBlockTime value for a LONG_TERM order | ||
* the calling function is responsible for creating the messages. | ||
|
@@ -175,6 +198,60 @@ export class CompositeClient { | |
return Math.round(future.getTime() / 1000); | ||
} | ||
|
||
/** | ||
* @description Place a short term order with human readable input. | ||
* | ||
* Use human readable form of input, including price and size | ||
* The quantum and subticks are calculated and submitted | ||
* | ||
* @param subaccount The subaccount to place the order under | ||
* @param marketId The market to place the order on | ||
* @param side The side of the order to place | ||
* @param price The price of the order to place | ||
* @param size The size of the order to place | ||
* @param clientId The client id of the order to place | ||
* @param timeInForce The time in force of the order to place | ||
* @param goodTilBlock The goodTilBlock of the order to place | ||
* @param reduceOnly The reduceOnly of the order to place | ||
* | ||
* | ||
* @throws UnexpectedClientError if a malformed response is returned with no GRPC error | ||
* at any point. | ||
* @returns The transaction hash. | ||
*/ | ||
async placeShortTermOrder( | ||
subaccount: Subaccount, | ||
marketId: string, | ||
side: OrderSide, | ||
price: number, | ||
size: number, | ||
clientId: number, | ||
goodTilBlock: number, | ||
timeInForce: Order_TimeInForce, | ||
reduceOnly: boolean, | ||
): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> { | ||
const msgs: Promise<EncodeObject[]> = new Promise((resolve) => { | ||
const msg = this.placeShortTermOrderMessage( | ||
subaccount, | ||
marketId, | ||
side, | ||
price, | ||
size, | ||
clientId, | ||
timeInForce, | ||
goodTilBlock, | ||
reduceOnly, | ||
); | ||
msg.then((it) => resolve([it])).catch((err) => { | ||
console.log(err); | ||
}); | ||
}); | ||
return this.send( | ||
subaccount.wallet, | ||
() => msgs, | ||
true); | ||
} | ||
|
||
/** | ||
* @description Place an order with human readable input. | ||
* | ||
|
@@ -341,6 +418,79 @@ export class CompositeClient { | |
); | ||
} | ||
|
||
/** | ||
* @description Calculate and create the short term place order message | ||
* | ||
* Use human readable form of input, including price and size | ||
* The quantum and subticks are calculated and submitted | ||
* | ||
* @param subaccount The subaccount to place the order under | ||
* @param marketId The market to place the order on | ||
* @param side The side of the order to place | ||
* @param price The price of the order to place | ||
* @param size The size of the order to place | ||
* @param clientId The client id of the order to place | ||
* @param timeInForce The time in force of the order to place | ||
* @param goodTilBlock The goodTilBlock of the order to place | ||
* @param reduceOnly The reduceOnly of the order to place | ||
* | ||
* | ||
* @throws UnexpectedClientError if a malformed response is returned with no GRPC error | ||
* at any point. | ||
* @returns The message to be passed into the protocol | ||
*/ | ||
private async placeShortTermOrderMessage( | ||
subaccount: Subaccount, | ||
marketId: string, | ||
side: OrderSide, | ||
price: number, | ||
size: number, | ||
clientId: number, | ||
goodTilBlock: number, | ||
timeInForce: Order_TimeInForce, | ||
reduceOnly: boolean, | ||
): Promise<EncodeObject> { | ||
await this.validateGoodTilBlock(goodTilBlock); | ||
|
||
const marketsResponse = await this.indexerClient.markets.getPerpetualMarkets(marketId); | ||
const market = marketsResponse.markets[marketId]; | ||
const clobPairId = market.clobPairId; | ||
const atomicResolution = market.atomicResolution; | ||
const stepBaseQuantums = market.stepBaseQuantums; | ||
const quantumConversionExponent = market.quantumConversionExponent; | ||
const subticksPerTick = market.subticksPerTick; | ||
const orderSide = calculateSide(side); | ||
const quantums = calculateQuantums( | ||
size, | ||
atomicResolution, | ||
stepBaseQuantums, | ||
); | ||
const subticks = calculateSubticks( | ||
price, | ||
atomicResolution, | ||
quantumConversionExponent, | ||
subticksPerTick, | ||
); | ||
const orderFlags = OrderFlags.SHORT_TERM; | ||
return this.validatorClient.post.composer.composeMsgPlaceOrder( | ||
subaccount.address, | ||
subaccount.subaccountNumber, | ||
clientId, | ||
clobPairId, | ||
orderFlags, | ||
goodTilBlock, | ||
0, // Short term orders use goodTilBlock. | ||
orderSide, | ||
quantums, | ||
subticks, | ||
timeInForce, | ||
reduceOnly, | ||
0, // Client metadata is 0 for short term orders. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: client metadata can be non-zero for short-term and stateful orders |
||
Order_ConditionType.CONDITION_TYPE_UNSPECIFIED, // Short term orders cannot be conditional. | ||
Long.fromInt(0), // Short term orders cannot be conditional. | ||
); | ||
} | ||
|
||
/** | ||
* @description Cancel an order with human readable input. | ||
* | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: do you think this function should be defined within
v4-client-js/src/clients/composite-client.ts
and called withinplaceShortTermOrder
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I'm fine with you leaving it here if it's meant to translate from a JSON order
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is only meant to translate from the JSON example file, so will keep it in here