Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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