@yuants/vendor-aster 0.0.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.
@@ -0,0 +1,37 @@
1
+ import { provideAccountInfoService } from '@yuants/data-account';
2
+ import { Terminal } from '@yuants/protocol';
3
+ import { getFApiV4Account } from './api';
4
+ const ADDRESS = process.env.ADDRESS;
5
+ export const ACCOUNT_ID = `ASTER/${ADDRESS}`;
6
+ provideAccountInfoService(Terminal.fromNodeEnv(), ACCOUNT_ID, async () => {
7
+ const a = await getFApiV4Account({});
8
+ const profit = +a.totalUnrealizedProfit;
9
+ const balance = +a.totalWalletBalance;
10
+ const equity = balance + profit;
11
+ const free = +a.availableBalance;
12
+ const used = equity - free;
13
+ const money = {
14
+ currency: 'USD',
15
+ equity: equity,
16
+ balance: balance,
17
+ profit: profit,
18
+ free: free,
19
+ used: used,
20
+ };
21
+ const positions = a.positions
22
+ .filter((p) => +p.positionAmt !== 0)
23
+ .map((p) => ({
24
+ position_id: p.symbol,
25
+ product_id: p.symbol,
26
+ datasource_id: 'ASTER',
27
+ direction: p.positionSide === 'BOTH' ? (+p.positionAmt > 0 ? 'LONG' : 'SHORT') : p.positionSide,
28
+ volume: Math.abs(+p.positionAmt),
29
+ free_volume: Math.abs(+p.positionAmt),
30
+ position_price: +p.entryPrice,
31
+ closable_price: Math.abs(+p.notional / +p.positionAmt),
32
+ floating_profit: +p.unrealizedProfit,
33
+ valuation: Math.abs(+p.notional),
34
+ }));
35
+ return { money, positions };
36
+ }, { auto_refresh_interval: 1000 });
37
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAEzC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAQ,CAAC;AACrC,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,OAAO,EAAE,CAAC;AAE7C,yBAAyB,CACvB,QAAQ,CAAC,WAAW,EAAE,EACtB,UAAU,EACV,KAAK,IAAI,EAAE;IACT,MAAM,CAAC,GAAG,MAAM,gBAAgB,CAAC,EAAE,CAAC,CAAC;IAErC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,qBAAqB,CAAC;IACxC,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC;IACtC,MAAM,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAChC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC;IACjC,MAAM,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAE3B,MAAM,KAAK,GAAkB;QAC3B,QAAQ,EAAE,KAAK;QACf,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,IAAI;KACX,CAAC;IACF,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS;SAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC;SACnC,GAAG,CACF,CAAC,CAAC,EAAa,EAAE,CAAC,CAAC;QACjB,WAAW,EAAE,CAAC,CAAC,MAAM;QACrB,UAAU,EAAE,CAAC,CAAC,MAAM;QACpB,aAAa,EAAE,OAAO;QACtB,SAAS,EAAE,CAAC,CAAC,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY;QAC/F,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QAChC,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QACrC,cAAc,EAAE,CAAC,CAAC,CAAC,UAAU;QAC7B,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC;QACtD,eAAe,EAAE,CAAC,CAAC,CAAC,gBAAgB;QACpC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;KACjC,CAAC,CACH,CAAC;IACJ,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAC9B,CAAC,EACD,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAChC,CAAC","sourcesContent":["import { IAccountMoney, IPosition, provideAccountInfoService } from '@yuants/data-account';\nimport { Terminal } from '@yuants/protocol';\nimport { getFApiV4Account } from './api';\n\nconst ADDRESS = process.env.ADDRESS!;\nexport const ACCOUNT_ID = `ASTER/${ADDRESS}`;\n\nprovideAccountInfoService(\n Terminal.fromNodeEnv(),\n ACCOUNT_ID,\n async () => {\n const a = await getFApiV4Account({});\n\n const profit = +a.totalUnrealizedProfit;\n const balance = +a.totalWalletBalance;\n const equity = balance + profit;\n const free = +a.availableBalance;\n const used = equity - free;\n\n const money: IAccountMoney = {\n currency: 'USD',\n equity: equity,\n balance: balance,\n profit: profit,\n free: free,\n used: used,\n };\n const positions = a.positions\n .filter((p) => +p.positionAmt !== 0)\n .map(\n (p): IPosition => ({\n position_id: p.symbol,\n product_id: p.symbol,\n datasource_id: 'ASTER',\n direction: p.positionSide === 'BOTH' ? (+p.positionAmt > 0 ? 'LONG' : 'SHORT') : p.positionSide,\n volume: Math.abs(+p.positionAmt),\n free_volume: Math.abs(+p.positionAmt),\n position_price: +p.entryPrice,\n closable_price: Math.abs(+p.notional / +p.positionAmt),\n floating_profit: +p.unrealizedProfit,\n valuation: Math.abs(+p.notional),\n }),\n );\n return { money, positions };\n },\n { auto_refresh_interval: 1000 },\n);\n"]}
package/dist/api.js ADDED
@@ -0,0 +1,65 @@
1
+ const API_KEY = process.env.API_KEY;
2
+ const SECRET_KEY = process.env.SECRET_KEY;
3
+ const BASE_URL = 'https://fapi.asterdex.com';
4
+ function arrayBufferToHex(buffer) {
5
+ const bytes = new Uint8Array(buffer);
6
+ const hexArray = [];
7
+ for (const byte of bytes) {
8
+ const hex = byte.toString(16).padStart(2, '0');
9
+ hexArray.push(hex);
10
+ }
11
+ return hexArray.join('');
12
+ }
13
+ async function opensslEquivalentHMAC(message, secretKey) {
14
+ try {
15
+ const keyBuffer = new TextEncoder().encode(secretKey);
16
+ const messageBuffer = new TextEncoder().encode(message);
17
+ // 导入密钥
18
+ const cryptoKey = await crypto.subtle.importKey('raw', keyBuffer, {
19
+ name: 'HMAC',
20
+ hash: { name: 'SHA-256' },
21
+ }, false, ['sign']);
22
+ // 进行 HMAC-SHA256 签名
23
+ const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageBuffer);
24
+ // 转换为16进制小写(与 openssl 输出格式一致)
25
+ return arrayBufferToHex(signature);
26
+ }
27
+ catch (error) {
28
+ throw new Error(`HMAC-SHA256 签名失败: ${error}`);
29
+ }
30
+ }
31
+ const request = async (type, method, endpoint, params = {}) => {
32
+ const needApiKey = type !== 'NONE';
33
+ const needSign = type === 'TRADE' || type === 'USER_DATA';
34
+ const url = new URL(BASE_URL);
35
+ url.pathname = endpoint;
36
+ for (const [key, value] of Object.entries(params)) {
37
+ if (value === undefined)
38
+ continue;
39
+ url.searchParams.set(key, `${value}`);
40
+ }
41
+ if (needSign) {
42
+ url.searchParams.set('timestamp', `${Date.now()}`);
43
+ const msg = url.search.slice(1); // 去掉开头的 '?'
44
+ const signature = await opensslEquivalentHMAC(msg, SECRET_KEY);
45
+ url.searchParams.set('signature', signature);
46
+ }
47
+ console.info(url.toString());
48
+ return fetch(url.toString(), {
49
+ method,
50
+ headers: needApiKey
51
+ ? {
52
+ 'X-MBX-APIKEY': API_KEY,
53
+ }
54
+ : {},
55
+ }).then((response) => response.json());
56
+ };
57
+ const createApi = (type, method, endpoint) => (params) => request(type, method, endpoint, params);
58
+ /**
59
+ * 获取账户信息
60
+ *
61
+ * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E8%B4%A6%E6%88%B7%E4%BF%A1%E6%81%AFv4-user_data
62
+ */
63
+ export const getFApiV4Account = createApi('USER_DATA', 'GET', '/fapi/v4/account');
64
+ export const postFApiV1Order = createApi('TRADE', 'POST', '/fapi/v1/order');
65
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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","sourcesContent":["const API_KEY = process.env.API_KEY!;\nconst SECRET_KEY = process.env.SECRET_KEY!;\n\nconst BASE_URL = 'https://fapi.asterdex.com';\n\nfunction arrayBufferToHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n const hexArray: string[] = [];\n\n for (const byte of bytes) {\n const hex = byte.toString(16).padStart(2, '0');\n hexArray.push(hex);\n }\n\n return hexArray.join('');\n}\n\nasync function opensslEquivalentHMAC(message: string, secretKey: string): Promise<string> {\n try {\n const keyBuffer = new TextEncoder().encode(secretKey);\n const messageBuffer = new TextEncoder().encode(message);\n\n // 导入密钥\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n keyBuffer,\n {\n name: 'HMAC',\n hash: { name: 'SHA-256' },\n },\n false,\n ['sign'],\n );\n\n // 进行 HMAC-SHA256 签名\n const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageBuffer);\n\n // 转换为16进制小写(与 openssl 输出格式一致)\n return arrayBufferToHex(signature);\n } catch (error) {\n throw new Error(`HMAC-SHA256 签名失败: ${error}`);\n }\n}\n\nconst request = async <T>(\n type: 'NONE' | 'TRADE' | 'USER_DATA' | 'USER_STREAM' | 'MARKET_DATA',\n method: string,\n endpoint: string,\n params: any = {},\n): Promise<T> => {\n const needApiKey = type !== 'NONE';\n const needSign = type === 'TRADE' || type === 'USER_DATA';\n\n const url = new URL(BASE_URL);\n url.pathname = endpoint;\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined) continue;\n url.searchParams.set(key, `${value}`);\n }\n\n if (needSign) {\n url.searchParams.set('timestamp', `${Date.now()}`);\n const msg = url.search.slice(1); // 去掉开头的 '?'\n const signature = await opensslEquivalentHMAC(msg, SECRET_KEY);\n url.searchParams.set('signature', signature);\n }\n\n console.info(url.toString());\n return fetch(url.toString(), {\n method,\n headers: needApiKey\n ? {\n 'X-MBX-APIKEY': API_KEY,\n }\n : {},\n }).then((response) => response.json() as any as T);\n};\n\nconst createApi =\n <TReq, TRes>(\n type: 'NONE' | 'TRADE' | 'USER_DATA' | 'USER_STREAM' | 'MARKET_DATA',\n method: string,\n endpoint: string,\n ) =>\n (params: TReq) =>\n request<TRes>(type, method, endpoint, params);\n\n/**\n * 获取账户信息\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E8%B4%A6%E6%88%B7%E4%BF%A1%E6%81%AFv4-user_data\n */\nexport const getFApiV4Account = createApi<\n {},\n {\n feeTier: number;\n canTrade: boolean;\n canDeposit: boolean;\n canWithdraw: boolean;\n updateTime: number;\n totalInitialMargin: string;\n totalMaintMargin: string;\n totalWalletBalance: string;\n totalUnrealizedProfit: string;\n totalMarginBalance: string;\n totalPositionInitialMargin: string;\n totalOpenOrderInitialMargin: string;\n totalCrossWalletBalance: string;\n totalCrossUnPnl: string;\n availableBalance: string;\n maxWithdrawAmount: string;\n assets: {\n asset: string;\n walletBalance: string;\n unrealizedProfit: string;\n marginBalance: string;\n maintMargin: string;\n initialMargin: string;\n positionInitialMargin: string;\n openOrderInitialMargin: string;\n maxWithdrawAmount: string;\n crossWalletBalance: string;\n crossUnPnl: string;\n availableBalance: string;\n marginAvailable: boolean;\n updateTime: number;\n }[];\n positions: {\n symbol: string;\n initialMargin: string;\n maintMargin: string;\n unrealizedProfit: string;\n positionInitialMargin: string;\n openOrderInitialMargin: string;\n leverage: string;\n isolated: boolean;\n entryPrice: string;\n maxNotional: string;\n positionSide: 'BOTH' | 'LONG' | 'SHORT';\n positionAmt: string;\n notional: string;\n isolatedWallet: string;\n updateTime: number;\n }[];\n }\n>('USER_DATA', 'GET', '/fapi/v4/account');\n\nexport const postFApiV1Order = createApi<\n {\n symbol: string;\n side: 'BUY' | 'SELL';\n positionSide?: 'BOTH' | 'LONG' | 'SHORT';\n type:\n | 'MARKET'\n | 'LIMIT'\n | 'STOP'\n | 'STOP_MARKET'\n | 'TAKE_PROFIT'\n | 'TAKE_PROFIT_MARKET'\n | 'TRAILING_STOP_MARKET';\n reduceOnly?: 'true' | 'false';\n quantity?: number;\n price?: number;\n timeInForce?: 'GTC' | 'IOC' | 'FOK' | 'GTX' | 'HIDDEN';\n },\n {}\n>('TRADE', 'POST', '/fapi/v1/order');\n"]}
package/dist/cli.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import './index';
3
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,SAAS,CAAC","sourcesContent":["#!/usr/bin/env node\nimport './index';\n"]}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import './account';
2
+ import './order';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAC;AACnB,OAAO,SAAS,CAAC","sourcesContent":["import './account';\nimport './order';\n"]}
package/dist/order.js ADDED
@@ -0,0 +1,27 @@
1
+ import { Terminal } from '@yuants/protocol';
2
+ import { ACCOUNT_ID } from './account';
3
+ import { postFApiV1Order } from './api';
4
+ const terminal = Terminal.fromNodeEnv();
5
+ terminal.server.provideService('SubmitOrder', { required: ['account_id'], properties: { account_id: { type: 'string', const: ACCOUNT_ID } } }, async (msg) => {
6
+ const order = msg.req;
7
+ const symbol = order.product_id;
8
+ const side = { OPEN_LONG: 'BUY', OPEN_SHORT: 'SELL', CLOSE_LONG: 'SELL', CLOSE_SHORT: 'BUY' }[order.order_direction];
9
+ if (!side)
10
+ throw new Error(`Unsupported order_direction: ${order.order_direction}`);
11
+ const type = { MARKET: 'MARKET', LIMIT: 'LIMIT', MAKER: 'LIMIT' }[order.order_type];
12
+ if (!type)
13
+ throw new Error(`Unsupported order_type: ${order.order_type}`);
14
+ const quantity = order.volume;
15
+ const price = order.price;
16
+ const timeInForce = order.order_type === 'MAKER' ? 'GTX' : order.order_type === 'LIMIT' ? 'GTC' : undefined;
17
+ await postFApiV1Order({
18
+ symbol,
19
+ side,
20
+ type,
21
+ quantity,
22
+ price,
23
+ timeInForce,
24
+ });
25
+ return { res: { code: 0, message: 'OK' } };
26
+ });
27
+ //# sourceMappingURL=order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order.js","sourceRoot":"","sources":["../src/order.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAExC,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;AAExC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAC5B,aAAa,EACb,EAAE,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,UAAU,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,EAC/F,KAAK,EAAE,GAAG,EAAE,EAAE;IACZ,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC;IAEtB,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC;IAEhC,MAAM,IAAI,GAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAY,CACtG,KAAK,CAAC,eAAgB,CACvB,CAAC;IACF,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC;IAEpF,MAAM,IAAI,GAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAY,CAAC,KAAK,CAAC,UAAW,CAAC,CAAC;IAChG,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;IAE1E,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC;IAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAE1B,MAAM,WAAW,GACf,KAAK,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAE1F,MAAM,eAAe,CAAC;QACpB,MAAM;QACN,IAAI;QACJ,IAAI;QACJ,QAAQ;QACR,KAAK;QACL,WAAW;KACZ,CAAC,CAAC;IAEH,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;AAC7C,CAAC,CACF,CAAC","sourcesContent":["import { Terminal } from '@yuants/protocol';\nimport { IOrder } from '@yuants/data-order';\nimport { ACCOUNT_ID } from './account';\nimport { postFApiV1Order } from './api';\n\nconst terminal = Terminal.fromNodeEnv();\n\nterminal.server.provideService<IOrder>(\n 'SubmitOrder',\n { required: ['account_id'], properties: { account_id: { type: 'string', const: ACCOUNT_ID } } },\n async (msg) => {\n const order = msg.req;\n\n const symbol = order.product_id;\n\n const side = ({ OPEN_LONG: 'BUY', OPEN_SHORT: 'SELL', CLOSE_LONG: 'SELL', CLOSE_SHORT: 'BUY' } as const)[\n order.order_direction!\n ];\n if (!side) throw new Error(`Unsupported order_direction: ${order.order_direction}`);\n\n const type = ({ MARKET: 'MARKET', LIMIT: 'LIMIT', MAKER: 'LIMIT' } as const)[order.order_type!];\n if (!type) throw new Error(`Unsupported order_type: ${order.order_type}`);\n\n const quantity = order.volume;\n const price = order.price;\n\n const timeInForce =\n order.order_type === 'MAKER' ? 'GTX' : order.order_type === 'LIMIT' ? 'GTC' : undefined;\n\n await postFApiV1Order({\n symbol,\n side,\n type,\n quantity,\n price,\n timeInForce,\n });\n\n return { res: { code: 0, message: 'OK' } };\n },\n);\n"]}
@@ -0,0 +1,2 @@
1
+ export declare const ACCOUNT_ID: string;
2
+ //# sourceMappingURL=account.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,UAAU,QAAqB,CAAC"}
package/lib/account.js ADDED
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ACCOUNT_ID = void 0;
4
+ const data_account_1 = require("@yuants/data-account");
5
+ const protocol_1 = require("@yuants/protocol");
6
+ const api_1 = require("./api");
7
+ const ADDRESS = process.env.ADDRESS;
8
+ exports.ACCOUNT_ID = `ASTER/${ADDRESS}`;
9
+ (0, data_account_1.provideAccountInfoService)(protocol_1.Terminal.fromNodeEnv(), exports.ACCOUNT_ID, async () => {
10
+ const a = await (0, api_1.getFApiV4Account)({});
11
+ const profit = +a.totalUnrealizedProfit;
12
+ const balance = +a.totalWalletBalance;
13
+ const equity = balance + profit;
14
+ const free = +a.availableBalance;
15
+ const used = equity - free;
16
+ const money = {
17
+ currency: 'USD',
18
+ equity: equity,
19
+ balance: balance,
20
+ profit: profit,
21
+ free: free,
22
+ used: used,
23
+ };
24
+ const positions = a.positions
25
+ .filter((p) => +p.positionAmt !== 0)
26
+ .map((p) => ({
27
+ position_id: p.symbol,
28
+ product_id: p.symbol,
29
+ datasource_id: 'ASTER',
30
+ direction: p.positionSide === 'BOTH' ? (+p.positionAmt > 0 ? 'LONG' : 'SHORT') : p.positionSide,
31
+ volume: Math.abs(+p.positionAmt),
32
+ free_volume: Math.abs(+p.positionAmt),
33
+ position_price: +p.entryPrice,
34
+ closable_price: Math.abs(+p.notional / +p.positionAmt),
35
+ floating_profit: +p.unrealizedProfit,
36
+ valuation: Math.abs(+p.notional),
37
+ }));
38
+ return { money, positions };
39
+ }, { auto_refresh_interval: 1000 });
40
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":";;;AAAA,uDAA2F;AAC3F,+CAA4C;AAC5C,+BAAyC;AAEzC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAQ,CAAC;AACxB,QAAA,UAAU,GAAG,SAAS,OAAO,EAAE,CAAC;AAE7C,IAAA,wCAAyB,EACvB,mBAAQ,CAAC,WAAW,EAAE,EACtB,kBAAU,EACV,KAAK,IAAI,EAAE;IACT,MAAM,CAAC,GAAG,MAAM,IAAA,sBAAgB,EAAC,EAAE,CAAC,CAAC;IAErC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,qBAAqB,CAAC;IACxC,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC;IACtC,MAAM,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAChC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC;IACjC,MAAM,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAE3B,MAAM,KAAK,GAAkB;QAC3B,QAAQ,EAAE,KAAK;QACf,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,IAAI;KACX,CAAC;IACF,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS;SAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC;SACnC,GAAG,CACF,CAAC,CAAC,EAAa,EAAE,CAAC,CAAC;QACjB,WAAW,EAAE,CAAC,CAAC,MAAM;QACrB,UAAU,EAAE,CAAC,CAAC,MAAM;QACpB,aAAa,EAAE,OAAO;QACtB,SAAS,EAAE,CAAC,CAAC,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY;QAC/F,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QAChC,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QACrC,cAAc,EAAE,CAAC,CAAC,CAAC,UAAU;QAC7B,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC;QACtD,eAAe,EAAE,CAAC,CAAC,CAAC,gBAAgB;QACpC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;KACjC,CAAC,CACH,CAAC;IACJ,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAC9B,CAAC,EACD,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAChC,CAAC","sourcesContent":["import { IAccountMoney, IPosition, provideAccountInfoService } from '@yuants/data-account';\nimport { Terminal } from '@yuants/protocol';\nimport { getFApiV4Account } from './api';\n\nconst ADDRESS = process.env.ADDRESS!;\nexport const ACCOUNT_ID = `ASTER/${ADDRESS}`;\n\nprovideAccountInfoService(\n Terminal.fromNodeEnv(),\n ACCOUNT_ID,\n async () => {\n const a = await getFApiV4Account({});\n\n const profit = +a.totalUnrealizedProfit;\n const balance = +a.totalWalletBalance;\n const equity = balance + profit;\n const free = +a.availableBalance;\n const used = equity - free;\n\n const money: IAccountMoney = {\n currency: 'USD',\n equity: equity,\n balance: balance,\n profit: profit,\n free: free,\n used: used,\n };\n const positions = a.positions\n .filter((p) => +p.positionAmt !== 0)\n .map(\n (p): IPosition => ({\n position_id: p.symbol,\n product_id: p.symbol,\n datasource_id: 'ASTER',\n direction: p.positionSide === 'BOTH' ? (+p.positionAmt > 0 ? 'LONG' : 'SHORT') : p.positionSide,\n volume: Math.abs(+p.positionAmt),\n free_volume: Math.abs(+p.positionAmt),\n position_price: +p.entryPrice,\n closable_price: Math.abs(+p.notional / +p.positionAmt),\n floating_profit: +p.unrealizedProfit,\n valuation: Math.abs(+p.notional),\n }),\n );\n return { money, positions };\n },\n { auto_refresh_interval: 1000 },\n);\n"]}
package/lib/api.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * 获取账户信息
3
+ *
4
+ * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E8%B4%A6%E6%88%B7%E4%BF%A1%E6%81%AFv4-user_data
5
+ */
6
+ export declare const getFApiV4Account: (params: {}) => Promise<{
7
+ feeTier: number;
8
+ canTrade: boolean;
9
+ canDeposit: boolean;
10
+ canWithdraw: boolean;
11
+ updateTime: number;
12
+ totalInitialMargin: string;
13
+ totalMaintMargin: string;
14
+ totalWalletBalance: string;
15
+ totalUnrealizedProfit: string;
16
+ totalMarginBalance: string;
17
+ totalPositionInitialMargin: string;
18
+ totalOpenOrderInitialMargin: string;
19
+ totalCrossWalletBalance: string;
20
+ totalCrossUnPnl: string;
21
+ availableBalance: string;
22
+ maxWithdrawAmount: string;
23
+ assets: {
24
+ asset: string;
25
+ walletBalance: string;
26
+ unrealizedProfit: string;
27
+ marginBalance: string;
28
+ maintMargin: string;
29
+ initialMargin: string;
30
+ positionInitialMargin: string;
31
+ openOrderInitialMargin: string;
32
+ maxWithdrawAmount: string;
33
+ crossWalletBalance: string;
34
+ crossUnPnl: string;
35
+ availableBalance: string;
36
+ marginAvailable: boolean;
37
+ updateTime: number;
38
+ }[];
39
+ positions: {
40
+ symbol: string;
41
+ initialMargin: string;
42
+ maintMargin: string;
43
+ unrealizedProfit: string;
44
+ positionInitialMargin: string;
45
+ openOrderInitialMargin: string;
46
+ leverage: string;
47
+ isolated: boolean;
48
+ entryPrice: string;
49
+ maxNotional: string;
50
+ positionSide: 'BOTH' | 'LONG' | 'SHORT';
51
+ positionAmt: string;
52
+ notional: string;
53
+ isolatedWallet: string;
54
+ updateTime: number;
55
+ }[];
56
+ }>;
57
+ export declare const postFApiV1Order: (params: {
58
+ symbol: string;
59
+ side: 'BUY' | 'SELL';
60
+ positionSide?: "BOTH" | "LONG" | "SHORT" | undefined;
61
+ type: 'MARKET' | 'LIMIT' | 'STOP' | 'STOP_MARKET' | 'TAKE_PROFIT' | 'TAKE_PROFIT_MARKET' | 'TRAILING_STOP_MARKET';
62
+ reduceOnly?: "true" | "false" | undefined;
63
+ quantity?: number | undefined;
64
+ price?: number | undefined;
65
+ timeInForce?: "GTC" | "IOC" | "FOK" | "GTX" | "HIDDEN" | undefined;
66
+ }) => Promise<{}>;
67
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAuFA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;aAGhB,MAAM;cACL,OAAO;gBACL,OAAO;iBACN,OAAO;gBACR,MAAM;wBACE,MAAM;sBACR,MAAM;wBACJ,MAAM;2BACH,MAAM;wBACT,MAAM;gCACE,MAAM;iCACL,MAAM;6BACV,MAAM;qBACd,MAAM;sBACL,MAAM;uBACL,MAAM;YACjB;QACN,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,gBAAgB,EAAE,MAAM,CAAC;QACzB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,sBAAsB,EAAE,MAAM,CAAC;QAC/B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,eAAe,EAAE,OAAO,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;KACpB,EAAE;eACQ;QACT,MAAM,EAAE,MAAM,CAAC;QACf,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,gBAAgB,EAAE,MAAM,CAAC;QACzB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,sBAAsB,EAAE,MAAM,CAAC;QAC/B,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,OAAO,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;QACxC,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;KACpB,EAAE;EAEkC,CAAC;AAE1C,eAAO,MAAM,eAAe;YAEhB,MAAM;UACR,KAAK,GAAG,MAAM;;UAGhB,QAAQ,GACR,OAAO,GACP,MAAM,GACN,aAAa,GACb,aAAa,GACb,oBAAoB,GACpB,sBAAsB;;;;;iBAOM,CAAC"}
package/lib/api.js ADDED
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.postFApiV1Order = exports.getFApiV4Account = void 0;
4
+ const API_KEY = process.env.API_KEY;
5
+ const SECRET_KEY = process.env.SECRET_KEY;
6
+ const BASE_URL = 'https://fapi.asterdex.com';
7
+ function arrayBufferToHex(buffer) {
8
+ const bytes = new Uint8Array(buffer);
9
+ const hexArray = [];
10
+ for (const byte of bytes) {
11
+ const hex = byte.toString(16).padStart(2, '0');
12
+ hexArray.push(hex);
13
+ }
14
+ return hexArray.join('');
15
+ }
16
+ async function opensslEquivalentHMAC(message, secretKey) {
17
+ try {
18
+ const keyBuffer = new TextEncoder().encode(secretKey);
19
+ const messageBuffer = new TextEncoder().encode(message);
20
+ // 导入密钥
21
+ const cryptoKey = await crypto.subtle.importKey('raw', keyBuffer, {
22
+ name: 'HMAC',
23
+ hash: { name: 'SHA-256' },
24
+ }, false, ['sign']);
25
+ // 进行 HMAC-SHA256 签名
26
+ const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageBuffer);
27
+ // 转换为16进制小写(与 openssl 输出格式一致)
28
+ return arrayBufferToHex(signature);
29
+ }
30
+ catch (error) {
31
+ throw new Error(`HMAC-SHA256 签名失败: ${error}`);
32
+ }
33
+ }
34
+ const request = async (type, method, endpoint, params = {}) => {
35
+ const needApiKey = type !== 'NONE';
36
+ const needSign = type === 'TRADE' || type === 'USER_DATA';
37
+ const url = new URL(BASE_URL);
38
+ url.pathname = endpoint;
39
+ for (const [key, value] of Object.entries(params)) {
40
+ if (value === undefined)
41
+ continue;
42
+ url.searchParams.set(key, `${value}`);
43
+ }
44
+ if (needSign) {
45
+ url.searchParams.set('timestamp', `${Date.now()}`);
46
+ const msg = url.search.slice(1); // 去掉开头的 '?'
47
+ const signature = await opensslEquivalentHMAC(msg, SECRET_KEY);
48
+ url.searchParams.set('signature', signature);
49
+ }
50
+ console.info(url.toString());
51
+ return fetch(url.toString(), {
52
+ method,
53
+ headers: needApiKey
54
+ ? {
55
+ 'X-MBX-APIKEY': API_KEY,
56
+ }
57
+ : {},
58
+ }).then((response) => response.json());
59
+ };
60
+ const createApi = (type, method, endpoint) => (params) => request(type, method, endpoint, params);
61
+ /**
62
+ * 获取账户信息
63
+ *
64
+ * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E8%B4%A6%E6%88%B7%E4%BF%A1%E6%81%AFv4-user_data
65
+ */
66
+ exports.getFApiV4Account = createApi('USER_DATA', 'GET', '/fapi/v4/account');
67
+ exports.postFApiV1Order = createApi('TRADE', 'POST', '/fapi/v1/order');
68
+ //# sourceMappingURL=api.js.map
package/lib/api.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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","sourcesContent":["const API_KEY = process.env.API_KEY!;\nconst SECRET_KEY = process.env.SECRET_KEY!;\n\nconst BASE_URL = 'https://fapi.asterdex.com';\n\nfunction arrayBufferToHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n const hexArray: string[] = [];\n\n for (const byte of bytes) {\n const hex = byte.toString(16).padStart(2, '0');\n hexArray.push(hex);\n }\n\n return hexArray.join('');\n}\n\nasync function opensslEquivalentHMAC(message: string, secretKey: string): Promise<string> {\n try {\n const keyBuffer = new TextEncoder().encode(secretKey);\n const messageBuffer = new TextEncoder().encode(message);\n\n // 导入密钥\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n keyBuffer,\n {\n name: 'HMAC',\n hash: { name: 'SHA-256' },\n },\n false,\n ['sign'],\n );\n\n // 进行 HMAC-SHA256 签名\n const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageBuffer);\n\n // 转换为16进制小写(与 openssl 输出格式一致)\n return arrayBufferToHex(signature);\n } catch (error) {\n throw new Error(`HMAC-SHA256 签名失败: ${error}`);\n }\n}\n\nconst request = async <T>(\n type: 'NONE' | 'TRADE' | 'USER_DATA' | 'USER_STREAM' | 'MARKET_DATA',\n method: string,\n endpoint: string,\n params: any = {},\n): Promise<T> => {\n const needApiKey = type !== 'NONE';\n const needSign = type === 'TRADE' || type === 'USER_DATA';\n\n const url = new URL(BASE_URL);\n url.pathname = endpoint;\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined) continue;\n url.searchParams.set(key, `${value}`);\n }\n\n if (needSign) {\n url.searchParams.set('timestamp', `${Date.now()}`);\n const msg = url.search.slice(1); // 去掉开头的 '?'\n const signature = await opensslEquivalentHMAC(msg, SECRET_KEY);\n url.searchParams.set('signature', signature);\n }\n\n console.info(url.toString());\n return fetch(url.toString(), {\n method,\n headers: needApiKey\n ? {\n 'X-MBX-APIKEY': API_KEY,\n }\n : {},\n }).then((response) => response.json() as any as T);\n};\n\nconst createApi =\n <TReq, TRes>(\n type: 'NONE' | 'TRADE' | 'USER_DATA' | 'USER_STREAM' | 'MARKET_DATA',\n method: string,\n endpoint: string,\n ) =>\n (params: TReq) =>\n request<TRes>(type, method, endpoint, params);\n\n/**\n * 获取账户信息\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E8%B4%A6%E6%88%B7%E4%BF%A1%E6%81%AFv4-user_data\n */\nexport const getFApiV4Account = createApi<\n {},\n {\n feeTier: number;\n canTrade: boolean;\n canDeposit: boolean;\n canWithdraw: boolean;\n updateTime: number;\n totalInitialMargin: string;\n totalMaintMargin: string;\n totalWalletBalance: string;\n totalUnrealizedProfit: string;\n totalMarginBalance: string;\n totalPositionInitialMargin: string;\n totalOpenOrderInitialMargin: string;\n totalCrossWalletBalance: string;\n totalCrossUnPnl: string;\n availableBalance: string;\n maxWithdrawAmount: string;\n assets: {\n asset: string;\n walletBalance: string;\n unrealizedProfit: string;\n marginBalance: string;\n maintMargin: string;\n initialMargin: string;\n positionInitialMargin: string;\n openOrderInitialMargin: string;\n maxWithdrawAmount: string;\n crossWalletBalance: string;\n crossUnPnl: string;\n availableBalance: string;\n marginAvailable: boolean;\n updateTime: number;\n }[];\n positions: {\n symbol: string;\n initialMargin: string;\n maintMargin: string;\n unrealizedProfit: string;\n positionInitialMargin: string;\n openOrderInitialMargin: string;\n leverage: string;\n isolated: boolean;\n entryPrice: string;\n maxNotional: string;\n positionSide: 'BOTH' | 'LONG' | 'SHORT';\n positionAmt: string;\n notional: string;\n isolatedWallet: string;\n updateTime: number;\n }[];\n }\n>('USER_DATA', 'GET', '/fapi/v4/account');\n\nexport const postFApiV1Order = createApi<\n {\n symbol: string;\n side: 'BUY' | 'SELL';\n positionSide?: 'BOTH' | 'LONG' | 'SHORT';\n type:\n | 'MARKET'\n | 'LIMIT'\n | 'STOP'\n | 'STOP_MARKET'\n | 'TAKE_PROFIT'\n | 'TAKE_PROFIT_MARKET'\n | 'TRAILING_STOP_MARKET';\n reduceOnly?: 'true' | 'false';\n quantity?: number;\n price?: number;\n timeInForce?: 'GTC' | 'IOC' | 'FOK' | 'GTX' | 'HIDDEN';\n },\n {}\n>('TRADE', 'POST', '/fapi/v1/order');\n"]}
package/lib/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import './index';
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,SAAS,CAAC"}
package/lib/cli.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ require("./index");
5
+ //# sourceMappingURL=cli.js.map
package/lib/cli.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;AACA,mBAAiB","sourcesContent":["#!/usr/bin/env node\nimport './index';\n"]}
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import './account';
2
+ import './order';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAC;AACnB,OAAO,SAAS,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./account");
4
+ require("./order");
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,qBAAmB;AACnB,mBAAiB","sourcesContent":["import './account';\nimport './order';\n"]}
package/lib/order.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=order.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order.d.ts","sourceRoot":"","sources":["../src/order.ts"],"names":[],"mappings":""}
package/lib/order.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const protocol_1 = require("@yuants/protocol");
4
+ const account_1 = require("./account");
5
+ const api_1 = require("./api");
6
+ const terminal = protocol_1.Terminal.fromNodeEnv();
7
+ terminal.server.provideService('SubmitOrder', { required: ['account_id'], properties: { account_id: { type: 'string', const: account_1.ACCOUNT_ID } } }, async (msg) => {
8
+ const order = msg.req;
9
+ const symbol = order.product_id;
10
+ const side = { OPEN_LONG: 'BUY', OPEN_SHORT: 'SELL', CLOSE_LONG: 'SELL', CLOSE_SHORT: 'BUY' }[order.order_direction];
11
+ if (!side)
12
+ throw new Error(`Unsupported order_direction: ${order.order_direction}`);
13
+ const type = { MARKET: 'MARKET', LIMIT: 'LIMIT', MAKER: 'LIMIT' }[order.order_type];
14
+ if (!type)
15
+ throw new Error(`Unsupported order_type: ${order.order_type}`);
16
+ const quantity = order.volume;
17
+ const price = order.price;
18
+ const timeInForce = order.order_type === 'MAKER' ? 'GTX' : order.order_type === 'LIMIT' ? 'GTC' : undefined;
19
+ await (0, api_1.postFApiV1Order)({
20
+ symbol,
21
+ side,
22
+ type,
23
+ quantity,
24
+ price,
25
+ timeInForce,
26
+ });
27
+ return { res: { code: 0, message: 'OK' } };
28
+ });
29
+ //# sourceMappingURL=order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order.js","sourceRoot":"","sources":["../src/order.ts"],"names":[],"mappings":";;AAAA,+CAA4C;AAE5C,uCAAuC;AACvC,+BAAwC;AAExC,MAAM,QAAQ,GAAG,mBAAQ,CAAC,WAAW,EAAE,CAAC;AAExC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAC5B,aAAa,EACb,EAAE,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,UAAU,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,oBAAU,EAAE,EAAE,EAAE,EAC/F,KAAK,EAAE,GAAG,EAAE,EAAE;IACZ,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC;IAEtB,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC;IAEhC,MAAM,IAAI,GAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAY,CACtG,KAAK,CAAC,eAAgB,CACvB,CAAC;IACF,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC;IAEpF,MAAM,IAAI,GAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAY,CAAC,KAAK,CAAC,UAAW,CAAC,CAAC;IAChG,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;IAE1E,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC;IAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAE1B,MAAM,WAAW,GACf,KAAK,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAE1F,MAAM,IAAA,qBAAe,EAAC;QACpB,MAAM;QACN,IAAI;QACJ,IAAI;QACJ,QAAQ;QACR,KAAK;QACL,WAAW;KACZ,CAAC,CAAC;IAEH,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;AAC7C,CAAC,CACF,CAAC","sourcesContent":["import { Terminal } from '@yuants/protocol';\nimport { IOrder } from '@yuants/data-order';\nimport { ACCOUNT_ID } from './account';\nimport { postFApiV1Order } from './api';\n\nconst terminal = Terminal.fromNodeEnv();\n\nterminal.server.provideService<IOrder>(\n 'SubmitOrder',\n { required: ['account_id'], properties: { account_id: { type: 'string', const: ACCOUNT_ID } } },\n async (msg) => {\n const order = msg.req;\n\n const symbol = order.product_id;\n\n const side = ({ OPEN_LONG: 'BUY', OPEN_SHORT: 'SELL', CLOSE_LONG: 'SELL', CLOSE_SHORT: 'BUY' } as const)[\n order.order_direction!\n ];\n if (!side) throw new Error(`Unsupported order_direction: ${order.order_direction}`);\n\n const type = ({ MARKET: 'MARKET', LIMIT: 'LIMIT', MAKER: 'LIMIT' } as const)[order.order_type!];\n if (!type) throw new Error(`Unsupported order_type: ${order.order_type}`);\n\n const quantity = order.volume;\n const price = order.price;\n\n const timeInForce =\n order.order_type === 'MAKER' ? 'GTX' : order.order_type === 'LIMIT' ? 'GTC' : undefined;\n\n await postFApiV1Order({\n symbol,\n side,\n type,\n quantity,\n price,\n timeInForce,\n });\n\n return { res: { code: 0, message: 'OK' } };\n },\n);\n"]}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@yuants/vendor-aster",
3
+ "version": "0.0.0",
4
+ "bin": "lib/cli.js",
5
+ "main": "lib/index.js",
6
+ "files": [
7
+ "dist",
8
+ "lib",
9
+ "temp"
10
+ ],
11
+ "dependencies": {
12
+ "@yuants/protocol": "0.43.0",
13
+ "@yuants/data-account": "0.4.4",
14
+ "@yuants/utils": "0.8.0",
15
+ "@yuants/data-series": "0.3.27",
16
+ "@yuants/sql": "0.9.6",
17
+ "@yuants/data-product": "0.2.16",
18
+ "@yuants/data-ohlc": "0.3.23",
19
+ "@yuants/data-order": "0.2.12",
20
+ "@yuants/data-interest-rate": "0.1.24",
21
+ "@yuants/transfer": "0.2.15",
22
+ "rxjs": "~7.5.6"
23
+ },
24
+ "devDependencies": {
25
+ "@microsoft/api-extractor": "~7.30.0",
26
+ "@rushstack/heft": "~0.47.5",
27
+ "@rushstack/heft-jest-plugin": "~0.16.8",
28
+ "@rushstack/heft-node-rig": "~1.10.7",
29
+ "@types/heft-jest": "1.0.3",
30
+ "@types/node": "22",
31
+ "@yuants/extension": "0.2.24",
32
+ "@yuants/tool-kit": "0.2.0",
33
+ "typescript": "~4.7.4",
34
+ "ts-node": "~10.9.2"
35
+ },
36
+ "publishConfig": {
37
+ "registry": "https://registry.npmjs.org",
38
+ "access": "public"
39
+ },
40
+ "io_ntnl": {
41
+ "deploy_files": [
42
+ "dist",
43
+ "lib",
44
+ "temp"
45
+ ]
46
+ },
47
+ "scripts": {
48
+ "dev": "ts-node src/index.ts",
49
+ "build": "heft test --clean && yuan-toolkit post-build"
50
+ }
51
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "apps/vendor-aster/config/jest.config.json": "4bb17bde3ee911163a3edb36a6eb71491d80b1bd",
3
+ "apps/vendor-aster/config/rig.json": "f6c7b5537dc77a3170ba9f008bae3b6c3ee11956",
4
+ "apps/vendor-aster/config/typescript.json": "854907e8a821f2050f6533368db160c649c25348",
5
+ "apps/vendor-aster/package.json": "6cb41e70897cb2351448580da7199b02d4b64832",
6
+ "apps/vendor-aster/src/account.ts": "dfef4baeb4a7f28726cd789e5a2e481e97d3a905",
7
+ "apps/vendor-aster/src/api.ts": "b50b5e0f4a2679eeb1aa53c8109ddced464a6293",
8
+ "apps/vendor-aster/src/cli.ts": "9bf6b5559a6c6f33da20e74cc6c5d702c60ec891",
9
+ "apps/vendor-aster/src/index.ts": "dc5f7588eba080e7098c267fd1218575bb2eb239",
10
+ "apps/vendor-aster/src/order.ts": "546900e63e6e1c72d01086ea5eecc3c418ba7b67",
11
+ "apps/vendor-aster/tsconfig.json": "81da8f78196974b5d15da0edb6b2d9f48641063c",
12
+ "apps/vendor-aster/.rush/temp/shrinkwrap-deps.json": "1f86f5175c8714c481b272c219e0d95bc6e72755",
13
+ "libraries/protocol/temp/package-deps.json": "3b8c7d38aeee5cc1b2858530c0bff8904d8326e4",
14
+ "libraries/data-account/temp/package-deps.json": "5eb844c54c6c70d8cdde8bceb2e3ce337ebbe061",
15
+ "libraries/utils/temp/package-deps.json": "c0ccd6946461153e7eb1b04a3061e075788fad2a",
16
+ "libraries/data-series/temp/package-deps.json": "f26c0a0e5ae88682a502ccaa50da5c33dc32aa2c",
17
+ "libraries/sql/temp/package-deps.json": "58c2368f516d411e6849e82620f1969f34db381e",
18
+ "libraries/data-product/temp/package-deps.json": "54454b1f541b962b2e87f66459d3a2f0cd524d4d",
19
+ "libraries/data-ohlc/temp/package-deps.json": "be3c2f508e0e7a20c4a402da38ac0882fb4e4c2b",
20
+ "libraries/data-order/temp/package-deps.json": "52b60ad284690709bdd3a1b922b50321e54faa81",
21
+ "libraries/data-interest-rate/temp/package-deps.json": "2eb39b7c4946bd07e168d2f8729fd843eddca04a",
22
+ "libraries/transfer/temp/package-deps.json": "55b5e0be5042b445bf9cf2be5b4087baf93de46b",
23
+ "libraries/extension/temp/package-deps.json": "abcd1ce716b9fd88f33173d01f02c4c3a9462009",
24
+ "tools/toolkit/temp/package-deps.json": "3bef053db16659f0cdaceea64c8a8580c0131633"
25
+ }