@yuants/vendor-aster 0.7.19 → 0.7.20
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/public-api.js +5 -0
- package/dist/api/public-api.js.map +1 -1
- package/dist/services/markets/quote.js +62 -55
- package/dist/services/markets/quote.js.map +1 -1
- package/lib/api/public-api.d.ts +25 -0
- package/lib/api/public-api.d.ts.map +1 -1
- package/lib/api/public-api.js +6 -1
- package/lib/api/public-api.js.map +1 -1
- package/lib/services/markets/quote.js +60 -53
- package/lib/services/markets/quote.js.map +1 -1
- package/package.json +1 -1
- package/temp/package-deps.json +5 -5
package/dist/api/public-api.js
CHANGED
|
@@ -45,4 +45,9 @@ export const getFApiV1OpenInterest = createApi('GET', '/fapi/v1/openInterest');
|
|
|
45
45
|
* https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E6%9C%80%E6%96%B0%E4%BB%B7%E6%A0%BC
|
|
46
46
|
*/
|
|
47
47
|
export const getFApiV1TickerPrice = createApi('GET', '/fapi/v1/ticker/price');
|
|
48
|
+
/**
|
|
49
|
+
* 获取资金费率
|
|
50
|
+
* https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md
|
|
51
|
+
*/
|
|
52
|
+
export const getFApiV1PremiumIndex = createApi('GET', '/fapi/v1/premiumIndex');
|
|
48
53
|
//# sourceMappingURL=public-api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public-api.js","sourceRoot":"","sources":["../../src/api/public-api.ts"],"names":[],"mappings":"AAAA,MAAM,QAAQ,GAAG,2BAA2B,CAAC;AAC7C,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEtE,MAAM,0BAA0B,GAAG,wBAAwB,CAAC,OAAO,CACjE,gBAAgB,EAChB,0BAA0B,CAC3B,CAAC;AACF,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;AACxC,MAAM,OAAO,GAAG,KAAK,EAAK,MAAc,EAAE,QAAgB,EAAE,SAAc,EAAE,EAAc,EAAE;IAC1F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9B,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;QACjD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;KACvC;IAED,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7B,0BAA0B,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;IACnG,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;QACtC,MAAM;KACP,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE;QAC9B,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;KAC3B;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,SAAS,GACb,CAAa,MAAc,EAAE,QAAgB,EAAE,EAAE,CACjD,CAAC,MAAY,EAAE,EAAE,CACf,OAAO,CAAO,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAY3C,KAAK,EAAE,sBAAsB,CAAC,CAAC;AA2BjC;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAS,CAAyB,KAAK,EAAE,uBAAuB,CAAC,CAAC;AAEvG;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAS,CAS5C,KAAK,EAAE,uBAAuB,CAAC,CAAC;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAO3C,KAAK,EAAE,uBAAuB,CAAC,CAAC","sourcesContent":["const BASE_URL = 'https://fapi.asterdex.com';\nimport { GlobalPrometheusRegistry, Terminal } from '@yuants/protocol';\n\nconst MetricsAsterApiCallCounter = GlobalPrometheusRegistry.counter(\n 'aster_api_call',\n 'Number of aster api call',\n);\nconst terminal = Terminal.fromNodeEnv();\nconst request = async <T>(method: string, endpoint: string, params: any = {}): Promise<T> => {\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 console.info(url.toString());\n MetricsAsterApiCallCounter.labels({ path: url.pathname, terminal_id: terminal.terminal_id }).inc();\n const res = await fetch(url.toString(), {\n method,\n }).then((response) => response.json());\n if (res.code && res.code !== 0) {\n throw JSON.stringify(res);\n }\n return res;\n};\n\nconst createApi =\n <TReq, TRes>(method: string, endpoint: string) =>\n (params: TReq) =>\n request<TRes>(method, endpoint, params);\n\n/**\n * 获取资金费率历史\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E6%9F%A5%E8%AF%A2%E8%B5%84%E9%87%91%E8%B4%B9%E7%8E%87%E5%8E%86%E5%8F%B2\n */\nexport const getFApiV1FundingRate = createApi<\n {\n symbol?: string;\n startTime?: number;\n endTime?: number;\n limit?: number;\n },\n {\n symbol: string;\n fundingRate: string;\n fundingTime: number;\n }[]\n>('GET', '/fapi/v1/fundingRate');\n\nexport interface IAsterRateLimit {\n rateLimitType?: string;\n interval?: string;\n intervalNum?: number;\n limit?: number;\n}\n\nexport interface IAsterExchangeInfo {\n symbols: {\n symbol: string;\n status: 'TRADING' | 'BREAK' | 'HALT';\n baseAsset: string;\n quoteAsset: string;\n pricePrecision: number;\n quantityPrecision: number;\n baseAssetPrecision: number;\n quotePrecision: number;\n filters: {\n filterType: string;\n [key: string]: any;\n }[];\n }[];\n rateLimits?: IAsterRateLimit[];\n}\n\n/**\n * 获取交易对信息\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E4%BA%A4%E6%98%93%E5%AF%B9%E4%BF%A1%E6%81%AF\n */\nexport const getFApiV1ExchangeInfo = createApi<{}, IAsterExchangeInfo>('GET', '/fapi/v1/exchangeInfo');\n\n/**\n * 获取未平仓合约数量\n *\n * 无 API 文档 (weird)\n */\nexport const getFApiV1OpenInterest = createApi<\n {\n symbol: string;\n },\n {\n symbol: string;\n openInterest: string;\n time: number;\n }\n>('GET', '/fapi/v1/openInterest');\n\n/**\n * 获取最新价格\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E6%9C%80%E6%96%B0%E4%BB%B7%E6%A0%BC\n */\nexport const getFApiV1TickerPrice = createApi<\n {},\n {\n symbol: string;\n price: string;\n time?: number;\n }[]\n>('GET', '/fapi/v1/ticker/price');\n"]}
|
|
1
|
+
{"version":3,"file":"public-api.js","sourceRoot":"","sources":["../../src/api/public-api.ts"],"names":[],"mappings":"AAAA,MAAM,QAAQ,GAAG,2BAA2B,CAAC;AAC7C,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEtE,MAAM,0BAA0B,GAAG,wBAAwB,CAAC,OAAO,CACjE,gBAAgB,EAChB,0BAA0B,CAC3B,CAAC;AACF,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;AACxC,MAAM,OAAO,GAAG,KAAK,EAAK,MAAc,EAAE,QAAgB,EAAE,SAAc,EAAE,EAAc,EAAE;IAC1F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9B,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;QACjD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;KACvC;IAED,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7B,0BAA0B,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;IACnG,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;QACtC,MAAM;KACP,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE;QAC9B,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;KAC3B;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,SAAS,GACb,CAAa,MAAc,EAAE,QAAgB,EAAE,EAAE,CACjD,CAAC,MAAY,EAAE,EAAE,CACf,OAAO,CAAO,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAY3C,KAAK,EAAE,sBAAsB,CAAC,CAAC;AA2BjC;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAS,CAAyB,KAAK,EAAE,uBAAuB,CAAC,CAAC;AAEvG;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAS,CAS5C,KAAK,EAAE,uBAAuB,CAAC,CAAC;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAO3C,KAAK,EAAE,uBAAuB,CAAC,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAS,CAwB5C,KAAK,EAAE,uBAAuB,CAAC,CAAC","sourcesContent":["const BASE_URL = 'https://fapi.asterdex.com';\nimport { GlobalPrometheusRegistry, Terminal } from '@yuants/protocol';\n\nconst MetricsAsterApiCallCounter = GlobalPrometheusRegistry.counter(\n 'aster_api_call',\n 'Number of aster api call',\n);\nconst terminal = Terminal.fromNodeEnv();\nconst request = async <T>(method: string, endpoint: string, params: any = {}): Promise<T> => {\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 console.info(url.toString());\n MetricsAsterApiCallCounter.labels({ path: url.pathname, terminal_id: terminal.terminal_id }).inc();\n const res = await fetch(url.toString(), {\n method,\n }).then((response) => response.json());\n if (res.code && res.code !== 0) {\n throw JSON.stringify(res);\n }\n return res;\n};\n\nconst createApi =\n <TReq, TRes>(method: string, endpoint: string) =>\n (params: TReq) =>\n request<TRes>(method, endpoint, params);\n\n/**\n * 获取资金费率历史\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E6%9F%A5%E8%AF%A2%E8%B5%84%E9%87%91%E8%B4%B9%E7%8E%87%E5%8E%86%E5%8F%B2\n */\nexport const getFApiV1FundingRate = createApi<\n {\n symbol?: string;\n startTime?: number;\n endTime?: number;\n limit?: number;\n },\n {\n symbol: string;\n fundingRate: string;\n fundingTime: number;\n }[]\n>('GET', '/fapi/v1/fundingRate');\n\nexport interface IAsterRateLimit {\n rateLimitType?: string;\n interval?: string;\n intervalNum?: number;\n limit?: number;\n}\n\nexport interface IAsterExchangeInfo {\n symbols: {\n symbol: string;\n status: 'TRADING' | 'BREAK' | 'HALT';\n baseAsset: string;\n quoteAsset: string;\n pricePrecision: number;\n quantityPrecision: number;\n baseAssetPrecision: number;\n quotePrecision: number;\n filters: {\n filterType: string;\n [key: string]: any;\n }[];\n }[];\n rateLimits?: IAsterRateLimit[];\n}\n\n/**\n * 获取交易对信息\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E4%BA%A4%E6%98%93%E5%AF%B9%E4%BF%A1%E6%81%AF\n */\nexport const getFApiV1ExchangeInfo = createApi<{}, IAsterExchangeInfo>('GET', '/fapi/v1/exchangeInfo');\n\n/**\n * 获取未平仓合约数量\n *\n * 无 API 文档 (weird)\n */\nexport const getFApiV1OpenInterest = createApi<\n {\n symbol: string;\n },\n {\n symbol: string;\n openInterest: string;\n time: number;\n }\n>('GET', '/fapi/v1/openInterest');\n\n/**\n * 获取最新价格\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E6%9C%80%E6%96%B0%E4%BB%B7%E6%A0%BC\n */\nexport const getFApiV1TickerPrice = createApi<\n {},\n {\n symbol: string;\n price: string;\n time?: number;\n }[]\n>('GET', '/fapi/v1/ticker/price');\n\n/**\n * 获取资金费率\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md\n */\nexport const getFApiV1PremiumIndex = createApi<\n {\n symbol?: string;\n },\n | {\n symbol: string; // 交易对\n markPrice: string; // 标记价格\n indexPrice: string; // 指数价格\n estimatedSettlePrice: string; // 预估结算价,仅在交割开始前最后一小时有意义\n lastFundingRate: string; // 最近更新的资金费率\n nextFundingTime: number; // 下次资金费时间\n interestRate: string; // 标的资产基础利率\n time: number; // 更新时间\n }\n | {\n symbol: string; // 交易对\n markPrice: string; // 标记价格\n indexPrice: string; // 指数价格\n estimatedSettlePrice: string; // 预估结算价,仅在交割开始前最后一小时有意义\n lastFundingRate: string; // 最近更新的资金费率\n nextFundingTime: number; // 下次资金费时间\n interestRate: string; // 标的资产基础利率\n time: number; // 更新时间\n }[]\n>('GET', '/fapi/v1/premiumIndex');\n"]}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { createCache } from '@yuants/cache';
|
|
1
2
|
import { Terminal } from '@yuants/protocol';
|
|
2
3
|
import { writeToSQL } from '@yuants/sql';
|
|
3
4
|
import { decodePath, encodePath } from '@yuants/utils';
|
|
4
|
-
import { defer, filter, groupBy, map, merge, mergeMap, repeat, retry, scan, shareReplay, } from 'rxjs';
|
|
5
|
-
import { getFApiV1TickerPrice, } from '../../api/public-api';
|
|
5
|
+
import { catchError, combineLatest, concatMap, defer, exhaustMap, filter, from, groupBy, map, merge, mergeMap, of, repeat, retry, scan, shareReplay, startWith, timer, } from 'rxjs';
|
|
6
|
+
import { getFApiV1ExchangeInfo, getFApiV1OpenInterest, getFApiV1PremiumIndex, getFApiV1TickerPrice, } from '../../api/public-api';
|
|
6
7
|
const terminal = Terminal.fromNodeEnv();
|
|
7
8
|
const DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS = 500;
|
|
8
9
|
const OPEN_INTEREST_TTL = process.env.OPEN_INTEREST_TTL ? Number(process.env.OPEN_INTEREST_TTL) : 120000;
|
|
@@ -35,60 +36,66 @@ const getRequestIntervalMs = (rateLimits, fallbackMs) => {
|
|
|
35
36
|
return fallbackMs;
|
|
36
37
|
return Math.max(fallbackMs, Math.max(...intervals));
|
|
37
38
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// catchError((err) => {
|
|
53
|
-
// console.warn('getFApiV1ExchangeInfo failed when calculating request interval', err);
|
|
54
|
-
// return of(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS);
|
|
55
|
-
// }),
|
|
56
|
-
// startWith(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS),
|
|
57
|
-
// retry({ delay: 60_000 }),
|
|
58
|
-
// shareReplay({ bufferSize: 1, refCount: true }),
|
|
59
|
-
// );
|
|
39
|
+
const openInterestCache = createCache(async (symbol) => {
|
|
40
|
+
try {
|
|
41
|
+
const data = await getFApiV1OpenInterest({ symbol });
|
|
42
|
+
return data.openInterest;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.warn('getFApiV1OpenInterest failed', symbol, error);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}, { expire: OPEN_INTEREST_TTL });
|
|
49
|
+
const requestInterval$ = defer(() => getFApiV1ExchangeInfo({})).pipe(map((info) => getRequestIntervalMs(info.rateLimits, DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS)), catchError((err) => {
|
|
50
|
+
console.warn('getFApiV1ExchangeInfo failed when calculating request interval', err);
|
|
51
|
+
return of(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS);
|
|
52
|
+
}), startWith(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS), retry({ delay: 60000 }), shareReplay({ bufferSize: 1, refCount: true }));
|
|
60
53
|
const ticker$ = defer(() => getFApiV1TickerPrice({})).pipe(map((tickers) => (Array.isArray(tickers) ? tickers : [])), repeat({ delay: 1000 }), retry({ delay: 5000 }), shareReplay({ bufferSize: 1, refCount: true }));
|
|
61
|
-
const quoteFromTicker$ = ticker$.pipe(mergeMap((tickers) => tickers || []), map((ticker) => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
54
|
+
const quoteFromTicker$ = ticker$.pipe(mergeMap((tickers) => tickers || []), map((ticker) => ({
|
|
55
|
+
datasource_id: 'ASTER',
|
|
56
|
+
product_id: encodePath('ASTER', 'PERP', ticker.symbol),
|
|
57
|
+
last_price: `${ticker.price}`,
|
|
58
|
+
bid_price: `${ticker.price}`,
|
|
59
|
+
ask_price: `${ticker.price}`,
|
|
60
|
+
})));
|
|
61
|
+
// Extract unique symbols from ticker stream
|
|
62
|
+
const symbolList$ = ticker$.pipe(map((tickers) => tickers.map((t) => t.symbol)), shareReplay({ bufferSize: 1, refCount: true }));
|
|
63
|
+
// Create a controlled rotation stream for fetching open interest
|
|
64
|
+
// This cycles through all symbols with proper delays, independent of ticker updates
|
|
65
|
+
const OPEN_INTEREST_CYCLE_DELAY = process.env.OPEN_INTEREST_CYCLE_DELAY
|
|
66
|
+
? Number(process.env.OPEN_INTEREST_CYCLE_DELAY)
|
|
67
|
+
: 60000; // 60 seconds between full cycles
|
|
68
|
+
const openInterestRotation$ = combineLatest([symbolList$, requestInterval$]).pipe(exhaustMap(([symbols, requestInterval]) => defer(() => {
|
|
69
|
+
console.info(`Starting open interest rotation for ${symbols.length} symbols with ${requestInterval}ms interval`);
|
|
70
|
+
return from(symbols).pipe(concatMap((symbol, index) => (index > 0 ? timer(requestInterval) : of(0)).pipe(mergeMap(() => from(openInterestCache.query(symbol))), map((openInterest) => ({
|
|
71
|
+
symbol,
|
|
72
|
+
openInterest: openInterest !== null && openInterest !== void 0 ? openInterest : '0',
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
})), catchError((err) => {
|
|
75
|
+
console.warn(`Failed to fetch open interest for ${symbol}:`, err);
|
|
76
|
+
return of(undefined);
|
|
77
|
+
}))));
|
|
78
|
+
})), filter((x) => !!x), shareReplay({ bufferSize: 1000, refCount: true }));
|
|
79
|
+
// Convert open interest rotation data to quote format
|
|
80
|
+
const quoteFromOpenInterest$ = openInterestRotation$.pipe(map((data) => ({
|
|
81
|
+
datasource_id: 'ASTER',
|
|
82
|
+
product_id: encodePath('ASTER', 'PERP', data === null || data === void 0 ? void 0 : data.symbol),
|
|
83
|
+
open_interest: data === null || data === void 0 ? void 0 : data.openInterest,
|
|
84
|
+
})));
|
|
85
|
+
// Funding rate stream - fetches all symbols at once (more efficient than per-symbol)
|
|
86
|
+
const fundingRate$ = defer(() => getFApiV1PremiumIndex({})).pipe(map((data) => {
|
|
87
|
+
// Handle both single object and array responses
|
|
88
|
+
const premiumDataArray = Array.isArray(data) ? data : [data];
|
|
89
|
+
return premiumDataArray;
|
|
90
|
+
}), repeat({ delay: OPEN_INTEREST_CYCLE_DELAY }), retry({ delay: 5000 }), shareReplay({ bufferSize: 1, refCount: true }));
|
|
91
|
+
// Convert funding rate data to quote format
|
|
92
|
+
const quoteFromFundingRate$ = fundingRate$.pipe(mergeMap((premiumDataArray) => premiumDataArray), map((premiumData) => ({
|
|
93
|
+
datasource_id: 'ASTER',
|
|
94
|
+
product_id: encodePath('ASTER', 'PERP', premiumData.symbol),
|
|
95
|
+
interest_rate_long: premiumData.lastFundingRate ? `${-+premiumData.lastFundingRate}` : undefined,
|
|
96
|
+
interest_rate_short: premiumData.lastFundingRate,
|
|
97
|
+
})));
|
|
98
|
+
const quote$ = merge(quoteFromTicker$, quoteFromOpenInterest$, quoteFromFundingRate$).pipe(groupBy((quote) => quote.product_id), mergeMap((group$) => group$.pipe(scan((acc, cur) => Object.assign(acc, cur, {
|
|
92
99
|
datasource_id: 'ASTER',
|
|
93
100
|
product_id: group$.key,
|
|
94
101
|
}), {}))), shareReplay({ bufferSize: 1, refCount: true }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quote.js","sourceRoot":"","sources":["../../../src/services/markets/quote.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCache } from '@yuants/cache';\nimport type { IQuote } from '@yuants/data-quote';\nimport { Terminal } from '@yuants/protocol';\nimport { writeToSQL } from '@yuants/sql';\nimport { decodePath, encodePath } from '@yuants/utils';\nimport {\n catchError,\n combineLatest,\n concatMap,\n defer,\n filter,\n from,\n groupBy,\n map,\n merge,\n mergeMap,\n of,\n repeat,\n retry,\n scan,\n shareReplay,\n startWith,\n timer,\n} from 'rxjs';\nimport {\n getFApiV1ExchangeInfo,\n getFApiV1OpenInterest,\n getFApiV1TickerPrice,\n IAsterRateLimit,\n} from '../../api/public-api';\n\nconst terminal = Terminal.fromNodeEnv();\nconst DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS = 500;\nconst OPEN_INTEREST_TTL = process.env.OPEN_INTEREST_TTL ? Number(process.env.OPEN_INTEREST_TTL) : 120_000;\n\nconst toIntervalMs = (interval?: string, intervalNum?: number) => {\n switch (interval) {\n case 'SECOND':\n return (intervalNum ?? 1) * 1_000;\n case 'MINUTE':\n return (intervalNum ?? 1) * 60_000;\n case 'HOUR':\n return (intervalNum ?? 1) * 3_600_000;\n case 'DAY':\n return (intervalNum ?? 1) * 86_400_000;\n default:\n return undefined;\n }\n};\n\nconst getRequestIntervalMs = (rateLimits: IAsterRateLimit[] | undefined, fallbackMs: number) => {\n const intervals: number[] = [];\n for (const item of rateLimits ?? []) {\n if (item.rateLimitType !== 'REQUEST_WEIGHT' && item.rateLimitType !== 'RAW_REQUESTS') continue;\n const duration = toIntervalMs(item.interval, item.intervalNum);\n const limit = item.limit;\n if (duration == null || limit == null || limit <= 0) continue;\n intervals.push(Math.ceil(duration / limit));\n }\n if (!intervals.length) return fallbackMs;\n return Math.max(fallbackMs, Math.max(...intervals));\n};\n\n// const openInterestCache = createCache<string>(\n// async (symbol: string) => {\n// try {\n// const data = await getFApiV1OpenInterest({ symbol });\n// return data.openInterest;\n// } catch (error) {\n// console.warn('getFApiV1OpenInterest failed', symbol, error);\n// return undefined;\n// }\n// },\n// { expire: OPEN_INTEREST_TTL },\n// );\n\n// const requestInterval$ = defer(() => getFApiV1ExchangeInfo({})).pipe(\n// map((info) => getRequestIntervalMs(info.rateLimits, DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS)),\n// catchError((err) => {\n// console.warn('getFApiV1ExchangeInfo failed when calculating request interval', err);\n// return of(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS);\n// }),\n// startWith(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS),\n// retry({ delay: 60_000 }),\n// shareReplay({ bufferSize: 1, refCount: true }),\n// );\n\nconst ticker$ = defer(() => getFApiV1TickerPrice({})).pipe(\n map((tickers) => (Array.isArray(tickers) ? tickers : [])),\n repeat({ delay: 1000 }),\n retry({ delay: 5000 }),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nconst quoteFromTicker$ = ticker$.pipe(\n mergeMap((tickers) => tickers || []),\n map(\n (ticker): Partial<IQuote> => ({\n datasource_id: 'ASTER',\n product_id: encodePath('ASTER', 'PERP', ticker.symbol),\n last_price: `${ticker.price}`,\n bid_price: `${ticker.price}`,\n ask_price: `${ticker.price}`,\n updated_at: new Date(ticker.time ?? Date.now()).toISOString(),\n }),\n ),\n);\n\n// const quoteFromOpenInterest$ = combineLatest([ticker$, requestInterval$]).pipe(\n// mergeMap(([tickers, requestInterval]) =>\n// from(tickers).pipe(\n// concatMap((ticker, index) =>\n// (index > 0 ? timer(requestInterval) : of(0)).pipe(\n// mergeMap(() => from(openInterestCache.query(ticker.symbol))),\n// map(\n// (openInterest): Partial<IQuote> => ({\n// datasource_id: 'ASTER',\n// product_id: encodePath('ASTER', 'PERP', ticker.symbol),\n// open_interest: `${openInterest ?? 0}`,\n// updated_at: new Date(ticker.time ?? Date.now()).toISOString(),\n// }),\n// ),\n// ),\n// ),\n// ),\n// ),\n// );\n\nconst quote$ = merge(\n quoteFromTicker$,\n // quoteFromOpenInterest$\n).pipe(\n groupBy((quote) => quote.product_id),\n mergeMap((group$) =>\n group$.pipe(\n scan(\n (acc, cur) =>\n Object.assign(acc, cur, {\n datasource_id: 'ASTER',\n product_id: group$.key,\n }),\n {} as Partial<IQuote>,\n ),\n ),\n ),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nif (process.env.WRITE_QUOTE_TO_SQL === 'true') {\n quote$\n .pipe(\n writeToSQL({\n terminal,\n tableName: 'quote',\n writeInterval: 1000,\n conflictKeys: ['datasource_id', 'product_id'],\n }),\n )\n .subscribe();\n\n terminal.channel.publishChannel('quote', { pattern: '^ASTER/' }, (channel_id) => {\n const [datasourceId, productId] = decodePath(channel_id);\n if (!datasourceId || !productId) {\n throw new Error(`Invalid channel_id: ${channel_id}`);\n }\n return quote$.pipe(filter((quote) => quote.product_id === productId));\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"quote.js","sourceRoot":"","sources":["../../../src/services/markets/quote.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCache } from '@yuants/cache';\nimport type { IQuote } from '@yuants/data-quote';\nimport { Terminal } from '@yuants/protocol';\nimport { writeToSQL } from '@yuants/sql';\nimport { decodePath, encodePath } from '@yuants/utils';\nimport {\n catchError,\n combineLatest,\n concatMap,\n defer,\n exhaustMap,\n filter,\n from,\n groupBy,\n map,\n merge,\n mergeMap,\n of,\n repeat,\n retry,\n scan,\n shareReplay,\n startWith,\n tap,\n timer,\n} from 'rxjs';\nimport {\n getFApiV1ExchangeInfo,\n getFApiV1OpenInterest,\n getFApiV1PremiumIndex,\n getFApiV1TickerPrice,\n IAsterRateLimit,\n} from '../../api/public-api';\n\nconst terminal = Terminal.fromNodeEnv();\nconst DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS = 500;\nconst OPEN_INTEREST_TTL = process.env.OPEN_INTEREST_TTL ? Number(process.env.OPEN_INTEREST_TTL) : 120_000;\n\nconst toIntervalMs = (interval?: string, intervalNum?: number) => {\n switch (interval) {\n case 'SECOND':\n return (intervalNum ?? 1) * 1_000;\n case 'MINUTE':\n return (intervalNum ?? 1) * 60_000;\n case 'HOUR':\n return (intervalNum ?? 1) * 3_600_000;\n case 'DAY':\n return (intervalNum ?? 1) * 86_400_000;\n default:\n return undefined;\n }\n};\n\nconst getRequestIntervalMs = (rateLimits: IAsterRateLimit[] | undefined, fallbackMs: number) => {\n const intervals: number[] = [];\n for (const item of rateLimits ?? []) {\n if (item.rateLimitType !== 'REQUEST_WEIGHT' && item.rateLimitType !== 'RAW_REQUESTS') continue;\n const duration = toIntervalMs(item.interval, item.intervalNum);\n const limit = item.limit;\n if (duration == null || limit == null || limit <= 0) continue;\n intervals.push(Math.ceil(duration / limit));\n }\n if (!intervals.length) return fallbackMs;\n return Math.max(fallbackMs, Math.max(...intervals));\n};\n\nconst openInterestCache = createCache<string>(\n async (symbol: string) => {\n try {\n const data = await getFApiV1OpenInterest({ symbol });\n return data.openInterest;\n } catch (error) {\n console.warn('getFApiV1OpenInterest failed', symbol, error);\n return undefined;\n }\n },\n { expire: OPEN_INTEREST_TTL },\n);\n\nconst requestInterval$ = defer(() => getFApiV1ExchangeInfo({})).pipe(\n map((info) => getRequestIntervalMs(info.rateLimits, DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS)),\n catchError((err) => {\n console.warn('getFApiV1ExchangeInfo failed when calculating request interval', err);\n return of(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS);\n }),\n startWith(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS),\n retry({ delay: 60_000 }),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nconst ticker$ = defer(() => getFApiV1TickerPrice({})).pipe(\n map((tickers) => (Array.isArray(tickers) ? tickers : [])),\n repeat({ delay: 1000 }),\n retry({ delay: 5000 }),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nconst quoteFromTicker$ = ticker$.pipe(\n mergeMap((tickers) => tickers || []),\n map(\n (ticker): Partial<IQuote> => ({\n datasource_id: 'ASTER',\n product_id: encodePath('ASTER', 'PERP', ticker.symbol),\n last_price: `${ticker.price}`,\n bid_price: `${ticker.price}`,\n ask_price: `${ticker.price}`,\n }),\n ),\n);\n\n// Extract unique symbols from ticker stream\nconst symbolList$ = ticker$.pipe(\n map((tickers) => tickers.map((t) => t.symbol)),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\n// Create a controlled rotation stream for fetching open interest\n// This cycles through all symbols with proper delays, independent of ticker updates\nconst OPEN_INTEREST_CYCLE_DELAY = process.env.OPEN_INTEREST_CYCLE_DELAY\n ? Number(process.env.OPEN_INTEREST_CYCLE_DELAY)\n : 60_000; // 60 seconds between full cycles\n\nconst openInterestRotation$ = combineLatest([symbolList$, requestInterval$]).pipe(\n exhaustMap(([symbols, requestInterval]) =>\n defer(() => {\n console.info(\n `Starting open interest rotation for ${symbols.length} symbols with ${requestInterval}ms interval`,\n );\n return from(symbols).pipe(\n concatMap((symbol, index) =>\n (index > 0 ? timer(requestInterval) : of(0)).pipe(\n mergeMap(() => from(openInterestCache.query(symbol))),\n map((openInterest) => ({\n symbol,\n openInterest: openInterest ?? '0',\n timestamp: Date.now(),\n })),\n catchError((err) => {\n console.warn(`Failed to fetch open interest for ${symbol}:`, err);\n return of(undefined);\n }),\n ),\n ),\n );\n }),\n ),\n filter((x) => !!x),\n shareReplay({ bufferSize: 1000, refCount: true }), // Cache open interest for all symbols\n);\n\n// Convert open interest rotation data to quote format\nconst quoteFromOpenInterest$ = openInterestRotation$.pipe(\n map(\n (data): Partial<IQuote> => ({\n datasource_id: 'ASTER',\n product_id: encodePath('ASTER', 'PERP', data?.symbol),\n open_interest: data?.openInterest,\n }),\n ),\n);\n\n// Funding rate stream - fetches all symbols at once (more efficient than per-symbol)\nconst fundingRate$ = defer(() => getFApiV1PremiumIndex({})).pipe(\n map((data) => {\n // Handle both single object and array responses\n const premiumDataArray = Array.isArray(data) ? data : [data];\n return premiumDataArray;\n }),\n repeat({ delay: OPEN_INTEREST_CYCLE_DELAY }),\n retry({ delay: 5000 }),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\n// Convert funding rate data to quote format\nconst quoteFromFundingRate$ = fundingRate$.pipe(\n mergeMap((premiumDataArray) => premiumDataArray),\n map(\n (premiumData): Partial<IQuote> => ({\n datasource_id: 'ASTER',\n product_id: encodePath('ASTER', 'PERP', premiumData.symbol),\n interest_rate_long: premiumData.lastFundingRate ? `${-+premiumData.lastFundingRate}` : undefined,\n interest_rate_short: premiumData.lastFundingRate,\n }),\n ),\n);\n\nconst quote$ = merge(quoteFromTicker$, quoteFromOpenInterest$, quoteFromFundingRate$).pipe(\n groupBy((quote) => quote.product_id),\n mergeMap((group$) =>\n group$.pipe(\n scan(\n (acc, cur) =>\n Object.assign(acc, cur, {\n datasource_id: 'ASTER',\n product_id: group$.key,\n }),\n {} as Partial<IQuote>,\n ),\n ),\n ),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nif (process.env.WRITE_QUOTE_TO_SQL === 'true') {\n quote$\n .pipe(\n writeToSQL({\n terminal,\n tableName: 'quote',\n writeInterval: 1000,\n conflictKeys: ['datasource_id', 'product_id'],\n }),\n )\n .subscribe();\n\n terminal.channel.publishChannel('quote', { pattern: '^ASTER/' }, (channel_id) => {\n const [datasourceId, productId] = decodePath(channel_id);\n if (!datasourceId || !productId) {\n throw new Error(`Invalid channel_id: ${channel_id}`);\n }\n return quote$.pipe(filter((quote) => quote.product_id === productId));\n });\n}\n"]}
|
package/lib/api/public-api.d.ts
CHANGED
|
@@ -64,4 +64,29 @@ export declare const getFApiV1TickerPrice: (params: {}) => Promise<{
|
|
|
64
64
|
price: string;
|
|
65
65
|
time?: number | undefined;
|
|
66
66
|
}[]>;
|
|
67
|
+
/**
|
|
68
|
+
* 获取资金费率
|
|
69
|
+
* https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md
|
|
70
|
+
*/
|
|
71
|
+
export declare const getFApiV1PremiumIndex: (params: {
|
|
72
|
+
symbol?: string | undefined;
|
|
73
|
+
}) => Promise<{
|
|
74
|
+
symbol: string;
|
|
75
|
+
markPrice: string;
|
|
76
|
+
indexPrice: string;
|
|
77
|
+
estimatedSettlePrice: string;
|
|
78
|
+
lastFundingRate: string;
|
|
79
|
+
nextFundingTime: number;
|
|
80
|
+
interestRate: string;
|
|
81
|
+
time: number;
|
|
82
|
+
} | {
|
|
83
|
+
symbol: string;
|
|
84
|
+
markPrice: string;
|
|
85
|
+
indexPrice: string;
|
|
86
|
+
estimatedSettlePrice: string;
|
|
87
|
+
lastFundingRate: string;
|
|
88
|
+
nextFundingTime: number;
|
|
89
|
+
interestRate: string;
|
|
90
|
+
time: number;
|
|
91
|
+
}[]>;
|
|
67
92
|
//# sourceMappingURL=public-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../../src/api/public-api.ts"],"names":[],"mappings":"AAgCA;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;YAQrB,MAAM;iBACD,MAAM;iBACN,MAAM;IAES,CAAC;AAEjC,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;QACrC,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE;YACP,UAAU,EAAE,MAAM,CAAC;YACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;SACpB,EAAE,CAAC;KACL,EAAE,CAAC;IACJ,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;CAChC;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,6CAAoE,CAAC;AAEvG;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;YAEtB,MAAM;;YAGN,MAAM;kBACA,MAAM;UACd,MAAM;EAEiB,CAAC;AAElC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;YAGrB,MAAM;WACP,MAAM;;IAGgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../../src/api/public-api.ts"],"names":[],"mappings":"AAgCA;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;YAQrB,MAAM;iBACD,MAAM;iBACN,MAAM;IAES,CAAC;AAEjC,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;QACrC,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE;YACP,UAAU,EAAE,MAAM,CAAC;YACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;SACpB,EAAE,CAAC;KACL,EAAE,CAAC;IACJ,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;CAChC;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,6CAAoE,CAAC;AAEvG;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;YAEtB,MAAM;;YAGN,MAAM;kBACA,MAAM;UACd,MAAM;EAEiB,CAAC;AAElC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;YAGrB,MAAM;WACP,MAAM;;IAGgB,CAAC;AAElC;;;GAGG;AACH,eAAO,MAAM,qBAAqB;;;YAKpB,MAAM;eACH,MAAM;gBACL,MAAM;0BACI,MAAM;qBACX,MAAM;qBACN,MAAM;kBACT,MAAM;UACd,MAAM;;YAGJ,MAAM;eACH,MAAM;gBACL,MAAM;0BACI,MAAM;qBACX,MAAM;qBACN,MAAM;kBACT,MAAM;UACd,MAAM;IAEe,CAAC"}
|
package/lib/api/public-api.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getFApiV1TickerPrice = exports.getFApiV1OpenInterest = exports.getFApiV1ExchangeInfo = exports.getFApiV1FundingRate = void 0;
|
|
3
|
+
exports.getFApiV1PremiumIndex = exports.getFApiV1TickerPrice = exports.getFApiV1OpenInterest = exports.getFApiV1ExchangeInfo = exports.getFApiV1FundingRate = void 0;
|
|
4
4
|
const BASE_URL = 'https://fapi.asterdex.com';
|
|
5
5
|
const protocol_1 = require("@yuants/protocol");
|
|
6
6
|
const MetricsAsterApiCallCounter = protocol_1.GlobalPrometheusRegistry.counter('aster_api_call', 'Number of aster api call');
|
|
@@ -48,4 +48,9 @@ exports.getFApiV1OpenInterest = createApi('GET', '/fapi/v1/openInterest');
|
|
|
48
48
|
* https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E6%9C%80%E6%96%B0%E4%BB%B7%E6%A0%BC
|
|
49
49
|
*/
|
|
50
50
|
exports.getFApiV1TickerPrice = createApi('GET', '/fapi/v1/ticker/price');
|
|
51
|
+
/**
|
|
52
|
+
* 获取资金费率
|
|
53
|
+
* https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md
|
|
54
|
+
*/
|
|
55
|
+
exports.getFApiV1PremiumIndex = createApi('GET', '/fapi/v1/premiumIndex');
|
|
51
56
|
//# sourceMappingURL=public-api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public-api.js","sourceRoot":"","sources":["../../src/api/public-api.ts"],"names":[],"mappings":";;;AAAA,MAAM,QAAQ,GAAG,2BAA2B,CAAC;AAC7C,+CAAsE;AAEtE,MAAM,0BAA0B,GAAG,mCAAwB,CAAC,OAAO,CACjE,gBAAgB,EAChB,0BAA0B,CAC3B,CAAC;AACF,MAAM,QAAQ,GAAG,mBAAQ,CAAC,WAAW,EAAE,CAAC;AACxC,MAAM,OAAO,GAAG,KAAK,EAAK,MAAc,EAAE,QAAgB,EAAE,SAAc,EAAE,EAAc,EAAE;IAC1F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9B,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;QACjD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;KACvC;IAED,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7B,0BAA0B,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;IACnG,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;QACtC,MAAM;KACP,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE;QAC9B,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;KAC3B;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,SAAS,GACb,CAAa,MAAc,EAAE,QAAgB,EAAE,EAAE,CACjD,CAAC,MAAY,EAAE,EAAE,CACf,OAAO,CAAO,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAE5C;;;;GAIG;AACU,QAAA,oBAAoB,GAAG,SAAS,CAY3C,KAAK,EAAE,sBAAsB,CAAC,CAAC;AA2BjC;;;;GAIG;AACU,QAAA,qBAAqB,GAAG,SAAS,CAAyB,KAAK,EAAE,uBAAuB,CAAC,CAAC;AAEvG;;;;GAIG;AACU,QAAA,qBAAqB,GAAG,SAAS,CAS5C,KAAK,EAAE,uBAAuB,CAAC,CAAC;AAElC;;;;GAIG;AACU,QAAA,oBAAoB,GAAG,SAAS,CAO3C,KAAK,EAAE,uBAAuB,CAAC,CAAC","sourcesContent":["const BASE_URL = 'https://fapi.asterdex.com';\nimport { GlobalPrometheusRegistry, Terminal } from '@yuants/protocol';\n\nconst MetricsAsterApiCallCounter = GlobalPrometheusRegistry.counter(\n 'aster_api_call',\n 'Number of aster api call',\n);\nconst terminal = Terminal.fromNodeEnv();\nconst request = async <T>(method: string, endpoint: string, params: any = {}): Promise<T> => {\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 console.info(url.toString());\n MetricsAsterApiCallCounter.labels({ path: url.pathname, terminal_id: terminal.terminal_id }).inc();\n const res = await fetch(url.toString(), {\n method,\n }).then((response) => response.json());\n if (res.code && res.code !== 0) {\n throw JSON.stringify(res);\n }\n return res;\n};\n\nconst createApi =\n <TReq, TRes>(method: string, endpoint: string) =>\n (params: TReq) =>\n request<TRes>(method, endpoint, params);\n\n/**\n * 获取资金费率历史\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E6%9F%A5%E8%AF%A2%E8%B5%84%E9%87%91%E8%B4%B9%E7%8E%87%E5%8E%86%E5%8F%B2\n */\nexport const getFApiV1FundingRate = createApi<\n {\n symbol?: string;\n startTime?: number;\n endTime?: number;\n limit?: number;\n },\n {\n symbol: string;\n fundingRate: string;\n fundingTime: number;\n }[]\n>('GET', '/fapi/v1/fundingRate');\n\nexport interface IAsterRateLimit {\n rateLimitType?: string;\n interval?: string;\n intervalNum?: number;\n limit?: number;\n}\n\nexport interface IAsterExchangeInfo {\n symbols: {\n symbol: string;\n status: 'TRADING' | 'BREAK' | 'HALT';\n baseAsset: string;\n quoteAsset: string;\n pricePrecision: number;\n quantityPrecision: number;\n baseAssetPrecision: number;\n quotePrecision: number;\n filters: {\n filterType: string;\n [key: string]: any;\n }[];\n }[];\n rateLimits?: IAsterRateLimit[];\n}\n\n/**\n * 获取交易对信息\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E4%BA%A4%E6%98%93%E5%AF%B9%E4%BF%A1%E6%81%AF\n */\nexport const getFApiV1ExchangeInfo = createApi<{}, IAsterExchangeInfo>('GET', '/fapi/v1/exchangeInfo');\n\n/**\n * 获取未平仓合约数量\n *\n * 无 API 文档 (weird)\n */\nexport const getFApiV1OpenInterest = createApi<\n {\n symbol: string;\n },\n {\n symbol: string;\n openInterest: string;\n time: number;\n }\n>('GET', '/fapi/v1/openInterest');\n\n/**\n * 获取最新价格\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E6%9C%80%E6%96%B0%E4%BB%B7%E6%A0%BC\n */\nexport const getFApiV1TickerPrice = createApi<\n {},\n {\n symbol: string;\n price: string;\n time?: number;\n }[]\n>('GET', '/fapi/v1/ticker/price');\n"]}
|
|
1
|
+
{"version":3,"file":"public-api.js","sourceRoot":"","sources":["../../src/api/public-api.ts"],"names":[],"mappings":";;;AAAA,MAAM,QAAQ,GAAG,2BAA2B,CAAC;AAC7C,+CAAsE;AAEtE,MAAM,0BAA0B,GAAG,mCAAwB,CAAC,OAAO,CACjE,gBAAgB,EAChB,0BAA0B,CAC3B,CAAC;AACF,MAAM,QAAQ,GAAG,mBAAQ,CAAC,WAAW,EAAE,CAAC;AACxC,MAAM,OAAO,GAAG,KAAK,EAAK,MAAc,EAAE,QAAgB,EAAE,SAAc,EAAE,EAAc,EAAE;IAC1F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9B,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;QACjD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;KACvC;IAED,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7B,0BAA0B,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;IACnG,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;QACtC,MAAM;KACP,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE;QAC9B,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;KAC3B;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,SAAS,GACb,CAAa,MAAc,EAAE,QAAgB,EAAE,EAAE,CACjD,CAAC,MAAY,EAAE,EAAE,CACf,OAAO,CAAO,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAE5C;;;;GAIG;AACU,QAAA,oBAAoB,GAAG,SAAS,CAY3C,KAAK,EAAE,sBAAsB,CAAC,CAAC;AA2BjC;;;;GAIG;AACU,QAAA,qBAAqB,GAAG,SAAS,CAAyB,KAAK,EAAE,uBAAuB,CAAC,CAAC;AAEvG;;;;GAIG;AACU,QAAA,qBAAqB,GAAG,SAAS,CAS5C,KAAK,EAAE,uBAAuB,CAAC,CAAC;AAElC;;;;GAIG;AACU,QAAA,oBAAoB,GAAG,SAAS,CAO3C,KAAK,EAAE,uBAAuB,CAAC,CAAC;AAElC;;;GAGG;AACU,QAAA,qBAAqB,GAAG,SAAS,CAwB5C,KAAK,EAAE,uBAAuB,CAAC,CAAC","sourcesContent":["const BASE_URL = 'https://fapi.asterdex.com';\nimport { GlobalPrometheusRegistry, Terminal } from '@yuants/protocol';\n\nconst MetricsAsterApiCallCounter = GlobalPrometheusRegistry.counter(\n 'aster_api_call',\n 'Number of aster api call',\n);\nconst terminal = Terminal.fromNodeEnv();\nconst request = async <T>(method: string, endpoint: string, params: any = {}): Promise<T> => {\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 console.info(url.toString());\n MetricsAsterApiCallCounter.labels({ path: url.pathname, terminal_id: terminal.terminal_id }).inc();\n const res = await fetch(url.toString(), {\n method,\n }).then((response) => response.json());\n if (res.code && res.code !== 0) {\n throw JSON.stringify(res);\n }\n return res;\n};\n\nconst createApi =\n <TReq, TRes>(method: string, endpoint: string) =>\n (params: TReq) =>\n request<TRes>(method, endpoint, params);\n\n/**\n * 获取资金费率历史\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E6%9F%A5%E8%AF%A2%E8%B5%84%E9%87%91%E8%B4%B9%E7%8E%87%E5%8E%86%E5%8F%B2\n */\nexport const getFApiV1FundingRate = createApi<\n {\n symbol?: string;\n startTime?: number;\n endTime?: number;\n limit?: number;\n },\n {\n symbol: string;\n fundingRate: string;\n fundingTime: number;\n }[]\n>('GET', '/fapi/v1/fundingRate');\n\nexport interface IAsterRateLimit {\n rateLimitType?: string;\n interval?: string;\n intervalNum?: number;\n limit?: number;\n}\n\nexport interface IAsterExchangeInfo {\n symbols: {\n symbol: string;\n status: 'TRADING' | 'BREAK' | 'HALT';\n baseAsset: string;\n quoteAsset: string;\n pricePrecision: number;\n quantityPrecision: number;\n baseAssetPrecision: number;\n quotePrecision: number;\n filters: {\n filterType: string;\n [key: string]: any;\n }[];\n }[];\n rateLimits?: IAsterRateLimit[];\n}\n\n/**\n * 获取交易对信息\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E4%BA%A4%E6%98%93%E5%AF%B9%E4%BF%A1%E6%81%AF\n */\nexport const getFApiV1ExchangeInfo = createApi<{}, IAsterExchangeInfo>('GET', '/fapi/v1/exchangeInfo');\n\n/**\n * 获取未平仓合约数量\n *\n * 无 API 文档 (weird)\n */\nexport const getFApiV1OpenInterest = createApi<\n {\n symbol: string;\n },\n {\n symbol: string;\n openInterest: string;\n time: number;\n }\n>('GET', '/fapi/v1/openInterest');\n\n/**\n * 获取最新价格\n *\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md#%E6%9C%80%E6%96%B0%E4%BB%B7%E6%A0%BC\n */\nexport const getFApiV1TickerPrice = createApi<\n {},\n {\n symbol: string;\n price: string;\n time?: number;\n }[]\n>('GET', '/fapi/v1/ticker/price');\n\n/**\n * 获取资金费率\n * https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api_CN.md\n */\nexport const getFApiV1PremiumIndex = createApi<\n {\n symbol?: string;\n },\n | {\n symbol: string; // 交易对\n markPrice: string; // 标记价格\n indexPrice: string; // 指数价格\n estimatedSettlePrice: string; // 预估结算价,仅在交割开始前最后一小时有意义\n lastFundingRate: string; // 最近更新的资金费率\n nextFundingTime: number; // 下次资金费时间\n interestRate: string; // 标的资产基础利率\n time: number; // 更新时间\n }\n | {\n symbol: string; // 交易对\n markPrice: string; // 标记价格\n indexPrice: string; // 指数价格\n estimatedSettlePrice: string; // 预估结算价,仅在交割开始前最后一小时有意义\n lastFundingRate: string; // 最近更新的资金费率\n nextFundingTime: number; // 下次资金费时间\n interestRate: string; // 标的资产基础利率\n time: number; // 更新时间\n }[]\n>('GET', '/fapi/v1/premiumIndex');\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const cache_1 = require("@yuants/cache");
|
|
3
4
|
const protocol_1 = require("@yuants/protocol");
|
|
4
5
|
const sql_1 = require("@yuants/sql");
|
|
5
6
|
const utils_1 = require("@yuants/utils");
|
|
@@ -37,60 +38,66 @@ const getRequestIntervalMs = (rateLimits, fallbackMs) => {
|
|
|
37
38
|
return fallbackMs;
|
|
38
39
|
return Math.max(fallbackMs, Math.max(...intervals));
|
|
39
40
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// catchError((err) => {
|
|
55
|
-
// console.warn('getFApiV1ExchangeInfo failed when calculating request interval', err);
|
|
56
|
-
// return of(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS);
|
|
57
|
-
// }),
|
|
58
|
-
// startWith(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS),
|
|
59
|
-
// retry({ delay: 60_000 }),
|
|
60
|
-
// shareReplay({ bufferSize: 1, refCount: true }),
|
|
61
|
-
// );
|
|
41
|
+
const openInterestCache = (0, cache_1.createCache)(async (symbol) => {
|
|
42
|
+
try {
|
|
43
|
+
const data = await (0, public_api_1.getFApiV1OpenInterest)({ symbol });
|
|
44
|
+
return data.openInterest;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.warn('getFApiV1OpenInterest failed', symbol, error);
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}, { expire: OPEN_INTEREST_TTL });
|
|
51
|
+
const requestInterval$ = (0, rxjs_1.defer)(() => (0, public_api_1.getFApiV1ExchangeInfo)({})).pipe((0, rxjs_1.map)((info) => getRequestIntervalMs(info.rateLimits, DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS)), (0, rxjs_1.catchError)((err) => {
|
|
52
|
+
console.warn('getFApiV1ExchangeInfo failed when calculating request interval', err);
|
|
53
|
+
return (0, rxjs_1.of)(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS);
|
|
54
|
+
}), (0, rxjs_1.startWith)(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS), (0, rxjs_1.retry)({ delay: 60000 }), (0, rxjs_1.shareReplay)({ bufferSize: 1, refCount: true }));
|
|
62
55
|
const ticker$ = (0, rxjs_1.defer)(() => (0, public_api_1.getFApiV1TickerPrice)({})).pipe((0, rxjs_1.map)((tickers) => (Array.isArray(tickers) ? tickers : [])), (0, rxjs_1.repeat)({ delay: 1000 }), (0, rxjs_1.retry)({ delay: 5000 }), (0, rxjs_1.shareReplay)({ bufferSize: 1, refCount: true }));
|
|
63
|
-
const quoteFromTicker$ = ticker$.pipe((0, rxjs_1.mergeMap)((tickers) => tickers || []), (0, rxjs_1.map)((ticker) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
56
|
+
const quoteFromTicker$ = ticker$.pipe((0, rxjs_1.mergeMap)((tickers) => tickers || []), (0, rxjs_1.map)((ticker) => ({
|
|
57
|
+
datasource_id: 'ASTER',
|
|
58
|
+
product_id: (0, utils_1.encodePath)('ASTER', 'PERP', ticker.symbol),
|
|
59
|
+
last_price: `${ticker.price}`,
|
|
60
|
+
bid_price: `${ticker.price}`,
|
|
61
|
+
ask_price: `${ticker.price}`,
|
|
62
|
+
})));
|
|
63
|
+
// Extract unique symbols from ticker stream
|
|
64
|
+
const symbolList$ = ticker$.pipe((0, rxjs_1.map)((tickers) => tickers.map((t) => t.symbol)), (0, rxjs_1.shareReplay)({ bufferSize: 1, refCount: true }));
|
|
65
|
+
// Create a controlled rotation stream for fetching open interest
|
|
66
|
+
// This cycles through all symbols with proper delays, independent of ticker updates
|
|
67
|
+
const OPEN_INTEREST_CYCLE_DELAY = process.env.OPEN_INTEREST_CYCLE_DELAY
|
|
68
|
+
? Number(process.env.OPEN_INTEREST_CYCLE_DELAY)
|
|
69
|
+
: 60000; // 60 seconds between full cycles
|
|
70
|
+
const openInterestRotation$ = (0, rxjs_1.combineLatest)([symbolList$, requestInterval$]).pipe((0, rxjs_1.exhaustMap)(([symbols, requestInterval]) => (0, rxjs_1.defer)(() => {
|
|
71
|
+
console.info(`Starting open interest rotation for ${symbols.length} symbols with ${requestInterval}ms interval`);
|
|
72
|
+
return (0, rxjs_1.from)(symbols).pipe((0, rxjs_1.concatMap)((symbol, index) => (index > 0 ? (0, rxjs_1.timer)(requestInterval) : (0, rxjs_1.of)(0)).pipe((0, rxjs_1.mergeMap)(() => (0, rxjs_1.from)(openInterestCache.query(symbol))), (0, rxjs_1.map)((openInterest) => ({
|
|
73
|
+
symbol,
|
|
74
|
+
openInterest: openInterest !== null && openInterest !== void 0 ? openInterest : '0',
|
|
75
|
+
timestamp: Date.now(),
|
|
76
|
+
})), (0, rxjs_1.catchError)((err) => {
|
|
77
|
+
console.warn(`Failed to fetch open interest for ${symbol}:`, err);
|
|
78
|
+
return (0, rxjs_1.of)(undefined);
|
|
79
|
+
}))));
|
|
80
|
+
})), (0, rxjs_1.filter)((x) => !!x), (0, rxjs_1.shareReplay)({ bufferSize: 1000, refCount: true }));
|
|
81
|
+
// Convert open interest rotation data to quote format
|
|
82
|
+
const quoteFromOpenInterest$ = openInterestRotation$.pipe((0, rxjs_1.map)((data) => ({
|
|
83
|
+
datasource_id: 'ASTER',
|
|
84
|
+
product_id: (0, utils_1.encodePath)('ASTER', 'PERP', data === null || data === void 0 ? void 0 : data.symbol),
|
|
85
|
+
open_interest: data === null || data === void 0 ? void 0 : data.openInterest,
|
|
86
|
+
})));
|
|
87
|
+
// Funding rate stream - fetches all symbols at once (more efficient than per-symbol)
|
|
88
|
+
const fundingRate$ = (0, rxjs_1.defer)(() => (0, public_api_1.getFApiV1PremiumIndex)({})).pipe((0, rxjs_1.map)((data) => {
|
|
89
|
+
// Handle both single object and array responses
|
|
90
|
+
const premiumDataArray = Array.isArray(data) ? data : [data];
|
|
91
|
+
return premiumDataArray;
|
|
92
|
+
}), (0, rxjs_1.repeat)({ delay: OPEN_INTEREST_CYCLE_DELAY }), (0, rxjs_1.retry)({ delay: 5000 }), (0, rxjs_1.shareReplay)({ bufferSize: 1, refCount: true }));
|
|
93
|
+
// Convert funding rate data to quote format
|
|
94
|
+
const quoteFromFundingRate$ = fundingRate$.pipe((0, rxjs_1.mergeMap)((premiumDataArray) => premiumDataArray), (0, rxjs_1.map)((premiumData) => ({
|
|
95
|
+
datasource_id: 'ASTER',
|
|
96
|
+
product_id: (0, utils_1.encodePath)('ASTER', 'PERP', premiumData.symbol),
|
|
97
|
+
interest_rate_long: premiumData.lastFundingRate ? `${-+premiumData.lastFundingRate}` : undefined,
|
|
98
|
+
interest_rate_short: premiumData.lastFundingRate,
|
|
99
|
+
})));
|
|
100
|
+
const quote$ = (0, rxjs_1.merge)(quoteFromTicker$, quoteFromOpenInterest$, quoteFromFundingRate$).pipe((0, rxjs_1.groupBy)((quote) => quote.product_id), (0, rxjs_1.mergeMap)((group$) => group$.pipe((0, rxjs_1.scan)((acc, cur) => Object.assign(acc, cur, {
|
|
94
101
|
datasource_id: 'ASTER',
|
|
95
102
|
product_id: group$.key,
|
|
96
103
|
}), {}))), (0, rxjs_1.shareReplay)({ bufferSize: 1, refCount: true }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quote.js","sourceRoot":"","sources":["../../../src/services/markets/quote.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCache } from '@yuants/cache';\nimport type { IQuote } from '@yuants/data-quote';\nimport { Terminal } from '@yuants/protocol';\nimport { writeToSQL } from '@yuants/sql';\nimport { decodePath, encodePath } from '@yuants/utils';\nimport {\n catchError,\n combineLatest,\n concatMap,\n defer,\n filter,\n from,\n groupBy,\n map,\n merge,\n mergeMap,\n of,\n repeat,\n retry,\n scan,\n shareReplay,\n startWith,\n timer,\n} from 'rxjs';\nimport {\n getFApiV1ExchangeInfo,\n getFApiV1OpenInterest,\n getFApiV1TickerPrice,\n IAsterRateLimit,\n} from '../../api/public-api';\n\nconst terminal = Terminal.fromNodeEnv();\nconst DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS = 500;\nconst OPEN_INTEREST_TTL = process.env.OPEN_INTEREST_TTL ? Number(process.env.OPEN_INTEREST_TTL) : 120_000;\n\nconst toIntervalMs = (interval?: string, intervalNum?: number) => {\n switch (interval) {\n case 'SECOND':\n return (intervalNum ?? 1) * 1_000;\n case 'MINUTE':\n return (intervalNum ?? 1) * 60_000;\n case 'HOUR':\n return (intervalNum ?? 1) * 3_600_000;\n case 'DAY':\n return (intervalNum ?? 1) * 86_400_000;\n default:\n return undefined;\n }\n};\n\nconst getRequestIntervalMs = (rateLimits: IAsterRateLimit[] | undefined, fallbackMs: number) => {\n const intervals: number[] = [];\n for (const item of rateLimits ?? []) {\n if (item.rateLimitType !== 'REQUEST_WEIGHT' && item.rateLimitType !== 'RAW_REQUESTS') continue;\n const duration = toIntervalMs(item.interval, item.intervalNum);\n const limit = item.limit;\n if (duration == null || limit == null || limit <= 0) continue;\n intervals.push(Math.ceil(duration / limit));\n }\n if (!intervals.length) return fallbackMs;\n return Math.max(fallbackMs, Math.max(...intervals));\n};\n\n// const openInterestCache = createCache<string>(\n// async (symbol: string) => {\n// try {\n// const data = await getFApiV1OpenInterest({ symbol });\n// return data.openInterest;\n// } catch (error) {\n// console.warn('getFApiV1OpenInterest failed', symbol, error);\n// return undefined;\n// }\n// },\n// { expire: OPEN_INTEREST_TTL },\n// );\n\n// const requestInterval$ = defer(() => getFApiV1ExchangeInfo({})).pipe(\n// map((info) => getRequestIntervalMs(info.rateLimits, DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS)),\n// catchError((err) => {\n// console.warn('getFApiV1ExchangeInfo failed when calculating request interval', err);\n// return of(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS);\n// }),\n// startWith(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS),\n// retry({ delay: 60_000 }),\n// shareReplay({ bufferSize: 1, refCount: true }),\n// );\n\nconst ticker$ = defer(() => getFApiV1TickerPrice({})).pipe(\n map((tickers) => (Array.isArray(tickers) ? tickers : [])),\n repeat({ delay: 1000 }),\n retry({ delay: 5000 }),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nconst quoteFromTicker$ = ticker$.pipe(\n mergeMap((tickers) => tickers || []),\n map(\n (ticker): Partial<IQuote> => ({\n datasource_id: 'ASTER',\n product_id: encodePath('ASTER', 'PERP', ticker.symbol),\n last_price: `${ticker.price}`,\n bid_price: `${ticker.price}`,\n ask_price: `${ticker.price}`,\n updated_at: new Date(ticker.time ?? Date.now()).toISOString(),\n }),\n ),\n);\n\n// const quoteFromOpenInterest$ = combineLatest([ticker$, requestInterval$]).pipe(\n// mergeMap(([tickers, requestInterval]) =>\n// from(tickers).pipe(\n// concatMap((ticker, index) =>\n// (index > 0 ? timer(requestInterval) : of(0)).pipe(\n// mergeMap(() => from(openInterestCache.query(ticker.symbol))),\n// map(\n// (openInterest): Partial<IQuote> => ({\n// datasource_id: 'ASTER',\n// product_id: encodePath('ASTER', 'PERP', ticker.symbol),\n// open_interest: `${openInterest ?? 0}`,\n// updated_at: new Date(ticker.time ?? Date.now()).toISOString(),\n// }),\n// ),\n// ),\n// ),\n// ),\n// ),\n// );\n\nconst quote$ = merge(\n quoteFromTicker$,\n // quoteFromOpenInterest$\n).pipe(\n groupBy((quote) => quote.product_id),\n mergeMap((group$) =>\n group$.pipe(\n scan(\n (acc, cur) =>\n Object.assign(acc, cur, {\n datasource_id: 'ASTER',\n product_id: group$.key,\n }),\n {} as Partial<IQuote>,\n ),\n ),\n ),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nif (process.env.WRITE_QUOTE_TO_SQL === 'true') {\n quote$\n .pipe(\n writeToSQL({\n terminal,\n tableName: 'quote',\n writeInterval: 1000,\n conflictKeys: ['datasource_id', 'product_id'],\n }),\n )\n .subscribe();\n\n terminal.channel.publishChannel('quote', { pattern: '^ASTER/' }, (channel_id) => {\n const [datasourceId, productId] = decodePath(channel_id);\n if (!datasourceId || !productId) {\n throw new Error(`Invalid channel_id: ${channel_id}`);\n }\n return quote$.pipe(filter((quote) => quote.product_id === productId));\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"quote.js","sourceRoot":"","sources":["../../../src/services/markets/quote.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCache } from '@yuants/cache';\nimport type { IQuote } from '@yuants/data-quote';\nimport { Terminal } from '@yuants/protocol';\nimport { writeToSQL } from '@yuants/sql';\nimport { decodePath, encodePath } from '@yuants/utils';\nimport {\n catchError,\n combineLatest,\n concatMap,\n defer,\n exhaustMap,\n filter,\n from,\n groupBy,\n map,\n merge,\n mergeMap,\n of,\n repeat,\n retry,\n scan,\n shareReplay,\n startWith,\n tap,\n timer,\n} from 'rxjs';\nimport {\n getFApiV1ExchangeInfo,\n getFApiV1OpenInterest,\n getFApiV1PremiumIndex,\n getFApiV1TickerPrice,\n IAsterRateLimit,\n} from '../../api/public-api';\n\nconst terminal = Terminal.fromNodeEnv();\nconst DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS = 500;\nconst OPEN_INTEREST_TTL = process.env.OPEN_INTEREST_TTL ? Number(process.env.OPEN_INTEREST_TTL) : 120_000;\n\nconst toIntervalMs = (interval?: string, intervalNum?: number) => {\n switch (interval) {\n case 'SECOND':\n return (intervalNum ?? 1) * 1_000;\n case 'MINUTE':\n return (intervalNum ?? 1) * 60_000;\n case 'HOUR':\n return (intervalNum ?? 1) * 3_600_000;\n case 'DAY':\n return (intervalNum ?? 1) * 86_400_000;\n default:\n return undefined;\n }\n};\n\nconst getRequestIntervalMs = (rateLimits: IAsterRateLimit[] | undefined, fallbackMs: number) => {\n const intervals: number[] = [];\n for (const item of rateLimits ?? []) {\n if (item.rateLimitType !== 'REQUEST_WEIGHT' && item.rateLimitType !== 'RAW_REQUESTS') continue;\n const duration = toIntervalMs(item.interval, item.intervalNum);\n const limit = item.limit;\n if (duration == null || limit == null || limit <= 0) continue;\n intervals.push(Math.ceil(duration / limit));\n }\n if (!intervals.length) return fallbackMs;\n return Math.max(fallbackMs, Math.max(...intervals));\n};\n\nconst openInterestCache = createCache<string>(\n async (symbol: string) => {\n try {\n const data = await getFApiV1OpenInterest({ symbol });\n return data.openInterest;\n } catch (error) {\n console.warn('getFApiV1OpenInterest failed', symbol, error);\n return undefined;\n }\n },\n { expire: OPEN_INTEREST_TTL },\n);\n\nconst requestInterval$ = defer(() => getFApiV1ExchangeInfo({})).pipe(\n map((info) => getRequestIntervalMs(info.rateLimits, DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS)),\n catchError((err) => {\n console.warn('getFApiV1ExchangeInfo failed when calculating request interval', err);\n return of(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS);\n }),\n startWith(DEFAULT_OPEN_INTEREST_REQUEST_INTERVAL_MS),\n retry({ delay: 60_000 }),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nconst ticker$ = defer(() => getFApiV1TickerPrice({})).pipe(\n map((tickers) => (Array.isArray(tickers) ? tickers : [])),\n repeat({ delay: 1000 }),\n retry({ delay: 5000 }),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nconst quoteFromTicker$ = ticker$.pipe(\n mergeMap((tickers) => tickers || []),\n map(\n (ticker): Partial<IQuote> => ({\n datasource_id: 'ASTER',\n product_id: encodePath('ASTER', 'PERP', ticker.symbol),\n last_price: `${ticker.price}`,\n bid_price: `${ticker.price}`,\n ask_price: `${ticker.price}`,\n }),\n ),\n);\n\n// Extract unique symbols from ticker stream\nconst symbolList$ = ticker$.pipe(\n map((tickers) => tickers.map((t) => t.symbol)),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\n// Create a controlled rotation stream for fetching open interest\n// This cycles through all symbols with proper delays, independent of ticker updates\nconst OPEN_INTEREST_CYCLE_DELAY = process.env.OPEN_INTEREST_CYCLE_DELAY\n ? Number(process.env.OPEN_INTEREST_CYCLE_DELAY)\n : 60_000; // 60 seconds between full cycles\n\nconst openInterestRotation$ = combineLatest([symbolList$, requestInterval$]).pipe(\n exhaustMap(([symbols, requestInterval]) =>\n defer(() => {\n console.info(\n `Starting open interest rotation for ${symbols.length} symbols with ${requestInterval}ms interval`,\n );\n return from(symbols).pipe(\n concatMap((symbol, index) =>\n (index > 0 ? timer(requestInterval) : of(0)).pipe(\n mergeMap(() => from(openInterestCache.query(symbol))),\n map((openInterest) => ({\n symbol,\n openInterest: openInterest ?? '0',\n timestamp: Date.now(),\n })),\n catchError((err) => {\n console.warn(`Failed to fetch open interest for ${symbol}:`, err);\n return of(undefined);\n }),\n ),\n ),\n );\n }),\n ),\n filter((x) => !!x),\n shareReplay({ bufferSize: 1000, refCount: true }), // Cache open interest for all symbols\n);\n\n// Convert open interest rotation data to quote format\nconst quoteFromOpenInterest$ = openInterestRotation$.pipe(\n map(\n (data): Partial<IQuote> => ({\n datasource_id: 'ASTER',\n product_id: encodePath('ASTER', 'PERP', data?.symbol),\n open_interest: data?.openInterest,\n }),\n ),\n);\n\n// Funding rate stream - fetches all symbols at once (more efficient than per-symbol)\nconst fundingRate$ = defer(() => getFApiV1PremiumIndex({})).pipe(\n map((data) => {\n // Handle both single object and array responses\n const premiumDataArray = Array.isArray(data) ? data : [data];\n return premiumDataArray;\n }),\n repeat({ delay: OPEN_INTEREST_CYCLE_DELAY }),\n retry({ delay: 5000 }),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\n// Convert funding rate data to quote format\nconst quoteFromFundingRate$ = fundingRate$.pipe(\n mergeMap((premiumDataArray) => premiumDataArray),\n map(\n (premiumData): Partial<IQuote> => ({\n datasource_id: 'ASTER',\n product_id: encodePath('ASTER', 'PERP', premiumData.symbol),\n interest_rate_long: premiumData.lastFundingRate ? `${-+premiumData.lastFundingRate}` : undefined,\n interest_rate_short: premiumData.lastFundingRate,\n }),\n ),\n);\n\nconst quote$ = merge(quoteFromTicker$, quoteFromOpenInterest$, quoteFromFundingRate$).pipe(\n groupBy((quote) => quote.product_id),\n mergeMap((group$) =>\n group$.pipe(\n scan(\n (acc, cur) =>\n Object.assign(acc, cur, {\n datasource_id: 'ASTER',\n product_id: group$.key,\n }),\n {} as Partial<IQuote>,\n ),\n ),\n ),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nif (process.env.WRITE_QUOTE_TO_SQL === 'true') {\n quote$\n .pipe(\n writeToSQL({\n terminal,\n tableName: 'quote',\n writeInterval: 1000,\n conflictKeys: ['datasource_id', 'product_id'],\n }),\n )\n .subscribe();\n\n terminal.channel.publishChannel('quote', { pattern: '^ASTER/' }, (channel_id) => {\n const [datasourceId, productId] = decodePath(channel_id);\n if (!datasourceId || !productId) {\n throw new Error(`Invalid channel_id: ${channel_id}`);\n }\n return quote$.pipe(filter((quote) => quote.product_id === productId));\n });\n}\n"]}
|
package/package.json
CHANGED
package/temp/package-deps.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"apps/vendor-aster/AGENTS.md": "dcd006d19a5c3b73081525d7c0afea68cd6d058b",
|
|
3
|
-
"apps/vendor-aster/CHANGELOG.json": "
|
|
4
|
-
"apps/vendor-aster/CHANGELOG.md": "
|
|
3
|
+
"apps/vendor-aster/CHANGELOG.json": "ed645a9b95139905994bbb3a49d1d28721a31619",
|
|
4
|
+
"apps/vendor-aster/CHANGELOG.md": "7fe7f275a2fc807b58d175dea35ff3afb510e414",
|
|
5
5
|
"apps/vendor-aster/SESSION_NOTES.md": "8a7aaa694e6428ef6e1ef14096e5c3ee00c42152",
|
|
6
6
|
"apps/vendor-aster/config/jest.config.json": "4bb17bde3ee911163a3edb36a6eb71491d80b1bd",
|
|
7
7
|
"apps/vendor-aster/config/rig.json": "f6c7b5537dc77a3170ba9f008bae3b6c3ee11956",
|
|
8
8
|
"apps/vendor-aster/config/typescript.json": "854907e8a821f2050f6533368db160c649c25348",
|
|
9
|
-
"apps/vendor-aster/package.json": "
|
|
9
|
+
"apps/vendor-aster/package.json": "1976fd5c4f36a4fd6627001e889958bcf2100b75",
|
|
10
10
|
"apps/vendor-aster/src/api/private-api.ts": "71f2f794377a45c64f688a663111318e36463bdb",
|
|
11
|
-
"apps/vendor-aster/src/api/public-api.ts": "
|
|
11
|
+
"apps/vendor-aster/src/api/public-api.ts": "4cd51f4b64e0c673fb3840fd3b6b13ddc3a7977e",
|
|
12
12
|
"apps/vendor-aster/src/index.ts": "509ef4c18ec2deb8a6e11ed5682334433cb2bcb2",
|
|
13
13
|
"apps/vendor-aster/src/services/accounts/profile.ts": "fc57e7d2c865790dd257c043c8dd5cf5c641b7fd",
|
|
14
14
|
"apps/vendor-aster/src/services/accounts/spot.ts": "d04bf81c9025cae2b49b1ae0099c9525d888e7af",
|
|
15
15
|
"apps/vendor-aster/src/services/exchange.ts": "71a555ceec0d84b6f12a9c2002834676f1370d51",
|
|
16
16
|
"apps/vendor-aster/src/services/markets/interest_rate.ts": "8512bb506f3d66b16aceb9d153d1b3a4671de5ef",
|
|
17
17
|
"apps/vendor-aster/src/services/markets/product.ts": "027646b5af638bacc0f943cefc14e32e71bbb6ce",
|
|
18
|
-
"apps/vendor-aster/src/services/markets/quote.ts": "
|
|
18
|
+
"apps/vendor-aster/src/services/markets/quote.ts": "ba022abb87d0114a922030bfba56bce1cd23f135",
|
|
19
19
|
"apps/vendor-aster/src/services/orders/cancelOrder.ts": "09a6d3b10a218ad226346dd68d0ff0207083a7f1",
|
|
20
20
|
"apps/vendor-aster/src/services/orders/listOrders.ts": "579e83c45f6c4435ad3f5a2d3fe163a036bb9fd1",
|
|
21
21
|
"apps/vendor-aster/src/services/orders/submitOrder.ts": "9e99af753829d8f75a110cb29042ee3ebe6b724d",
|