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: adding new flag to trade all possible quantity for buy/sell #1922

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions docs/api.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 16 additions & 5 deletions lib/cli/placeorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { checkDecimalPlaces } from '../utils/utils';
export const placeOrderBuilder = (argv: Argv, side: OrderSide) => {
const command = side === OrderSide.BUY ? 'buy' : 'sell';
argv.positional('quantity', {
type: 'number',
describe: 'the quantity to trade',
type: 'string',
describe: 'the quantity to trade, `max` trades everything',
})
.positional('pair_id', {
type: 'string',
Expand Down Expand Up @@ -39,6 +39,7 @@ export const placeOrderBuilder = (argv: Argv, side: OrderSide) => {
describe: 'immediate-or-cancel',
})
.example(`$0 ${command} 5 LTC/BTC .01 1337`, `place a limit order to ${command} 5 LTC @ 0.01 BTC with local order id 1337`)
.example(`$0 ${command} max LTC/BTC .01`, `place a limit order to ${command} max LTC @ 0.01 BTC`)
.example(`$0 ${command} 3 BTC/USDT mkt`, `place a market order to ${command} 3 BTC for USDT`)
.example(`$0 ${command} 1 BTC/USDT market`, `place a market order to ${command} 1 BTC for USDT`);
};
Expand All @@ -48,9 +49,17 @@ export const placeOrderHandler = async (argv: Arguments<any>, side: OrderSide) =

const numericPrice = Number(argv.price);
const priceStr = argv.price.toLowerCase();
const isMax = argv.quantity === 'max';

const quantity = coinsToSats(argv.quantity);
request.setQuantity(quantity);
if (isMax) {
request.setMax(true);
} else {
if (isNaN(argv.quantity)) {
console.error('quantity is not a valid number');
process.exit(1);
}
request.setQuantity(coinsToSats(parseFloat(argv.quantity)));
}
request.setSide(side);
request.setPairId(argv.pair_id.toUpperCase());
request.setImmediateOrCancel(argv.ioc);
Expand Down Expand Up @@ -81,7 +90,7 @@ export const placeOrderHandler = async (argv: Arguments<any>, side: OrderSide) =
} else {
const subscription = client.placeOrder(request);
let noMatches = true;
let remainingQuantity = quantity;
let remainingQuantity = isMax ? 0 : coinsToSats(parseFloat(argv.quantity));
subscription.on('data', (response: PlaceOrderEvent) => {
if (argv.json) {
console.log(JSON.stringify(response.toObject(), undefined, 2));
Expand Down Expand Up @@ -113,6 +122,8 @@ export const placeOrderHandler = async (argv: Arguments<any>, side: OrderSide) =
subscription.on('end', () => {
if (noMatches) {
console.log('no matches found');
} else if (isMax) {
console.log('no more matches found');
} else if (remainingQuantity > 0) {
console.log(`no more matches found, ${satsToCoinsStr(remainingQuantity)} qty will be discarded`);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/proto/annotations_grpc_pb.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/proto/xudp2p_grpc_pb.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions lib/proto/xudrpc.swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions lib/proto/xudrpc_pb.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 30 additions & 1 deletion lib/proto/xudrpc_pb.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 68 additions & 4 deletions lib/service/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,19 +624,37 @@ class Service extends EventEmitter {
* If price is zero or unspecified a market order will get added.
*/
public placeOrder = async (
args: { pairId: string, price: number, quantity: number, orderId: string, side: number,
replaceOrderId: string, immediateOrCancel: boolean },
args: { pairId: string, price: number, quantity?: number, orderId: string, side: number,
replaceOrderId: string, immediateOrCancel: boolean, max?: boolean },
callback?: (e: ServicePlaceOrderEvent) => void,
) => {
argChecks.PRICE_NON_NEGATIVE(args);
argChecks.PRICE_MAX_DECIMAL_PLACES(args);
argChecks.HAS_PAIR_ID(args);
const { pairId, price, quantity, orderId, side, replaceOrderId, immediateOrCancel } = args;
const { pairId, price, quantity, orderId, side, replaceOrderId, immediateOrCancel, max } = args;

let calculatedQuantity: number;

if (max) {
if (!price) {
throw errors.INVALID_ARGUMENT("max flag can't be used for market orders");
}

const baseCurrency = pairId.split('/')[0];
const baseSwapClient = this.swapClientManager.get(baseCurrency)?.type;

const quoteCurrency = pairId.split('/')[1];
const quoteSwapClient = this.swapClientManager.get(quoteCurrency)?.type;

calculatedQuantity = await this.calculateLimitOrderMaxQuantity(baseCurrency, quoteCurrency, side, price, baseSwapClient, quoteSwapClient);
} else {
calculatedQuantity = quantity || 0;
}

const order: OwnMarketOrder | OwnLimitOrder = {
pairId,
price,
quantity,
quantity: calculatedQuantity,
isBuy: side === OrderSide.Buy,
localId: orderId || replaceOrderId,
};
Expand All @@ -662,6 +680,52 @@ class Service extends EventEmitter {
await this.orderBook.placeMarketOrder(placeOrderRequest);
}

private async calculateLimitOrderMaxQuantity(baseCurrency: string, quoteCurrency: string, side: number,
price: number, baseSwapClient?: SwapClientType, quoteSwapClient?: SwapClientType) {
let calculatedQuantity;

const baseTradingLimits = (await this.tradingLimits({ currency: baseCurrency })).get(baseCurrency);
const quoteTradingLimits = (await this.tradingLimits({ currency: quoteCurrency })).get(quoteCurrency);

if (baseSwapClient === SwapClientType.Lnd && quoteSwapClient === SwapClientType.Lnd) {
const maxGettableFromQuote = ((side === OrderSide.Sell ? quoteTradingLimits?.maxBuy : quoteTradingLimits?.maxSell) || 0) / price;
const maxGettableFromBase = (side === OrderSide.Sell ? baseTradingLimits?.maxSell : baseTradingLimits?.maxBuy) || 0;

calculatedQuantity = Math.min(maxGettableFromBase, maxGettableFromQuote);
} else if (baseSwapClient === SwapClientType.Lnd && quoteSwapClient === SwapClientType.Connext) {
if (side === OrderSide.Sell) {
calculatedQuantity = baseTradingLimits?.maxSell || 0;
} else {
const maxSellableFromQuote = (quoteTradingLimits?.maxSell || 0) / price;
const maxBuyableFromBase = baseTradingLimits?.maxBuy || 0;

calculatedQuantity = Math.min(maxSellableFromQuote, maxBuyableFromBase);
}

} else if (baseSwapClient === SwapClientType.Connext && quoteSwapClient === SwapClientType.Lnd) {
if (side === OrderSide.Sell) {
const maxBuyableFromQuote = (quoteTradingLimits?.maxBuy || 0) / price;
const maxSellableFromBase = baseTradingLimits?.maxSell || 0;

calculatedQuantity = Math.min(maxBuyableFromQuote, maxSellableFromBase);
} else {
calculatedQuantity = (quoteTradingLimits?.maxSell || 0) / price;
}

} else if (baseSwapClient === SwapClientType.Connext && quoteSwapClient === SwapClientType.Connext) {
if (side === OrderSide.Sell) {
calculatedQuantity = baseTradingLimits?.maxSell || 0;
} else {
calculatedQuantity = (quoteTradingLimits?.maxSell || 0) / price;
}

} else {
throw errors.INVALID_ARGUMENT(`unknown swap client pair ${baseSwapClient}/${quoteSwapClient}`);
}

return calculatedQuantity;
}

/** Removes a currency. */
public removeCurrency = async (args: { currency: string }) => {
argChecks.VALID_CURRENCY(args);
Expand Down
3 changes: 3 additions & 0 deletions proto/xudrpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,9 @@ message PlaceOrderRequest {
string replace_order_id = 6 [json_name = "replace_order_id"];
// Whether the order must be filled immediately and not allowed to enter the order book.
bool immediate_or_cancel = 7 [json_name = "immediate_or_cancel"];
// Whether to trade all available funds.
// If true, the quantity field is ignored.
bool max = 8 [json_name = "max"];
}
message PlaceOrderResponse {
// A list of own orders (or portions thereof) that matched the newly placed order.
Expand Down
Loading