Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 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.

22 changes: 17 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,8 @@ 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} max BTC/USDT mkt`, `place a market order to ${command} max BTC for USDT`)
.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 +50,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 +91,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 +123,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 @@ -611,19 +611,37 @@ class Service {
* 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 @@ -649,6 +667,52 @@ class Service {
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 @@ -699,6 +699,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