import type { Bar, DatafeedConfiguration, HistoryCallback, IDatafeedChartApi, IExternalDatafeed, LibrarySymbolInfo, OnReadyCallback, PeriodParams, ResolutionString, ResolveCallback, SearchSymbolsCallback, SubscribeBarsCallback, } from "#/datafeed-api"; import type { MarketDataStreaming } from "@/api/types"; import { tradeWebSocket } from "./websocket"; /** * 自定义 TradingView Datafeed 实现 * 用于从后端 API 获取 K 线数据 */ export class RWADatafeed extends Datafeeds.UDFCompatibleDatafeed { private subscribers: Map = new Map(); constructor(apiUrl: string) { super(apiUrl); } // /** // * 初始化配置 // */ // onReady(callback: OnReadyCallback): void { // console.log("[RWADatafeed]: onReady called"); // const config: DatafeedConfiguration = { // // 支持的交易所 // exchanges: [ // { value: "", name: "All Exchanges", desc: "" }, // ], // // 支持的品种类型 // symbols_types: [ // { name: "All types", value: "" }, // { name: "Stock", value: "stock" }, // { name: "Crypto", value: "crypto" }, // ], // // 支持的时间周期 // supported_resolutions: [ // "1" as ResolutionString, // "5" as ResolutionString, // "15" as ResolutionString, // "30" as ResolutionString, // "60" as ResolutionString, // "240" as ResolutionString, // "1D" as ResolutionString, // "1W" as ResolutionString, // "1M" as ResolutionString, // ], // supports_marks: false, // supports_timescale_marks: false, // supports_time: true, // }; // setTimeout(() => callback(config), 0); // } // /** // * 搜索品种 // */ // searchSymbols( // userInput: string, // exchange: string, // symbolType: string, // onResult: SearchSymbolsCallback, // ): void { // console.log("[RWADatafeed]: searchSymbols", userInput, exchange, symbolType); // // TODO: 调用后端 API 搜索品种 // // const results = await client.api.market.search.get({ query: { keyword: userInput, exchange, type: symbolType } }) // // 模拟搜索结果 // const results = [ // { // symbol: "AAPL", // full_name: "NASDAQ:AAPL", // description: "Apple Inc.", // exchange: "NASDAQ", // ticker: "AAPL", // type: "stock", // }, // ]; // onResult(results); // } // /** // * 解析品种信息 // */ // resolveSymbol( // symbolName: string, // onResolve: ResolveCallback, // onError: (reason: string) => void, // ): void { // console.log("[RWADatafeed]: resolveSymbol", symbolName); // // TODO: 调用后端 API 获取品种信息 // // const symbolInfo = await client.api.market.symbol[symbolName].get() // // 模拟品种信息 // const symbolInfo: LibrarySymbolInfo = { // name: symbolName, // ticker: symbolName, // description: symbolName, // type: "crypto", // session: "24x7", // exchange: "Binance", // listed_exchange: "Binance", // timezone: "Etc/UTC", // format: "price", // pricescale: 100, // minmov: 1, // has_intraday: true, // has_daily: true, // has_weekly_and_monthly: true, // supported_resolutions: [ // "1" as ResolutionString, // "5" as ResolutionString, // "15" as ResolutionString, // "30" as ResolutionString, // "60" as ResolutionString, // "240" as ResolutionString, // "1D" as ResolutionString, // "1W" as ResolutionString, // "1M" as ResolutionString, // ], // intraday_multipliers: ["1", "5", "15", "30", "60", "240"], // volume_precision: 2, // data_status: "streaming", // }; // setTimeout(() => onResolve(symbolInfo), 0); // } // /** // * 获取历史 K 线数据 // */ // getBars( // symbolInfo: LibrarySymbolInfo, // resolution: ResolutionString, // periodParams: PeriodParams, // onResult: HistoryCallback, // onError: (reason: string) => void, // ): void { // const { from, to, firstDataRequest } = periodParams; // console.log("[RWADatafeed]: getBars", { // symbol: symbolInfo.name, // resolution, // from: new Date(from * 1000).toISOString(), // to: new Date(to * 1000).toISOString(), // firstDataRequest, // }); // // TODO: 调用后端 API 获取 K 线数据 // // client.api.market.kline.get({ // // query: { // // symbol: symbolInfo.name, // // resolution, // // from, // // to, // // } // // }).then(response => { // // const bars = response.data.map(item => ({ // // time: item.time * 1000, // // open: item.open, // // high: item.high, // // low: item.low, // // close: item.close, // // volume: item.volume, // // })) // // onResult(bars, { noData: bars.length === 0 }) // // }).catch(error => { // // onError(error.message) // // }) // // 模拟 K 线数据 // const bars: Bar[] = this.generateMockBars(from, to, resolution); // if (bars.length === 0) { // onResult([], { noData: true }); // } // else { // onResult(bars, { noData: false }); // } // } /** * 订阅实时数据 */ subscribeBars( symbolInfo: LibrarySymbolInfo, resolution: ResolutionString, onTick: SubscribeBarsCallback, subscriberUID: string, onResetCacheNeededCallback: () => void, ): void { console.log("[RWADatafeed]: subscribeBars", { symbol: symbolInfo.name, resolution, subscriberUID }); this.subscribers.set(subscriberUID, { callback: onTick, resolution, symbol: symbolInfo.name }); const wsConnection = tradeWebSocket.getSocket(); wsConnection.send({ action: "subscribe", channels: [{ name: "bar", symbol: symbolInfo.name, resolution, }], }); wsConnection.subscribe((message) => { const data = message.data as any; if (data.type !== "bar") return; const bar: Bar = { time: data.bar.time, open: data.bar.open, high: data.bar.high, low: data.bar.low, close: data.bar.close, volume: data.bar.volume, }; onTick(bar); }); } /** * 取消订阅实时数据 */ unsubscribeBars(subscriberUID: string): void { console.log("[RWADatafeed]: unsubscribeBars", subscriberUID); const subscriber = this.subscribers.get(subscriberUID); const wsConnection = tradeWebSocket.getSocket(); wsConnection.send({ action: "unsubscribe", channels: [{ name: "bar", symbol: subscriber?.symbol || "", }], }); } }