@yuants/vendor-huobi 0.9.8 → 0.10.0
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/api.js +313 -0
- package/dist/api.js.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -0
- package/dist/cluster.js +72 -0
- package/dist/cluster.js.map +1 -0
- package/dist/extension.js +87 -0
- package/dist/extension.js.map +1 -0
- package/dist/index.js +582 -0
- package/dist/index.js.map +1 -0
- package/dist/interest_rate.js +59 -0
- package/dist/interest_rate.js.map +1 -0
- package/dist/logger.js +91 -0
- package/dist/logger.js.map +1 -0
- package/dist/product.js +85 -0
- package/dist/product.js.map +1 -0
- package/dist/quote.js +93 -0
- package/dist/quote.js.map +1 -0
- package/dist/vendor-huobi.d.ts +1 -0
- package/lib/api.d.ts +634 -0
- package/lib/api.d.ts.map +1 -0
- package/lib/api.js +320 -0
- package/lib/api.js.map +1 -0
- package/lib/cli.d.ts +3 -0
- package/lib/cli.d.ts.map +1 -0
- package/lib/cli.js +5 -0
- package/lib/cli.js.map +1 -0
- package/lib/cluster.d.ts +2 -0
- package/lib/cluster.d.ts.map +1 -0
- package/lib/cluster.js +100 -0
- package/lib/cluster.js.map +1 -0
- package/lib/extension.d.ts +4 -0
- package/lib/extension.d.ts.map +1 -0
- package/lib/extension.js +89 -0
- package/lib/extension.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +584 -0
- package/lib/index.js.map +1 -0
- package/lib/interest_rate.d.ts +2 -0
- package/lib/interest_rate.d.ts.map +1 -0
- package/lib/interest_rate.js +61 -0
- package/lib/interest_rate.js.map +1 -0
- package/lib/logger.d.ts +21 -0
- package/lib/logger.d.ts.map +1 -0
- package/lib/logger.js +98 -0
- package/lib/logger.js.map +1 -0
- package/lib/product.d.ts +4 -0
- package/lib/product.d.ts.map +1 -0
- package/lib/product.js +88 -0
- package/lib/product.js.map +1 -0
- package/lib/quote.d.ts +2 -0
- package/lib/quote.d.ts.map +1 -0
- package/lib/quote.js +95 -0
- package/lib/quote.js.map +1 -0
- package/package.json +5 -2
- package/temp/image-tag +1 -0
- package/temp/package-deps.json +37 -0
- package/temp/vendor-huobi.api.json +177 -0
- package/temp/vendor-huobi.api.md +9 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import { addAccountMarket, publishAccountInfo, } from '@yuants/data-account';
|
|
2
|
+
import { Terminal } from '@yuants/protocol';
|
|
3
|
+
import { addAccountTransferAddress } from '@yuants/transfer';
|
|
4
|
+
import { formatTime, roundToStep } from '@yuants/utils';
|
|
5
|
+
import { catchError, combineLatestWith, defer, distinct, filter, first, firstValueFrom, from, map, mergeMap, of, reduce, repeat, retry, shareReplay, tap, timeout, toArray, } from 'rxjs';
|
|
6
|
+
import { client } from './api';
|
|
7
|
+
import './interest_rate';
|
|
8
|
+
import { perpetualContractProducts$, spotProducts$ } from './product';
|
|
9
|
+
import './quote';
|
|
10
|
+
const terminal = Terminal.fromNodeEnv();
|
|
11
|
+
(async () => {
|
|
12
|
+
var _a, _b, _c;
|
|
13
|
+
const swapAccountTypeRes = await client.getSwapUnifiedAccountType();
|
|
14
|
+
if (((_a = swapAccountTypeRes.data) === null || _a === void 0 ? void 0 : _a.account_type) === 1) {
|
|
15
|
+
console.info(formatTime(Date.now()), 'SwitchingAccountType', `previous: ${swapAccountTypeRes.data.account_type}, switching to 2 (unified account)`);
|
|
16
|
+
const switchRes = await client.postSwapSwitchAccountType({ account_type: 2 });
|
|
17
|
+
console.info(formatTime(Date.now()), 'SwitchingAccountType', `current: ${switchRes.data.account_type}`);
|
|
18
|
+
}
|
|
19
|
+
const huobiUid = (await client.getUid()).data;
|
|
20
|
+
console.info(formatTime(Date.now()), 'UID', huobiUid);
|
|
21
|
+
const huobiAccounts = await client.getAccount();
|
|
22
|
+
const superMarginAccountUid = (_b = huobiAccounts.data.find((v) => v.type === 'super-margin')) === null || _b === void 0 ? void 0 : _b.id;
|
|
23
|
+
const spotAccountUid = (_c = huobiAccounts.data.find((v) => v.type === 'spot')) === null || _c === void 0 ? void 0 : _c.id;
|
|
24
|
+
console.info(formatTime(Date.now()), 'huobiAccount', JSON.stringify(huobiAccounts));
|
|
25
|
+
const account_id = `huobi/${huobiUid}`;
|
|
26
|
+
const SPOT_ACCOUNT_ID = `${account_id}/spot/usdt`;
|
|
27
|
+
const SUPER_MARGIN_ACCOUNT_ID = `${account_id}/super-margin`;
|
|
28
|
+
const SWAP_ACCOUNT_ID = `${account_id}/swap`;
|
|
29
|
+
const subUsersRes = await client.getSubUserList();
|
|
30
|
+
const subAccounts = subUsersRes.data;
|
|
31
|
+
const isMainAccount = subUsersRes.ok;
|
|
32
|
+
console.info(formatTime(Date.now()), 'subAccounts', JSON.stringify(subAccounts));
|
|
33
|
+
const mapProductIdToPerpetualProduct$ = perpetualContractProducts$.pipe(map((x) => new Map(x.map((v) => [v.product_id, v]))), shareReplay(1));
|
|
34
|
+
const perpetualContractAccountInfo$ = defer(async () => {
|
|
35
|
+
var _a;
|
|
36
|
+
// balance
|
|
37
|
+
const balance = await client.getUnifiedAccountInfo();
|
|
38
|
+
if (!balance.data) {
|
|
39
|
+
throw new Error('Failed to get unified account info');
|
|
40
|
+
}
|
|
41
|
+
const balanceData = balance.data.find((v) => v.margin_asset === 'USDT');
|
|
42
|
+
if (!balanceData) {
|
|
43
|
+
throw new Error('No USDT balance found in unified account');
|
|
44
|
+
}
|
|
45
|
+
const money = {
|
|
46
|
+
currency: 'USDT',
|
|
47
|
+
balance: balanceData.cross_margin_static,
|
|
48
|
+
equity: balanceData.margin_balance,
|
|
49
|
+
profit: balanceData.cross_profit_unreal,
|
|
50
|
+
free: balanceData.withdraw_available,
|
|
51
|
+
used: balanceData.margin_balance - balanceData.withdraw_available,
|
|
52
|
+
};
|
|
53
|
+
// positions
|
|
54
|
+
const positionsRes = await client.getSwapCrossPositionInfo();
|
|
55
|
+
const mapProductIdToPerpetualProduct = await firstValueFrom(mapProductIdToPerpetualProduct$);
|
|
56
|
+
const positions = (positionsRes.data || []).map((v) => {
|
|
57
|
+
const product_id = v.contract_code;
|
|
58
|
+
const theProduct = mapProductIdToPerpetualProduct === null || mapProductIdToPerpetualProduct === void 0 ? void 0 : mapProductIdToPerpetualProduct.get(product_id);
|
|
59
|
+
const valuation = v.volume * v.last_price * ((theProduct === null || theProduct === void 0 ? void 0 : theProduct.value_scale) || 1);
|
|
60
|
+
return {
|
|
61
|
+
position_id: `${v.contract_code}/${v.contract_type}/${v.direction}/${v.margin_mode}`,
|
|
62
|
+
datasource_id: 'HUOBI-SWAP',
|
|
63
|
+
product_id,
|
|
64
|
+
direction: v.direction === 'buy' ? 'LONG' : 'SHORT',
|
|
65
|
+
volume: v.volume,
|
|
66
|
+
free_volume: v.available,
|
|
67
|
+
position_price: v.cost_hold,
|
|
68
|
+
closable_price: v.last_price,
|
|
69
|
+
floating_profit: v.profit_unreal,
|
|
70
|
+
valuation,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
// orders
|
|
74
|
+
const orders = [];
|
|
75
|
+
let page_index = 1;
|
|
76
|
+
const page_size = 50;
|
|
77
|
+
while (true) {
|
|
78
|
+
const ordersRes = await client.getSwapOpenOrders({ page_index, page_size });
|
|
79
|
+
if (!((_a = ordersRes.data) === null || _a === void 0 ? void 0 : _a.orders) || ordersRes.data.orders.length === 0) {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
const pageOrders = ordersRes.data.orders.map((v) => {
|
|
83
|
+
return {
|
|
84
|
+
order_id: v.order_id_str,
|
|
85
|
+
account_id: SWAP_ACCOUNT_ID,
|
|
86
|
+
product_id: v.contract_code,
|
|
87
|
+
order_type: ['lightning'].includes(v.order_price_type)
|
|
88
|
+
? 'MARKET'
|
|
89
|
+
: ['limit', 'opponent', 'post_only', 'optimal_5', 'optimal_10', 'optimal_20'].includes(v.order_price_type)
|
|
90
|
+
? 'LIMIT'
|
|
91
|
+
: ['fok'].includes(v.order_price_type)
|
|
92
|
+
? 'FOK'
|
|
93
|
+
: v.order_price_type.includes('ioc')
|
|
94
|
+
? 'IOC'
|
|
95
|
+
: 'STOP',
|
|
96
|
+
order_direction: v.direction === 'open'
|
|
97
|
+
? v.offset === 'buy'
|
|
98
|
+
? 'OPEN_LONG'
|
|
99
|
+
: 'OPEN_SHORT'
|
|
100
|
+
: v.offset === 'buy'
|
|
101
|
+
? 'CLOSE_SHORT'
|
|
102
|
+
: 'CLOSE_LONG',
|
|
103
|
+
volume: v.volume,
|
|
104
|
+
submit_at: v.created_at,
|
|
105
|
+
price: v.price,
|
|
106
|
+
traded_volume: v.trade_volume,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
orders.push(...pageOrders);
|
|
110
|
+
page_index++;
|
|
111
|
+
}
|
|
112
|
+
const accountInfo = {
|
|
113
|
+
updated_at: Date.now(),
|
|
114
|
+
account_id: SWAP_ACCOUNT_ID,
|
|
115
|
+
money: money,
|
|
116
|
+
currencies: [money],
|
|
117
|
+
positions,
|
|
118
|
+
orders,
|
|
119
|
+
};
|
|
120
|
+
return accountInfo;
|
|
121
|
+
}).pipe(repeat({ delay: 1000 }), tap({
|
|
122
|
+
error: (e) => {
|
|
123
|
+
console.error(formatTime(Date.now()), 'perpetualContractAccountInfo', e);
|
|
124
|
+
},
|
|
125
|
+
}), retry({ delay: 1000 }), shareReplay(1));
|
|
126
|
+
const superMarginUnifiedRawAccountBalance$ = defer(() => client.getSpotAccountBalance(superMarginAccountUid)).pipe(
|
|
127
|
+
//
|
|
128
|
+
map((res) => res.data), repeat({ delay: 1000 }), tap({
|
|
129
|
+
error: (e) => {
|
|
130
|
+
console.error(formatTime(Date.now()), 'unifiedRaw', e);
|
|
131
|
+
},
|
|
132
|
+
}), retry({ delay: 5000 }), shareReplay(1));
|
|
133
|
+
const subscriptions = new Set();
|
|
134
|
+
from(client.spot_ws.connection$).subscribe(() => {
|
|
135
|
+
subscriptions.clear();
|
|
136
|
+
});
|
|
137
|
+
// subscribe the symbols of positions we held
|
|
138
|
+
superMarginUnifiedRawAccountBalance$
|
|
139
|
+
.pipe(
|
|
140
|
+
//
|
|
141
|
+
mergeMap((res) => from((res === null || res === void 0 ? void 0 : res.list) || []).pipe(filter((v) => v.currency !== 'usdt'), map((v) => v.currency), distinct(), toArray(), map((v) => new Set(v)))))
|
|
142
|
+
.subscribe((v) => {
|
|
143
|
+
const toUnsubscribe = [...subscriptions].filter((x) => !v.has(x));
|
|
144
|
+
const toSubscribe = [...v].filter((x) => !subscriptions.has(x));
|
|
145
|
+
for (const symbol of toUnsubscribe) {
|
|
146
|
+
client.spot_ws.output$.next({
|
|
147
|
+
unsub: `market.${symbol}usdt.ticker`,
|
|
148
|
+
});
|
|
149
|
+
subscriptions.delete(symbol);
|
|
150
|
+
}
|
|
151
|
+
for (const symbol of toSubscribe) {
|
|
152
|
+
client.spot_ws.output$.next({
|
|
153
|
+
sub: `market.${symbol}usdt.ticker`,
|
|
154
|
+
});
|
|
155
|
+
subscriptions.add(symbol);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
const superMarginAccountInfo$ = defer(async () => {
|
|
159
|
+
var _a;
|
|
160
|
+
// get account balance
|
|
161
|
+
const accountBalance = await client.getSpotAccountBalance(superMarginAccountUid);
|
|
162
|
+
const balanceList = ((_a = accountBalance.data) === null || _a === void 0 ? void 0 : _a.list) || [];
|
|
163
|
+
// calculate usdt balance
|
|
164
|
+
const usdtBalance = balanceList
|
|
165
|
+
.filter((v) => v.currency === 'usdt')
|
|
166
|
+
.reduce((acc, cur) => acc + +cur.balance, 0);
|
|
167
|
+
// get positions (non-usdt currencies)
|
|
168
|
+
const positions = [];
|
|
169
|
+
const nonUsdtCurrencies = balanceList
|
|
170
|
+
.filter((v) => v.currency !== 'usdt')
|
|
171
|
+
.reduce((acc, cur) => {
|
|
172
|
+
const existing = acc.find((item) => item.currency === cur.currency);
|
|
173
|
+
if (existing) {
|
|
174
|
+
existing.balance += +cur.balance;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
acc.push({ currency: cur.currency, balance: +cur.balance });
|
|
178
|
+
}
|
|
179
|
+
return acc;
|
|
180
|
+
}, []);
|
|
181
|
+
// get prices and create positions
|
|
182
|
+
for (const currencyData of nonUsdtCurrencies) {
|
|
183
|
+
if (currencyData.balance > 0) {
|
|
184
|
+
try {
|
|
185
|
+
// get current price from websocket or fallback to REST API
|
|
186
|
+
let price;
|
|
187
|
+
try {
|
|
188
|
+
const tickPrice = await firstValueFrom(client.spot_ws.input$.pipe(
|
|
189
|
+
//
|
|
190
|
+
first((v) => { var _a, _b; return ((_a = v.ch) === null || _a === void 0 ? void 0 : _a.includes('ticker')) && ((_b = v.ch) === null || _b === void 0 ? void 0 : _b.includes(currencyData.currency)) && v.tick; }), map((v) => v.tick.bid), timeout(5000), tap({
|
|
191
|
+
error: (e) => {
|
|
192
|
+
subscriptions.clear();
|
|
193
|
+
},
|
|
194
|
+
})));
|
|
195
|
+
price = tickPrice;
|
|
196
|
+
}
|
|
197
|
+
catch (_b) {
|
|
198
|
+
// fallback to REST API
|
|
199
|
+
const tickerRes = await client.getSpotTick({ symbol: `${currencyData.currency}usdt` });
|
|
200
|
+
price = tickerRes.tick.close;
|
|
201
|
+
}
|
|
202
|
+
positions.push({
|
|
203
|
+
position_id: `${currencyData.currency}/usdt/spot`,
|
|
204
|
+
product_id: `${currencyData.currency}usdt`,
|
|
205
|
+
direction: 'LONG',
|
|
206
|
+
volume: currencyData.balance,
|
|
207
|
+
free_volume: currencyData.balance,
|
|
208
|
+
position_price: price,
|
|
209
|
+
closable_price: price,
|
|
210
|
+
floating_profit: 0,
|
|
211
|
+
valuation: currencyData.balance * price,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
console.warn(formatTime(Date.now()), `Failed to get price for ${currencyData.currency}:`, error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// calculate equity
|
|
220
|
+
const equity = positions.reduce((acc, cur) => acc + cur.closable_price * cur.volume, 0) + usdtBalance;
|
|
221
|
+
const money = {
|
|
222
|
+
currency: 'USDT',
|
|
223
|
+
balance: equity,
|
|
224
|
+
equity: equity,
|
|
225
|
+
profit: 0,
|
|
226
|
+
free: equity,
|
|
227
|
+
used: 0,
|
|
228
|
+
};
|
|
229
|
+
const accountInfo = {
|
|
230
|
+
updated_at: Date.now(),
|
|
231
|
+
account_id: SUPER_MARGIN_ACCOUNT_ID,
|
|
232
|
+
money: money,
|
|
233
|
+
currencies: [money],
|
|
234
|
+
positions,
|
|
235
|
+
orders: [],
|
|
236
|
+
};
|
|
237
|
+
return accountInfo;
|
|
238
|
+
}).pipe(repeat({ delay: 1000 }), tap({
|
|
239
|
+
error: (e) => {
|
|
240
|
+
console.error(formatTime(Date.now()), 'superMarginAccountInfo', e);
|
|
241
|
+
},
|
|
242
|
+
}), retry({ delay: 5000 }), shareReplay(1));
|
|
243
|
+
const spotRawBalance$ = defer(() => client.getSpotAccountBalance(spotAccountUid)).pipe(repeat({ delay: 1000 }), retry({ delay: 5000 }), shareReplay(1));
|
|
244
|
+
const spotAccountInfo$ = spotRawBalance$.pipe(map((spotBalance) => {
|
|
245
|
+
var _a, _b;
|
|
246
|
+
const balance = +((_b = (_a = spotBalance.data.list.find((v) => v.currency === 'usdt')) === null || _a === void 0 ? void 0 : _a.balance) !== null && _b !== void 0 ? _b : 0);
|
|
247
|
+
const equity = balance;
|
|
248
|
+
const free = equity;
|
|
249
|
+
const money = {
|
|
250
|
+
currency: 'USDT',
|
|
251
|
+
balance,
|
|
252
|
+
equity,
|
|
253
|
+
profit: 0,
|
|
254
|
+
free,
|
|
255
|
+
used: 0,
|
|
256
|
+
};
|
|
257
|
+
return {
|
|
258
|
+
updated_at: Date.now(),
|
|
259
|
+
account_id: SPOT_ACCOUNT_ID,
|
|
260
|
+
money: money,
|
|
261
|
+
currencies: [money],
|
|
262
|
+
positions: [],
|
|
263
|
+
orders: [],
|
|
264
|
+
};
|
|
265
|
+
}), shareReplay(1));
|
|
266
|
+
publishAccountInfo(terminal, SPOT_ACCOUNT_ID, spotAccountInfo$);
|
|
267
|
+
addAccountMarket(terminal, { account_id: SPOT_ACCOUNT_ID, market_id: 'HUOBI/SPOT' });
|
|
268
|
+
publishAccountInfo(terminal, SUPER_MARGIN_ACCOUNT_ID, superMarginAccountInfo$);
|
|
269
|
+
addAccountMarket(terminal, { account_id: SUPER_MARGIN_ACCOUNT_ID, market_id: 'HUOBI/SUPER-MARGIN' });
|
|
270
|
+
publishAccountInfo(terminal, SWAP_ACCOUNT_ID, perpetualContractAccountInfo$);
|
|
271
|
+
addAccountMarket(terminal, { account_id: SWAP_ACCOUNT_ID, market_id: 'HUOBI/SWAP' });
|
|
272
|
+
// Submit order
|
|
273
|
+
terminal.provideService('SubmitOrder', {
|
|
274
|
+
required: ['account_id'],
|
|
275
|
+
properties: {
|
|
276
|
+
account_id: {
|
|
277
|
+
enum: [SUPER_MARGIN_ACCOUNT_ID, SWAP_ACCOUNT_ID],
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
}, (msg) => {
|
|
281
|
+
const { account_id: req_account_id } = msg.req;
|
|
282
|
+
console.info(formatTime(Date.now()), `SubmitOrder for ${account_id}`, JSON.stringify(msg));
|
|
283
|
+
if (req_account_id === SWAP_ACCOUNT_ID) {
|
|
284
|
+
return defer(() => client.getSwapCrossPositionInfo()).pipe(mergeMap((res) => res.data), map((v) => [v.contract_code, v.lever_rate]), toArray(), map((v) => Object.fromEntries(v)), mergeMap((mapContractCodeToRate) => {
|
|
285
|
+
var _a;
|
|
286
|
+
const lever_rate = (_a = mapContractCodeToRate[msg.req.product_id]) !== null && _a !== void 0 ? _a : 20;
|
|
287
|
+
const params = {
|
|
288
|
+
contract_code: msg.req.product_id,
|
|
289
|
+
contract_type: 'swap',
|
|
290
|
+
price: msg.req.price,
|
|
291
|
+
volume: msg.req.volume,
|
|
292
|
+
offset: msg.req.order_direction === 'OPEN_LONG' || msg.req.order_direction === 'OPEN_SHORT'
|
|
293
|
+
? 'open'
|
|
294
|
+
: 'close',
|
|
295
|
+
direction: msg.req.order_direction === 'OPEN_LONG' || msg.req.order_direction === 'CLOSE_SHORT'
|
|
296
|
+
? 'buy'
|
|
297
|
+
: 'sell',
|
|
298
|
+
// dynamically adjust the leverage
|
|
299
|
+
lever_rate,
|
|
300
|
+
order_price_type: msg.req.order_type === 'MARKET' ? 'market' : 'limit',
|
|
301
|
+
};
|
|
302
|
+
return client.postSwapOrder(params).then((v) => {
|
|
303
|
+
console.info(formatTime(Date.now()), 'SubmitOrder', JSON.stringify(v), JSON.stringify(params));
|
|
304
|
+
return v;
|
|
305
|
+
});
|
|
306
|
+
}), map((v) => {
|
|
307
|
+
if (v.status !== 'ok') {
|
|
308
|
+
return { res: { code: 500, message: v.status } };
|
|
309
|
+
}
|
|
310
|
+
return { res: { code: 0, message: 'OK' } };
|
|
311
|
+
}), catchError((e) => {
|
|
312
|
+
console.error(formatTime(Date.now()), 'SubmitOrder', e);
|
|
313
|
+
return of({ res: { code: 500, message: `${e}` } });
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
// for super-margin orders, we need to denote the amount of usdt to borrow, therefore we need to:
|
|
317
|
+
// 1. get the loanable amount
|
|
318
|
+
// 2. get the current balance
|
|
319
|
+
// 3. get the current price
|
|
320
|
+
// 4. combine the information to submit the order
|
|
321
|
+
return defer(() => client.getCrossMarginLoanInfo()).pipe(
|
|
322
|
+
//
|
|
323
|
+
mergeMap((res) => res.data), first((v) => v.currency === 'usdt'), map((v) => +v['loanable-amt']), combineLatestWith(superMarginUnifiedRawAccountBalance$.pipe(first(), mergeMap((res) => from(res.list).pipe(
|
|
324
|
+
// we only need the amount of usdt that can be used to trade
|
|
325
|
+
filter((v) => v.currency === 'usdt' && v.type === 'trade'), reduce((acc, cur) => acc + +cur.balance, 0))))), combineLatestWith(spotProducts$.pipe(first())), mergeMap(async ([[loanable, balance], products]) => {
|
|
326
|
+
const priceRes = await client.getSpotTick({ symbol: msg.req.product_id });
|
|
327
|
+
const theProduct = products.find((v) => v.product_id === msg.req.product_id);
|
|
328
|
+
const price = priceRes.tick.close;
|
|
329
|
+
const borrow_amount = msg.req.order_direction === 'OPEN_LONG' || msg.req.order_direction === 'CLOSE_SHORT'
|
|
330
|
+
? Math.max(Math.min(loanable, msg.req.volume * price - balance), 0)
|
|
331
|
+
: undefined;
|
|
332
|
+
const params = {
|
|
333
|
+
symbol: msg.req.product_id,
|
|
334
|
+
'account-id': '' + superMarginAccountUid,
|
|
335
|
+
// amount: msg.req.type === OrderType.MARKET ? 0 : '' + msg.req.volume,
|
|
336
|
+
// 'market-amount': msg.req.type === OrderType.MARKET ? '' + msg.req.volume : undefined,
|
|
337
|
+
amount: '' +
|
|
338
|
+
(msg.req.order_direction === 'OPEN_LONG' || msg.req.order_direction === 'CLOSE_SHORT'
|
|
339
|
+
? roundToStep(msg.req.volume * price, theProduct === null || theProduct === void 0 ? void 0 : theProduct.volume_step)
|
|
340
|
+
: msg.req.volume),
|
|
341
|
+
'borrow-amount': '' + borrow_amount,
|
|
342
|
+
type: `${msg.req.order_direction === 'OPEN_LONG' || msg.req.order_direction === 'CLOSE_SHORT'
|
|
343
|
+
? 'buy'
|
|
344
|
+
: 'sell'}-${'LIMIT' === msg.req.order_type ? 'limit' : 'market'}`,
|
|
345
|
+
'trade-purpose': msg.req.order_direction === 'OPEN_LONG' || msg.req.order_direction === 'CLOSE_SHORT'
|
|
346
|
+
? '1' // auto borrow
|
|
347
|
+
: '2',
|
|
348
|
+
price: msg.req.order_type === 'MARKET' ? undefined : '' + msg.req.price,
|
|
349
|
+
source: 'super-margin-api',
|
|
350
|
+
};
|
|
351
|
+
return client.postSpotOrder(params).then((v) => {
|
|
352
|
+
console.info(formatTime(Date.now()), 'SubmitOrder', JSON.stringify(v), JSON.stringify(params));
|
|
353
|
+
return v;
|
|
354
|
+
});
|
|
355
|
+
}), map((v) => {
|
|
356
|
+
if (v.success === false) {
|
|
357
|
+
return { res: { code: v.code, message: v.message } };
|
|
358
|
+
}
|
|
359
|
+
return { res: { code: 0, message: 'OK' } };
|
|
360
|
+
}), catchError((e) => {
|
|
361
|
+
console.error(formatTime(Date.now()), 'SubmitOrder', e);
|
|
362
|
+
return of({ res: { code: 500, message: `${e}` } });
|
|
363
|
+
}));
|
|
364
|
+
});
|
|
365
|
+
// Update Spot TRC20 Addresses (Only Main Account)
|
|
366
|
+
if (isMainAccount) {
|
|
367
|
+
const res = await client.getSpotAccountDepositAddresses({ currency: 'usdt' });
|
|
368
|
+
const addresses = res.data.filter((v) => v.chain === 'trc20usdt').map((v) => v.address);
|
|
369
|
+
for (const address of addresses) {
|
|
370
|
+
addAccountTransferAddress({
|
|
371
|
+
terminal,
|
|
372
|
+
account_id: SPOT_ACCOUNT_ID,
|
|
373
|
+
currency: 'USDT',
|
|
374
|
+
address: address,
|
|
375
|
+
network_id: 'TRC20',
|
|
376
|
+
onApply: {
|
|
377
|
+
INIT: async (order) => {
|
|
378
|
+
var _a, _b;
|
|
379
|
+
const res0 = await client.getV2ReferenceCurrencies({ currency: 'usdt' });
|
|
380
|
+
const fee = (_b = (_a = res0.data
|
|
381
|
+
.find((v) => v.currency === 'usdt')) === null || _a === void 0 ? void 0 : _a.chains.find((v) => v.chain === 'trc20usdt')) === null || _b === void 0 ? void 0 : _b.transactFeeWithdraw;
|
|
382
|
+
if (!fee) {
|
|
383
|
+
return { state: 'ERROR', message: 'MISSING FEE' };
|
|
384
|
+
}
|
|
385
|
+
const res = await client.postWithdraw({
|
|
386
|
+
address: order.current_rx_address,
|
|
387
|
+
amount: '' + (order.expected_amount - +fee),
|
|
388
|
+
currency: 'usdt',
|
|
389
|
+
fee: fee,
|
|
390
|
+
chain: 'trc20usdt',
|
|
391
|
+
});
|
|
392
|
+
if (res.status != 'ok') {
|
|
393
|
+
return { state: 'INIT', message: `${res.status}` };
|
|
394
|
+
}
|
|
395
|
+
return { state: 'PENDING', context: `${res.data}` };
|
|
396
|
+
},
|
|
397
|
+
PENDING: async (order) => {
|
|
398
|
+
var _a;
|
|
399
|
+
if (!order.current_tx_context) {
|
|
400
|
+
return { state: 'ERROR', message: 'MISSING CONTEXT' };
|
|
401
|
+
}
|
|
402
|
+
const wdId = +order.current_tx_context;
|
|
403
|
+
const res = await client.getDepositWithdrawHistory({
|
|
404
|
+
currency: 'usdt',
|
|
405
|
+
type: 'withdraw',
|
|
406
|
+
from: `${wdId}`,
|
|
407
|
+
});
|
|
408
|
+
const txId = (_a = res.data.find((v) => v.id === wdId)) === null || _a === void 0 ? void 0 : _a['tx-hash'];
|
|
409
|
+
if (!txId) {
|
|
410
|
+
return { state: 'PENDING', context: `${wdId}` };
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
state: 'COMPLETE',
|
|
414
|
+
transaction_id: txId,
|
|
415
|
+
};
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
onEval: async (order) => {
|
|
419
|
+
const res = await client.getDepositWithdrawHistory({
|
|
420
|
+
currency: 'usdt',
|
|
421
|
+
type: 'deposit',
|
|
422
|
+
direct: 'next',
|
|
423
|
+
});
|
|
424
|
+
const theItem = res.data.find((v) => v['tx-hash'] === order.current_transaction_id && v.state === 'safe');
|
|
425
|
+
if (!theItem) {
|
|
426
|
+
return { state: 'PENDING' };
|
|
427
|
+
}
|
|
428
|
+
return { received_amount: +theItem.amount, state: 'COMPLETE' };
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
addAccountTransferAddress({
|
|
434
|
+
terminal,
|
|
435
|
+
account_id: SPOT_ACCOUNT_ID,
|
|
436
|
+
currency: 'USDT',
|
|
437
|
+
network_id: `Huobi/${huobiUid}/SPOT-SUPER_MARGIN`,
|
|
438
|
+
address: 'SPOT',
|
|
439
|
+
onApply: {
|
|
440
|
+
INIT: async (order) => {
|
|
441
|
+
const transferInResult = await client.postSuperMarginAccountTransferIn({
|
|
442
|
+
currency: 'usdt',
|
|
443
|
+
amount: '' + (order.current_amount || order.expected_amount),
|
|
444
|
+
});
|
|
445
|
+
if (transferInResult.status !== 'ok') {
|
|
446
|
+
return { state: 'INIT' };
|
|
447
|
+
}
|
|
448
|
+
return { state: 'COMPLETE' };
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
onEval: async (order) => {
|
|
452
|
+
return { received_amount: order.current_amount || order.expected_amount, state: 'COMPLETE' };
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
addAccountTransferAddress({
|
|
456
|
+
terminal,
|
|
457
|
+
account_id: SUPER_MARGIN_ACCOUNT_ID,
|
|
458
|
+
currency: 'USDT',
|
|
459
|
+
network_id: `Huobi/${huobiUid}/SPOT-SUPER_MARGIN`,
|
|
460
|
+
address: 'SUPER_MARGIN',
|
|
461
|
+
onApply: {
|
|
462
|
+
INIT: async (order) => {
|
|
463
|
+
const transferOutResult = await client.postSuperMarginAccountTransferOut({
|
|
464
|
+
currency: 'usdt',
|
|
465
|
+
amount: '' + (order.current_amount || order.expected_amount),
|
|
466
|
+
});
|
|
467
|
+
if (transferOutResult.status !== 'ok') {
|
|
468
|
+
return { state: 'INIT' };
|
|
469
|
+
}
|
|
470
|
+
return { state: 'COMPLETE' };
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
onEval: async (order) => {
|
|
474
|
+
return { received_amount: order.current_amount || order.expected_amount, state: 'COMPLETE' };
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
addAccountTransferAddress({
|
|
478
|
+
terminal,
|
|
479
|
+
account_id: SPOT_ACCOUNT_ID,
|
|
480
|
+
currency: 'USDT',
|
|
481
|
+
network_id: `Huobi/${huobiUid}/SPOT-SWAP`,
|
|
482
|
+
address: 'SPOT',
|
|
483
|
+
onApply: {
|
|
484
|
+
INIT: async (order) => {
|
|
485
|
+
const transferResult = await client.postSpotAccountTransfer({
|
|
486
|
+
from: 'spot',
|
|
487
|
+
to: 'linear-swap',
|
|
488
|
+
currency: 'usdt',
|
|
489
|
+
amount: order.current_amount || order.expected_amount,
|
|
490
|
+
'margin-account': 'USDT',
|
|
491
|
+
});
|
|
492
|
+
if (!transferResult.success) {
|
|
493
|
+
return { state: 'INIT' };
|
|
494
|
+
}
|
|
495
|
+
return { state: 'COMPLETE' };
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
onEval: async (order) => {
|
|
499
|
+
return { received_amount: order.current_amount || order.expected_amount, state: 'COMPLETE' };
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
addAccountTransferAddress({
|
|
503
|
+
terminal,
|
|
504
|
+
account_id: SWAP_ACCOUNT_ID,
|
|
505
|
+
currency: 'USDT',
|
|
506
|
+
network_id: `Huobi/${huobiUid}/SPOT-SWAP`,
|
|
507
|
+
address: 'SWAP',
|
|
508
|
+
onApply: {
|
|
509
|
+
INIT: async (order) => {
|
|
510
|
+
const transferResult = await client.postSpotAccountTransfer({
|
|
511
|
+
from: 'linear-swap',
|
|
512
|
+
to: 'spot',
|
|
513
|
+
currency: 'usdt',
|
|
514
|
+
amount: order.current_amount || order.expected_amount,
|
|
515
|
+
'margin-account': 'USDT',
|
|
516
|
+
});
|
|
517
|
+
if (!transferResult.success) {
|
|
518
|
+
return { state: 'INIT' };
|
|
519
|
+
}
|
|
520
|
+
return { state: 'COMPLETE' };
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
onEval: async (order) => {
|
|
524
|
+
return { received_amount: order.current_amount || order.expected_amount, state: 'COMPLETE' };
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
if (isMainAccount) {
|
|
528
|
+
for (const subAccount of subAccounts) {
|
|
529
|
+
const SPOT_SUB_ACCOUNT_ID = `huobi/${subAccount.uid}/spot/usdt`;
|
|
530
|
+
const SUB_ACCOUNT_NETWORK_ID = `Huobi/${huobiUid}/SubAccount/${subAccount.uid}`;
|
|
531
|
+
addAccountTransferAddress({
|
|
532
|
+
terminal,
|
|
533
|
+
account_id: SPOT_ACCOUNT_ID,
|
|
534
|
+
currency: 'USDT',
|
|
535
|
+
network_id: SUB_ACCOUNT_NETWORK_ID,
|
|
536
|
+
address: '#main',
|
|
537
|
+
onApply: {
|
|
538
|
+
INIT: async (order) => {
|
|
539
|
+
const transferResult = await client.postSubUserTransfer({
|
|
540
|
+
'sub-uid': +order.current_rx_address,
|
|
541
|
+
currency: 'usdt',
|
|
542
|
+
amount: order.current_amount || order.expected_amount,
|
|
543
|
+
type: 'master-transfer-out',
|
|
544
|
+
});
|
|
545
|
+
if (transferResult.status !== 'ok') {
|
|
546
|
+
return { state: 'INIT' };
|
|
547
|
+
}
|
|
548
|
+
return { state: 'COMPLETE' };
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
onEval: async (order) => {
|
|
552
|
+
return { received_amount: order.current_amount || order.expected_amount, state: 'COMPLETE' };
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
addAccountTransferAddress({
|
|
556
|
+
terminal,
|
|
557
|
+
account_id: SPOT_SUB_ACCOUNT_ID,
|
|
558
|
+
currency: 'USDT',
|
|
559
|
+
network_id: SUB_ACCOUNT_NETWORK_ID,
|
|
560
|
+
address: `${subAccount.uid}`,
|
|
561
|
+
onApply: {
|
|
562
|
+
INIT: async (order) => {
|
|
563
|
+
const transferResult = await client.postSubUserTransfer({
|
|
564
|
+
'sub-uid': +order.current_tx_address,
|
|
565
|
+
currency: 'usdt',
|
|
566
|
+
amount: order.current_amount || order.expected_amount,
|
|
567
|
+
type: 'master-transfer-in',
|
|
568
|
+
});
|
|
569
|
+
if (transferResult.status !== 'ok') {
|
|
570
|
+
return { state: 'INIT' };
|
|
571
|
+
}
|
|
572
|
+
return { state: 'COMPLETE' };
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
onEval: async (order) => {
|
|
576
|
+
return { received_amount: order.current_amount || order.expected_amount, state: 'COMPLETE' };
|
|
577
|
+
},
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
})();
|
|
582
|
+
//# sourceMappingURL=index.js.map
|