@gbozee/ultimate 0.0.2-next.71 → 0.0.2-next.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +479 -13
- package/dist/index.d.ts +40 -0
- package/dist/index.js +479 -13
- package/dist/mcp-server.cjs +479 -13
- package/dist/mcp-server.js +479 -13
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -72035,15 +72035,20 @@ class BaseExchange {
|
|
|
72035
72035
|
});
|
|
72036
72036
|
}
|
|
72037
72037
|
}
|
|
72038
|
+
async previewSpotMarginHedge(_payload) {
|
|
72039
|
+
throw new Error("Spot margin hedge preview is not supported on this exchange");
|
|
72040
|
+
}
|
|
72041
|
+
async placeSpotMarginHedge(_payload) {
|
|
72042
|
+
throw new Error("Spot margin hedge placement is not supported on this exchange");
|
|
72043
|
+
}
|
|
72038
72044
|
async placeSpotLimitOrders(_payload) {}
|
|
72039
|
-
async
|
|
72045
|
+
async buildCumulative(payload) {
|
|
72040
72046
|
const {
|
|
72041
72047
|
orders,
|
|
72042
72048
|
kind,
|
|
72043
72049
|
decimal_places = "%.3f",
|
|
72044
|
-
price_places = "%.1f",
|
|
72045
72050
|
symbol,
|
|
72046
|
-
|
|
72051
|
+
is_stop = false
|
|
72047
72052
|
} = payload;
|
|
72048
72053
|
const totalQuantity = orders.reduce((sum, order) => sum + (order.quantity || 0), 0);
|
|
72049
72054
|
let runningTotal = to_f(totalQuantity, decimal_places);
|
|
@@ -72051,6 +72056,9 @@ class BaseExchange {
|
|
|
72051
72056
|
if (kind === "short") {
|
|
72052
72057
|
sortedOrders.reverse();
|
|
72053
72058
|
}
|
|
72059
|
+
if (is_stop) {
|
|
72060
|
+
sortedOrders.reverse();
|
|
72061
|
+
}
|
|
72054
72062
|
const withCumulative = [];
|
|
72055
72063
|
for (const order of sortedOrders) {
|
|
72056
72064
|
withCumulative.push({
|
|
@@ -72073,6 +72081,17 @@ class BaseExchange {
|
|
|
72073
72081
|
kind,
|
|
72074
72082
|
side: kind.toLowerCase() === "long" ? "buy" : "sell"
|
|
72075
72083
|
}));
|
|
72084
|
+
return filteredOrders;
|
|
72085
|
+
}
|
|
72086
|
+
async bulkPlaceLimitOrders(payload) {
|
|
72087
|
+
const {
|
|
72088
|
+
kind,
|
|
72089
|
+
decimal_places = "%.3f",
|
|
72090
|
+
price_places = "%.1f",
|
|
72091
|
+
symbol,
|
|
72092
|
+
place = false
|
|
72093
|
+
} = payload;
|
|
72094
|
+
const filteredOrders = await this.buildCumulative(payload);
|
|
72076
72095
|
if (filteredOrders.length > 0 && place) {
|
|
72077
72096
|
await this.cancelAllOrders(symbol, {
|
|
72078
72097
|
type: "limit",
|
|
@@ -72217,9 +72236,13 @@ class BaseExchange {
|
|
|
72217
72236
|
});
|
|
72218
72237
|
}
|
|
72219
72238
|
async entryStopLimitOrders(payload) {
|
|
72220
|
-
const { symbol, kind,
|
|
72239
|
+
const { symbol, kind, price_places, decimal_places, place } = payload;
|
|
72240
|
+
const cummulative = await this.buildCumulative({
|
|
72241
|
+
...payload,
|
|
72242
|
+
is_stop: true
|
|
72243
|
+
});
|
|
72221
72244
|
const price = await this.get_current_price(symbol);
|
|
72222
|
-
const orders_to_place =
|
|
72245
|
+
const orders_to_place = cummulative.map((order) => {
|
|
72223
72246
|
const { entry } = order;
|
|
72224
72247
|
let spread = 0.005;
|
|
72225
72248
|
if (price < 5000) {
|
|
@@ -72237,7 +72260,10 @@ class BaseExchange {
|
|
|
72237
72260
|
is_market: false
|
|
72238
72261
|
};
|
|
72239
72262
|
});
|
|
72240
|
-
if (place) {
|
|
72263
|
+
if (place && orders_to_place.length > 0) {
|
|
72264
|
+
await this.cancelAllOrders(symbol, {
|
|
72265
|
+
kind
|
|
72266
|
+
});
|
|
72241
72267
|
return this._createLimitPurchaseOrders({
|
|
72242
72268
|
symbol,
|
|
72243
72269
|
price_places,
|
|
@@ -72422,6 +72448,210 @@ class BaseExchange {
|
|
|
72422
72448
|
|
|
72423
72449
|
// src/exchanges/binance/index.ts
|
|
72424
72450
|
var import_p_limit = __toESM(require_p_limit(), 1);
|
|
72451
|
+
|
|
72452
|
+
// src/exchanges/binance/spot-margin-hedge.ts
|
|
72453
|
+
function roundNumber(value2, digits = 8) {
|
|
72454
|
+
return Number(value2.toFixed(digits));
|
|
72455
|
+
}
|
|
72456
|
+
var STOP_LIMIT_SPREAD_PERCENT = 0.0005;
|
|
72457
|
+
function normalizeOrderInput(order) {
|
|
72458
|
+
return {
|
|
72459
|
+
price: roundNumber(order.price),
|
|
72460
|
+
quantity: roundNumber(order.quantity)
|
|
72461
|
+
};
|
|
72462
|
+
}
|
|
72463
|
+
function sortOrderInputs(orders) {
|
|
72464
|
+
return [...orders].map(normalizeOrderInput).sort((left, right) => left.price - right.price);
|
|
72465
|
+
}
|
|
72466
|
+
function normalizeOrderForComparison(order) {
|
|
72467
|
+
const type = (order.type || "limit").toLowerCase();
|
|
72468
|
+
const raw_stop = "stop" in order && order.stop !== undefined ? order.stop : ("stopPrice" in order) ? order.stopPrice : undefined;
|
|
72469
|
+
const parsed_stop = raw_stop === undefined || raw_stop === null || raw_stop === "" ? undefined : Number(raw_stop);
|
|
72470
|
+
return {
|
|
72471
|
+
side: order.side,
|
|
72472
|
+
type,
|
|
72473
|
+
price: roundNumber(order.price),
|
|
72474
|
+
quantity: roundNumber(order.quantity),
|
|
72475
|
+
stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ? roundNumber(parsed_stop) : undefined
|
|
72476
|
+
};
|
|
72477
|
+
}
|
|
72478
|
+
function normalizeOrders(orders) {
|
|
72479
|
+
return [...orders].map(normalizeOrderForComparison).sort((left, right) => {
|
|
72480
|
+
if (left.side !== right.side)
|
|
72481
|
+
return left.side.localeCompare(right.side);
|
|
72482
|
+
if (left.type !== right.type)
|
|
72483
|
+
return left.type.localeCompare(right.type);
|
|
72484
|
+
if (left.price !== right.price)
|
|
72485
|
+
return left.price - right.price;
|
|
72486
|
+
return left.quantity - right.quantity;
|
|
72487
|
+
});
|
|
72488
|
+
}
|
|
72489
|
+
function toPlannedOrders(orders) {
|
|
72490
|
+
return normalizeOrders(orders).map((order) => ({
|
|
72491
|
+
side: order.side,
|
|
72492
|
+
type: order.type,
|
|
72493
|
+
price: order.price,
|
|
72494
|
+
quantity: order.quantity,
|
|
72495
|
+
stop: order.stop
|
|
72496
|
+
}));
|
|
72497
|
+
}
|
|
72498
|
+
function buildOrderKey(order) {
|
|
72499
|
+
return [
|
|
72500
|
+
order.side,
|
|
72501
|
+
order.type,
|
|
72502
|
+
order.price,
|
|
72503
|
+
order.quantity,
|
|
72504
|
+
order.stop ?? ""
|
|
72505
|
+
].join(":");
|
|
72506
|
+
}
|
|
72507
|
+
function buildDiff(options) {
|
|
72508
|
+
const expectedKeys = options.expected.map(buildOrderKey);
|
|
72509
|
+
const actualKeys = options.actual.map(buildOrderKey);
|
|
72510
|
+
return {
|
|
72511
|
+
missing: expectedKeys.filter((key) => !actualKeys.includes(key)),
|
|
72512
|
+
extra: actualKeys.filter((key) => !expectedKeys.includes(key))
|
|
72513
|
+
};
|
|
72514
|
+
}
|
|
72515
|
+
function getQuoteAsset(symbol) {
|
|
72516
|
+
const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
|
|
72517
|
+
const target = quoteAssets.find((asset) => symbol.endsWith(asset));
|
|
72518
|
+
return target || symbol.slice(-4);
|
|
72519
|
+
}
|
|
72520
|
+
function sumNotional(orders) {
|
|
72521
|
+
return roundNumber(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
|
|
72522
|
+
}
|
|
72523
|
+
function sumQuantity(orders) {
|
|
72524
|
+
return roundNumber(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
|
|
72525
|
+
}
|
|
72526
|
+
function buildLongOrderPlacement(options) {
|
|
72527
|
+
const { order, current_price } = options;
|
|
72528
|
+
if (order.price > current_price) {
|
|
72529
|
+
return {
|
|
72530
|
+
side: "buy",
|
|
72531
|
+
type: "stop_loss_limit",
|
|
72532
|
+
price: order.price,
|
|
72533
|
+
stop: roundNumber(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
|
|
72534
|
+
quantity: order.quantity
|
|
72535
|
+
};
|
|
72536
|
+
}
|
|
72537
|
+
return {
|
|
72538
|
+
side: "buy",
|
|
72539
|
+
type: "limit",
|
|
72540
|
+
price: order.price,
|
|
72541
|
+
quantity: order.quantity
|
|
72542
|
+
};
|
|
72543
|
+
}
|
|
72544
|
+
function inferSpotMarginHedgeInputFromLiveOrders(options) {
|
|
72545
|
+
const long_orders = sortOrderInputs([
|
|
72546
|
+
...options.spot_orders.filter((order) => order.side === "buy").map((order) => ({
|
|
72547
|
+
price: order.price,
|
|
72548
|
+
quantity: order.quantity
|
|
72549
|
+
})),
|
|
72550
|
+
...options.margin_orders.filter((order) => order.side === "buy").map((order) => ({
|
|
72551
|
+
price: order.price,
|
|
72552
|
+
quantity: order.quantity
|
|
72553
|
+
}))
|
|
72554
|
+
]);
|
|
72555
|
+
const short_orders = sortOrderInputs([
|
|
72556
|
+
...options.spot_orders.filter((order) => order.side === "sell").map((order) => ({
|
|
72557
|
+
price: order.price,
|
|
72558
|
+
quantity: order.quantity
|
|
72559
|
+
})),
|
|
72560
|
+
...options.margin_orders.filter((order) => order.side === "sell").map((order) => ({
|
|
72561
|
+
price: order.price,
|
|
72562
|
+
quantity: order.quantity
|
|
72563
|
+
}))
|
|
72564
|
+
]);
|
|
72565
|
+
return {
|
|
72566
|
+
symbol: options.symbol,
|
|
72567
|
+
margin_type: options.margin_type,
|
|
72568
|
+
borrow_amount: sumNotional(long_orders),
|
|
72569
|
+
long_orders,
|
|
72570
|
+
short_orders,
|
|
72571
|
+
placement_overrides: {
|
|
72572
|
+
spot: toPlannedOrders(options.spot_orders),
|
|
72573
|
+
margin: toPlannedOrders(options.margin_orders)
|
|
72574
|
+
}
|
|
72575
|
+
};
|
|
72576
|
+
}
|
|
72577
|
+
function buildSpotMarginHedgePlan(options) {
|
|
72578
|
+
const max_spot_buy_orders = options.max_spot_buy_orders ?? 5;
|
|
72579
|
+
const long_orders = sortOrderInputs(options.long_orders);
|
|
72580
|
+
const short_orders = sortOrderInputs(options.short_orders);
|
|
72581
|
+
const spot_long_orders = long_orders.slice(0, max_spot_buy_orders);
|
|
72582
|
+
const margin_long_orders = long_orders.slice(max_spot_buy_orders);
|
|
72583
|
+
const default_spot_orders_to_create = [
|
|
72584
|
+
...spot_long_orders.map((order) => buildLongOrderPlacement({
|
|
72585
|
+
order,
|
|
72586
|
+
current_price: options.current_price
|
|
72587
|
+
})),
|
|
72588
|
+
...short_orders.map((order) => ({
|
|
72589
|
+
side: "sell",
|
|
72590
|
+
type: "limit",
|
|
72591
|
+
price: order.price,
|
|
72592
|
+
quantity: order.quantity
|
|
72593
|
+
}))
|
|
72594
|
+
];
|
|
72595
|
+
const default_margin_orders_to_create = margin_long_orders.map((order) => buildLongOrderPlacement({
|
|
72596
|
+
order,
|
|
72597
|
+
current_price: options.current_price
|
|
72598
|
+
}));
|
|
72599
|
+
const spot_orders_to_create = options.placement_overrides?.spot || default_spot_orders_to_create;
|
|
72600
|
+
const margin_orders_to_create = options.placement_overrides?.margin || default_margin_orders_to_create;
|
|
72601
|
+
const borrow_delta = roundNumber(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
|
|
72602
|
+
const spot_long_notional = sumNotional(spot_long_orders);
|
|
72603
|
+
const transfer_to_spot_amount = roundNumber(Math.max(0, spot_long_notional - options.snapshot.spot_quote_free));
|
|
72604
|
+
const short_quantity = sumQuantity(short_orders);
|
|
72605
|
+
const transfer_base_to_spot_amount = roundNumber(Math.max(0, short_quantity - options.snapshot.spot_base_free));
|
|
72606
|
+
return {
|
|
72607
|
+
borrow_amount: options.borrow_amount,
|
|
72608
|
+
borrow_delta,
|
|
72609
|
+
transfer_to_spot_amount,
|
|
72610
|
+
transfer_to_spot_asset: getQuoteAsset(options.symbol),
|
|
72611
|
+
transfer_base_to_spot_amount,
|
|
72612
|
+
orders_to_cancel: {
|
|
72613
|
+
spot: options.snapshot.spot_orders,
|
|
72614
|
+
margin: options.snapshot.margin_orders
|
|
72615
|
+
},
|
|
72616
|
+
orders_to_create: {
|
|
72617
|
+
spot: spot_orders_to_create,
|
|
72618
|
+
margin: margin_orders_to_create
|
|
72619
|
+
},
|
|
72620
|
+
normalized: {
|
|
72621
|
+
spot: normalizeOrders(spot_orders_to_create),
|
|
72622
|
+
margin: normalizeOrders(margin_orders_to_create)
|
|
72623
|
+
}
|
|
72624
|
+
};
|
|
72625
|
+
}
|
|
72626
|
+
function compareSpotMarginHedgeOrders(options) {
|
|
72627
|
+
const actualSpot = normalizeOrders(options.spot_orders);
|
|
72628
|
+
const actualMargin = normalizeOrders(options.margin_orders);
|
|
72629
|
+
const spotDiff = buildDiff({
|
|
72630
|
+
expected: options.plan.normalized.spot,
|
|
72631
|
+
actual: actualSpot
|
|
72632
|
+
});
|
|
72633
|
+
const marginDiff = buildDiff({
|
|
72634
|
+
expected: options.plan.normalized.margin,
|
|
72635
|
+
actual: actualMargin
|
|
72636
|
+
});
|
|
72637
|
+
return {
|
|
72638
|
+
matches: spotDiff.missing.length === 0 && spotDiff.extra.length === 0 && marginDiff.missing.length === 0 && marginDiff.extra.length === 0,
|
|
72639
|
+
spot: {
|
|
72640
|
+
expected: options.plan.normalized.spot,
|
|
72641
|
+
actual: actualSpot,
|
|
72642
|
+
missing: spotDiff.missing,
|
|
72643
|
+
extra: spotDiff.extra
|
|
72644
|
+
},
|
|
72645
|
+
margin: {
|
|
72646
|
+
expected: options.plan.normalized.margin,
|
|
72647
|
+
actual: actualMargin,
|
|
72648
|
+
missing: marginDiff.missing,
|
|
72649
|
+
extra: marginDiff.extra
|
|
72650
|
+
}
|
|
72651
|
+
};
|
|
72652
|
+
}
|
|
72653
|
+
|
|
72654
|
+
// src/exchanges/binance/index.ts
|
|
72425
72655
|
var CONSTANTS = {
|
|
72426
72656
|
SPOT_TO_FIAT: "MAIN_C2C",
|
|
72427
72657
|
SPOT_TO_USDT_FUTURE: "MAIN_UMFUTURE",
|
|
@@ -72910,12 +73140,20 @@ async function getCrossMarginAccountDetails(client) {
|
|
|
72910
73140
|
async function cancelMarginOrders(client, symbol, isIsolated = false, orders) {
|
|
72911
73141
|
try {
|
|
72912
73142
|
if (orders && orders.length > 0) {
|
|
72913
|
-
const cancelPromises = orders.map((order) =>
|
|
72914
|
-
|
|
72915
|
-
|
|
72916
|
-
|
|
72917
|
-
|
|
72918
|
-
|
|
73143
|
+
const cancelPromises = orders.map((order) => {
|
|
73144
|
+
const payload = {
|
|
73145
|
+
symbol,
|
|
73146
|
+
isIsolated: isIsolated ? "TRUE" : "FALSE"
|
|
73147
|
+
};
|
|
73148
|
+
if (order.orderId !== undefined) {
|
|
73149
|
+
payload.orderId = order.orderId;
|
|
73150
|
+
}
|
|
73151
|
+
const origClientOrderId = order.origClientOrderId ?? order.clientOrderId;
|
|
73152
|
+
if (origClientOrderId !== undefined) {
|
|
73153
|
+
payload.origClientOrderId = origClientOrderId;
|
|
73154
|
+
}
|
|
73155
|
+
return client.marginAccountCancelOrder(payload);
|
|
73156
|
+
});
|
|
72919
73157
|
return await Promise.all(cancelPromises);
|
|
72920
73158
|
} else {
|
|
72921
73159
|
return await client.marginAccountCancelOpenOrders({
|
|
@@ -73170,7 +73408,7 @@ async function placeStopOrder(client, payload) {
|
|
|
73170
73408
|
});
|
|
73171
73409
|
}
|
|
73172
73410
|
}
|
|
73173
|
-
const spread = 1.00005;
|
|
73411
|
+
const spread = payload.stop > 30000 ? 1.00005 : 1.0002;
|
|
73174
73412
|
const order = {
|
|
73175
73413
|
kind: payload.kind,
|
|
73176
73414
|
side: payload.kind === "long" ? "sell" : "buy",
|
|
@@ -73577,6 +73815,16 @@ async function getAllOpenOrders(payload) {
|
|
|
73577
73815
|
const response = await client.getAllOpenOrders();
|
|
73578
73816
|
return response;
|
|
73579
73817
|
}
|
|
73818
|
+
function getPrintfFormatFromStepSize(stepSize, fallback) {
|
|
73819
|
+
if (stepSize === undefined || stepSize === null) {
|
|
73820
|
+
return fallback;
|
|
73821
|
+
}
|
|
73822
|
+
const normalized = String(stepSize);
|
|
73823
|
+
const [, decimalPart = ""] = normalized.split(".");
|
|
73824
|
+
const trimmed = decimalPart.replace(/0+$/, "");
|
|
73825
|
+
const digits = trimmed.length;
|
|
73826
|
+
return `%.${digits}f`;
|
|
73827
|
+
}
|
|
73580
73828
|
|
|
73581
73829
|
class BinanceExchange extends BaseExchange {
|
|
73582
73830
|
main_client;
|
|
@@ -74013,6 +74261,30 @@ class BinanceExchange extends BaseExchange {
|
|
|
74013
74261
|
throw error;
|
|
74014
74262
|
}
|
|
74015
74263
|
}
|
|
74264
|
+
async getSpotSymbolFormats(symbol) {
|
|
74265
|
+
if (!this.main_client || typeof this.main_client.getExchangeInfo !== "function") {
|
|
74266
|
+
return {
|
|
74267
|
+
price_places: "%.8f",
|
|
74268
|
+
decimal_places: "%.8f"
|
|
74269
|
+
};
|
|
74270
|
+
}
|
|
74271
|
+
try {
|
|
74272
|
+
const exchange_info = await this.main_client.getExchangeInfo();
|
|
74273
|
+
const symbol_info = exchange_info?.symbols?.find((item) => item.symbol === symbol.toUpperCase());
|
|
74274
|
+
const price_filter = symbol_info?.filters?.find((filter) => filter.filterType === "PRICE_FILTER");
|
|
74275
|
+
const lot_size_filter = symbol_info?.filters?.find((filter) => filter.filterType === "LOT_SIZE");
|
|
74276
|
+
return {
|
|
74277
|
+
price_places: getPrintfFormatFromStepSize(price_filter?.tickSize, "%.8f"),
|
|
74278
|
+
decimal_places: getPrintfFormatFromStepSize(lot_size_filter?.stepSize, "%.8f")
|
|
74279
|
+
};
|
|
74280
|
+
} catch (error) {
|
|
74281
|
+
console.error("Error fetching spot symbol formats:", error);
|
|
74282
|
+
return {
|
|
74283
|
+
price_places: "%.8f",
|
|
74284
|
+
decimal_places: "%.8f"
|
|
74285
|
+
};
|
|
74286
|
+
}
|
|
74287
|
+
}
|
|
74016
74288
|
async createMarginLimitOrders(payload) {
|
|
74017
74289
|
if (!this.main_client) {
|
|
74018
74290
|
throw new Error("Main client not available for margin trading");
|
|
@@ -74043,6 +74315,200 @@ class BinanceExchange extends BaseExchange {
|
|
|
74043
74315
|
}
|
|
74044
74316
|
return await getMarginOpenOrders(this.main_client, symbol, isIsolated);
|
|
74045
74317
|
}
|
|
74318
|
+
async getSpotMarginHedgeSnapshot(payload) {
|
|
74319
|
+
if (!this.main_client) {
|
|
74320
|
+
throw new Error("Main client not available for spot and margin trading");
|
|
74321
|
+
}
|
|
74322
|
+
if (payload.margin_type !== "isolated") {
|
|
74323
|
+
throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated is supported for replay right now.`);
|
|
74324
|
+
}
|
|
74325
|
+
const symbol = payload.symbol.toUpperCase();
|
|
74326
|
+
const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
|
|
74327
|
+
const quoteAsset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
|
|
74328
|
+
const baseAsset = symbol.slice(0, symbol.length - quoteAsset.length);
|
|
74329
|
+
const [spot_orders, margin_orders, spot_balances, isolated_margin_position] = await Promise.all([
|
|
74330
|
+
this.getSpotOpenOrders(symbol),
|
|
74331
|
+
this.getMarginOpenOrders(symbol, true),
|
|
74332
|
+
this.getSpotBalances([baseAsset, quoteAsset]),
|
|
74333
|
+
this.getIsolatedMarginPosition(symbol)
|
|
74334
|
+
]);
|
|
74335
|
+
const current_price = await this.getSpotCurrentPrice(symbol);
|
|
74336
|
+
const spot_quote_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === quoteAsset);
|
|
74337
|
+
const spot_base_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === baseAsset);
|
|
74338
|
+
const isolated_asset = isolated_margin_position?.assets?.[0];
|
|
74339
|
+
const borrowed_quote_amount = parseFloat(isolated_asset?.quoteAsset?.borrowed?.toString?.() || "0");
|
|
74340
|
+
return {
|
|
74341
|
+
symbol,
|
|
74342
|
+
margin_type: payload.margin_type,
|
|
74343
|
+
quote_asset: quoteAsset,
|
|
74344
|
+
base_asset: baseAsset,
|
|
74345
|
+
borrowed_quote_amount,
|
|
74346
|
+
spot_quote_free: parseFloat(spot_quote_balance?.free?.toString?.() || "0"),
|
|
74347
|
+
spot_base_free: parseFloat(spot_base_balance?.free?.toString?.() || "0"),
|
|
74348
|
+
spot_orders,
|
|
74349
|
+
margin_orders,
|
|
74350
|
+
current_price,
|
|
74351
|
+
spot_balances,
|
|
74352
|
+
isolated_margin_position
|
|
74353
|
+
};
|
|
74354
|
+
}
|
|
74355
|
+
async previewSpotMarginHedge(payload) {
|
|
74356
|
+
const snapshot = payload.snapshot ? {
|
|
74357
|
+
...payload.snapshot,
|
|
74358
|
+
current_price: payload.snapshot.current_price ?? await this.getSpotCurrentPrice(payload.symbol)
|
|
74359
|
+
} : await this.getSpotMarginHedgeSnapshot({
|
|
74360
|
+
symbol: payload.symbol,
|
|
74361
|
+
margin_type: payload.margin_type
|
|
74362
|
+
});
|
|
74363
|
+
const shouldInferLongOrders = !payload.long_orders || payload.long_orders.length === 0;
|
|
74364
|
+
const shouldInferShortOrders = !payload.short_orders || payload.short_orders.length === 0;
|
|
74365
|
+
const inferred_input = shouldInferLongOrders || shouldInferShortOrders ? inferSpotMarginHedgeInputFromLiveOrders({
|
|
74366
|
+
symbol: payload.symbol,
|
|
74367
|
+
margin_type: payload.margin_type,
|
|
74368
|
+
spot_orders: snapshot.spot_orders,
|
|
74369
|
+
margin_orders: snapshot.margin_orders
|
|
74370
|
+
}) : undefined;
|
|
74371
|
+
const long_orders = shouldInferLongOrders ? inferred_input?.long_orders || [] : payload.long_orders || [];
|
|
74372
|
+
const short_orders = shouldInferShortOrders ? inferred_input?.short_orders || [] : payload.short_orders || [];
|
|
74373
|
+
const borrow_amount = payload.borrow_amount ?? (long_orders.length > 0 ? sumNotional(long_orders) : inferred_input?.borrow_amount ?? 0);
|
|
74374
|
+
const requested_input = {
|
|
74375
|
+
borrow_amount,
|
|
74376
|
+
symbol: payload.symbol,
|
|
74377
|
+
margin_type: payload.margin_type,
|
|
74378
|
+
long_orders,
|
|
74379
|
+
short_orders,
|
|
74380
|
+
current_price: snapshot.current_price,
|
|
74381
|
+
max_spot_buy_orders: payload.max_spot_buy_orders,
|
|
74382
|
+
placement_overrides: inferred_input?.placement_overrides
|
|
74383
|
+
};
|
|
74384
|
+
const plan = buildSpotMarginHedgePlan({
|
|
74385
|
+
...requested_input,
|
|
74386
|
+
snapshot
|
|
74387
|
+
});
|
|
74388
|
+
const comparison = compareSpotMarginHedgeOrders({
|
|
74389
|
+
plan,
|
|
74390
|
+
spot_orders: snapshot.spot_orders,
|
|
74391
|
+
margin_orders: snapshot.margin_orders
|
|
74392
|
+
});
|
|
74393
|
+
return {
|
|
74394
|
+
snapshot,
|
|
74395
|
+
inferred_input,
|
|
74396
|
+
requested_input,
|
|
74397
|
+
plan,
|
|
74398
|
+
comparison
|
|
74399
|
+
};
|
|
74400
|
+
}
|
|
74401
|
+
async placeSpotMarginHedge(payload) {
|
|
74402
|
+
const preview = await this.previewSpotMarginHedge(payload);
|
|
74403
|
+
if (!payload.place) {
|
|
74404
|
+
return preview;
|
|
74405
|
+
}
|
|
74406
|
+
if (!this.main_client) {
|
|
74407
|
+
throw new Error("Main client not available for spot and margin trading");
|
|
74408
|
+
}
|
|
74409
|
+
if (payload.margin_type !== "isolated") {
|
|
74410
|
+
throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated placement is supported right now.`);
|
|
74411
|
+
}
|
|
74412
|
+
const symbol = payload.symbol.toUpperCase();
|
|
74413
|
+
const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
|
|
74414
|
+
const quote_asset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
|
|
74415
|
+
const base_asset = symbol.slice(0, symbol.length - quote_asset.length);
|
|
74416
|
+
const requested_input = preview.requested_input;
|
|
74417
|
+
if (preview.snapshot.spot_orders.length > 0) {
|
|
74418
|
+
await this.cancelSpotOrders(symbol, preview.snapshot.spot_orders.map((order) => ({
|
|
74419
|
+
orderId: order.orderId || order.order_id || order.id,
|
|
74420
|
+
origClientOrderId: order.origClientOrderId
|
|
74421
|
+
})));
|
|
74422
|
+
}
|
|
74423
|
+
if (preview.snapshot.margin_orders.length > 0) {
|
|
74424
|
+
await this.cancelMarginOrders(symbol, true, preview.snapshot.margin_orders.map((order) => ({
|
|
74425
|
+
orderId: order.orderId || order.order_id || order.id,
|
|
74426
|
+
origClientOrderId: order.origClientOrderId
|
|
74427
|
+
})));
|
|
74428
|
+
}
|
|
74429
|
+
const refreshed_snapshot = await this.getSpotMarginHedgeSnapshot({
|
|
74430
|
+
symbol,
|
|
74431
|
+
margin_type: payload.margin_type
|
|
74432
|
+
});
|
|
74433
|
+
const symbol_formats = await this.getSpotSymbolFormats(symbol);
|
|
74434
|
+
const execution_plan = buildSpotMarginHedgePlan({
|
|
74435
|
+
borrow_amount: requested_input.borrow_amount,
|
|
74436
|
+
symbol: requested_input.symbol,
|
|
74437
|
+
margin_type: requested_input.margin_type,
|
|
74438
|
+
long_orders: requested_input.long_orders,
|
|
74439
|
+
short_orders: requested_input.short_orders,
|
|
74440
|
+
current_price: refreshed_snapshot.current_price,
|
|
74441
|
+
max_spot_buy_orders: requested_input.max_spot_buy_orders,
|
|
74442
|
+
snapshot: refreshed_snapshot
|
|
74443
|
+
});
|
|
74444
|
+
let borrow_result = null;
|
|
74445
|
+
if (execution_plan.borrow_delta > 0) {
|
|
74446
|
+
borrow_result = await this.main_client.submitMarginAccountBorrowRepay({
|
|
74447
|
+
asset: quote_asset,
|
|
74448
|
+
amount: execution_plan.borrow_delta,
|
|
74449
|
+
isIsolated: "TRUE",
|
|
74450
|
+
symbol,
|
|
74451
|
+
type: "BORROW"
|
|
74452
|
+
});
|
|
74453
|
+
}
|
|
74454
|
+
let quote_transfer_result = null;
|
|
74455
|
+
if (execution_plan.transfer_to_spot_amount > 0) {
|
|
74456
|
+
quote_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
|
|
74457
|
+
asset: quote_asset,
|
|
74458
|
+
amount: execution_plan.transfer_to_spot_amount,
|
|
74459
|
+
symbol,
|
|
74460
|
+
transFrom: "ISOLATED_MARGIN",
|
|
74461
|
+
transTo: "SPOT"
|
|
74462
|
+
});
|
|
74463
|
+
}
|
|
74464
|
+
let base_transfer_result = null;
|
|
74465
|
+
if (execution_plan.transfer_base_to_spot_amount > 0) {
|
|
74466
|
+
base_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
|
|
74467
|
+
asset: base_asset,
|
|
74468
|
+
amount: execution_plan.transfer_base_to_spot_amount,
|
|
74469
|
+
symbol,
|
|
74470
|
+
transFrom: "ISOLATED_MARGIN",
|
|
74471
|
+
transTo: "SPOT"
|
|
74472
|
+
});
|
|
74473
|
+
}
|
|
74474
|
+
const spot_result = execution_plan.orders_to_create.spot.length > 0 ? await this.createSpotLimitOrders({
|
|
74475
|
+
symbol,
|
|
74476
|
+
orders: execution_plan.orders_to_create.spot,
|
|
74477
|
+
price_places: symbol_formats.price_places,
|
|
74478
|
+
decimal_places: symbol_formats.decimal_places
|
|
74479
|
+
}) : [];
|
|
74480
|
+
const margin_result = execution_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
|
|
74481
|
+
symbol,
|
|
74482
|
+
orders: execution_plan.orders_to_create.margin,
|
|
74483
|
+
isIsolated: true,
|
|
74484
|
+
price_places: symbol_formats.price_places,
|
|
74485
|
+
decimal_places: symbol_formats.decimal_places
|
|
74486
|
+
}) : [];
|
|
74487
|
+
return {
|
|
74488
|
+
...preview,
|
|
74489
|
+
execution_plan,
|
|
74490
|
+
refreshed_snapshot,
|
|
74491
|
+
execution: {
|
|
74492
|
+
borrow: borrow_result ? {
|
|
74493
|
+
asset: quote_asset,
|
|
74494
|
+
amount: execution_plan.borrow_delta,
|
|
74495
|
+
response: borrow_result
|
|
74496
|
+
} : null,
|
|
74497
|
+
quote_transfer: quote_transfer_result ? {
|
|
74498
|
+
asset: quote_asset,
|
|
74499
|
+
amount: execution_plan.transfer_to_spot_amount,
|
|
74500
|
+
response: quote_transfer_result
|
|
74501
|
+
} : null,
|
|
74502
|
+
base_transfer: base_transfer_result ? {
|
|
74503
|
+
asset: base_asset,
|
|
74504
|
+
amount: execution_plan.transfer_base_to_spot_amount,
|
|
74505
|
+
response: base_transfer_result
|
|
74506
|
+
} : null,
|
|
74507
|
+
spot_orders: spot_result,
|
|
74508
|
+
margin_orders: margin_result
|
|
74509
|
+
}
|
|
74510
|
+
};
|
|
74511
|
+
}
|
|
74046
74512
|
async getLastUserTrade(payload) {
|
|
74047
74513
|
return await getLastUserTrade(this.client, payload.symbol);
|
|
74048
74514
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -372,7 +372,47 @@ declare abstract class BaseExchange {
|
|
|
372
372
|
place?: boolean;
|
|
373
373
|
cancel?: boolean;
|
|
374
374
|
}): Promise<any>;
|
|
375
|
+
previewSpotMarginHedge(_payload: {
|
|
376
|
+
borrow_amount?: number;
|
|
377
|
+
symbol: string;
|
|
378
|
+
margin_type: "isolated" | "cross";
|
|
379
|
+
long_orders?: Array<{
|
|
380
|
+
price: number;
|
|
381
|
+
quantity: number;
|
|
382
|
+
}>;
|
|
383
|
+
short_orders?: Array<{
|
|
384
|
+
price: number;
|
|
385
|
+
quantity: number;
|
|
386
|
+
}>;
|
|
387
|
+
max_spot_buy_orders?: number;
|
|
388
|
+
snapshot?: any;
|
|
389
|
+
}): Promise<any>;
|
|
390
|
+
placeSpotMarginHedge(_payload: {
|
|
391
|
+
borrow_amount?: number;
|
|
392
|
+
symbol: string;
|
|
393
|
+
margin_type: "isolated" | "cross";
|
|
394
|
+
long_orders?: Array<{
|
|
395
|
+
price: number;
|
|
396
|
+
quantity: number;
|
|
397
|
+
}>;
|
|
398
|
+
short_orders?: Array<{
|
|
399
|
+
price: number;
|
|
400
|
+
quantity: number;
|
|
401
|
+
}>;
|
|
402
|
+
max_spot_buy_orders?: number;
|
|
403
|
+
snapshot?: any;
|
|
404
|
+
place?: boolean;
|
|
405
|
+
}): Promise<any>;
|
|
375
406
|
placeSpotLimitOrders(_payload: any): Promise<void>;
|
|
407
|
+
buildCumulative(payload: {
|
|
408
|
+
orders: Order$1[];
|
|
409
|
+
kind: "long" | "short";
|
|
410
|
+
decimal_places?: string;
|
|
411
|
+
price_places?: string;
|
|
412
|
+
symbol: string;
|
|
413
|
+
place?: boolean;
|
|
414
|
+
is_stop?: boolean;
|
|
415
|
+
}): Promise<any[]>;
|
|
376
416
|
bulkPlaceLimitOrders(payload: {
|
|
377
417
|
orders: Order$1[];
|
|
378
418
|
kind: "long" | "short";
|