From 304d8872a3981f72c65803b46c62412606d2db52 Mon Sep 17 00:00:00 2001 From: Michael Lake Date: Sat, 13 Jun 2020 19:27:44 -1000 Subject: [PATCH] new feature: added placeCanonicalStopOrder allows setting triggerPrice and setExpirationOnFill used in bot for tracking price movement updating the stop to minimize losses (ie. "trailing stop") --- src/modules/Api.ts | 190 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/src/modules/Api.ts b/src/modules/Api.ts index 994181b8..1369c8a6 100644 --- a/src/modules/Api.ts +++ b/src/modules/Api.ts @@ -44,7 +44,179 @@ export class Api { this.canonicalOrders = canonicalOrders; this.timeout = timeout; } + public async placeCanonicalStopOrder({ + order: { + side, + market, + amount, + price, + orderTriggerPrice, + makerAccountOwner, + makerAccountNumber, + expiration = new BigNumber(FOUR_WEEKS_IN_SECONDS), + limitFee, + }, + fillOrKill, + postOnly, + clientId, + cancelId, + cancelAmountOnRevert, + setExpirationOnFill, + triggerPrice, + }: { + order: { + side: ApiSide, + market: ApiMarketName, + amount: BigNumberable, + price: BigNumberable, + orderTriggerPrice: BigNumberable, + makerAccountOwner: address, + makerAccountNumber: BigNumberable, + expiration: BigNumberable, + limitFee?: BigNumberable, + }, + fillOrKill?: boolean, + postOnly?: boolean, + clientId?: string, + cancelId?: string, + cancelAmountOnRevert?: boolean, + setExpirationOnFill?:boolean, + triggerPrice?: BigNumberable, + }): Promise<{ order: ApiOrder }> { + const order: SignedCanonicalOrder = await this.createCanonicalStopOrder({ + side, + market, + amount, + price, + makerAccountOwner, + makerAccountNumber, + expiration, + limitFee, + postOnly, + triggerPrice: orderTriggerPrice, + }); + + return this.submitCanonicalStopOrder({ + order, + fillOrKill, + postOnly, + cancelId, + clientId, + cancelAmountOnRevert, + setExpirationOnFill, + triggerPrice, + }); + } + + /** + * Creates but does not place a signed canonicalOrder + */ + async createCanonicalStopOrder({ + side, + market, + amount, + price, + triggerPrice, + makerAccountOwner, + makerAccountNumber, + expiration, + limitFee, + postOnly, + }: { + side: ApiSide, + market: ApiMarketName, + amount: BigNumberable, + price: BigNumberable, + triggerPrice: BigNumberable, + makerAccountOwner: address, + makerAccountNumber: BigNumberable, + expiration: BigNumberable, + limitFee?: BigNumberable, + postOnly?: boolean, + }): Promise { + if (!Object.values(ApiSide).includes(side)) { + throw new Error(`side: ${side} is invalid`); + } + if (!Object.values(ApiMarketName).includes(market)) { + throw new Error(`market: ${market} is invalid`); + } + + const amountNumber: BigNumber = new BigNumber(amount); + const isTaker: boolean = !postOnly; + const markets: string[] = market.split('-'); + const baseMarket: BigNumber = MarketId[markets[0]]; + const limitFeeNumber: BigNumber = limitFee + ? new BigNumber(limitFee) + : this.canonicalOrders.getFeeForOrder(baseMarket, amountNumber, isTaker); + + const realExpiration: BigNumber = getRealExpiration(expiration); + const order: CanonicalOrder = { + baseMarket, + makerAccountOwner, + quoteMarket: MarketId[markets[1]], + isBuy: side === ApiSide.BUY, + isDecreaseOnly: true, + amount: amountNumber, + limitPrice: new BigNumber(price), + triggerPrice: new BigNumber(triggerPrice), + limitFee: limitFeeNumber, + makerAccountNumber: new BigNumber(makerAccountNumber), + expiration: realExpiration, + salt: generatePseudoRandom256BitNumber(), + }; + + const typedSignature: string = await this.canonicalOrders.signOrder( + order, + SigningMethod.Hash, + ); + + return { + ...order, + typedSignature, + }; + } + + /** + * Submits an already signed canonicalOrder + */ + public async submitCanonicalStopOrder({ + order, + fillOrKill = false, + postOnly = false, + cancelId, + clientId, + cancelAmountOnRevert, + setExpirationOnFill, + triggerPrice, + }: { + order: SignedCanonicalOrder, + fillOrKill: boolean, + postOnly: boolean, + cancelId: string, + clientId?: string, + cancelAmountOnRevert?: boolean, + setExpirationOnFill?: boolean, + triggerPrice: BigNumberable, + }): Promise<{ order: ApiOrder }> { + const jsonOrder = jsonifyCanonicalStopOrder(order); + + const data: any = { + fillOrKill, + postOnly, + clientId, + cancelId, + cancelAmountOnRevert, + setExpirationOnFill, + triggerPrice: new BigNumber(triggerPrice).toString(), + order: jsonOrder, + }; + return this.axiosRequest({ + data, + url: `${this.endpoint}/v2/orders`, + method: RequestMethod.POST, + }); + } public async placeCanonicalOrder({ order: { side, @@ -433,6 +605,24 @@ function jsonifyCanonicalOrder(order: SignedCanonicalOrder) { }; } +function jsonifyCanonicalStopOrder(order: SignedCanonicalOrder) { + return { + isBuy: order.isBuy, + isDecreaseOnly: order.isDecreaseOnly, + baseMarket: order.baseMarket.toFixed(0), + quoteMarket: order.quoteMarket.toFixed(0), + amount: order.amount.toFixed(0), + limitPrice: order.limitPrice.toString(), + triggerPrice: order.triggerPrice.toString(), + limitFee: order.limitFee.toString(), + makerAccountNumber: order.makerAccountNumber.toFixed(0), + makerAccountOwner: order.makerAccountOwner, + expiration: order.expiration.toFixed(0), + typedSignature: order.typedSignature, + salt: order.salt.toFixed(0), + }; +} + function getRealExpiration(expiration: BigNumberable): BigNumber { return new BigNumber(expiration).eq(0) ? new BigNumber(0)