feat: 重构 TradingView 数据源实现,更新数据源类名为 RWADatafeed;优化 TradingViewChart 组件的使用方式

This commit is contained in:
2025-12-30 02:03:45 +07:00
parent 4a313f2af0
commit bdd2812ae9
3 changed files with 62 additions and 29 deletions

View File

@@ -13,16 +13,19 @@ import type {
SubscribeBarsCallback,
} from "#/datafeed-api";
const { VITE_TRADINGVIEW_DATA_API_URL } = useEnv();
/**
* 自定义 TradingView Datafeed 实现
* 用于从后端 API 获取 K 线数据
*/
export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
private apiUrl: string;
export class RWADatafeed extends Datafeeds.UDFCompatibleDatafeed {
private apiUrl: string = VITE_TRADINGVIEW_DATA_API_URL;
private updateInterval: number;
private subscribers: Map<string, { callback: SubscribeBarsCallback; resolution: ResolutionString }> = new Map();
constructor(apiUrl: string, updateInterval = 1000) {
super(apiUrl);
this.apiUrl = apiUrl;
this.updateInterval = updateInterval;
}
@@ -31,7 +34,7 @@ export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
* 初始化配置
*/
onReady(callback: OnReadyCallback): void {
console.log("[CustomDatafeed]: onReady called");
console.log("[RWADatafeed]: onReady called");
const config: DatafeedConfiguration = {
// 支持的交易所
@@ -73,7 +76,7 @@ export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
symbolType: string,
onResult: SearchSymbolsCallback,
): void {
console.log("[CustomDatafeed]: searchSymbols", userInput, exchange, symbolType);
console.log("[RWADatafeed]: searchSymbols", userInput, exchange, symbolType);
// TODO: 调用后端 API 搜索品种
// const results = await client.api.market.search.get({ query: { keyword: userInput, exchange, type: symbolType } })
@@ -101,7 +104,7 @@ export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
onResolve: ResolveCallback,
onError: (reason: string) => void,
): void {
console.log("[CustomDatafeed]: resolveSymbol", symbolName);
console.log("[RWADatafeed]: resolveSymbol", symbolName);
// TODO: 调用后端 API 获取品种信息
// const symbolInfo = await client.api.market.symbol[symbolName].get()
@@ -152,7 +155,7 @@ export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
onError: (reason: string) => void,
): void {
const { from, to, firstDataRequest } = periodParams;
console.log("[CustomDatafeed]: getBars", {
console.log("[RWADatafeed]: getBars", {
symbol: symbolInfo.name,
resolution,
from: new Date(from * 1000).toISOString(),
@@ -203,7 +206,7 @@ export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
listenerGuid: string,
onResetCacheNeededCallback: () => void,
): void {
console.log("[CustomDatafeed]: subscribeBars", {
console.log("[RWADatafeed]: subscribeBars", {
symbol: symbolInfo.name,
resolution,
listenerGuid,
@@ -231,7 +234,7 @@ export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
* 取消订阅实时数据
*/
unsubscribeBars(listenerGuid: string): void {
console.log("[CustomDatafeed]: unsubscribeBars", listenerGuid);
console.log("[RWADatafeed]: unsubscribeBars", listenerGuid);
this.subscribers.delete(listenerGuid);
// TODO: 关闭 WebSocket 连接

View File

@@ -1,16 +1,18 @@
import type { ChartingLibraryWidgetOptions } from "#/charting_library";
import type { ChartingLibraryWidgetOptions, IChartingLibraryWidget } from "#/charting_library";
import type { ResolutionString } from "#/datafeed-api";
import { CustomDatafeed } from "./datafeed";
import { RWADatafeed } from "./datafeed";
const { VITE_TRADINGVIEW_LIBRARY_URL, VITE_TRADINGVIEW_DATA_API_URL } = useEnv();
const datafeed = new RWADatafeed(VITE_TRADINGVIEW_DATA_API_URL, 60000);
const defaultOptions = {
container: "tradingview_chart_container",
locale: "zh",
library_path: `${VITE_TRADINGVIEW_LIBRARY_URL}/charting_library/`,
datafeed: new CustomDatafeed(VITE_TRADINGVIEW_DATA_API_URL, 60000),
symbol: "AAPL",
datafeed,
interval: "1D" as ResolutionString,
symbol: "AAPL",
debug: true,
autosize: true,
// 禁用移动端不友好的功能
@@ -18,18 +20,46 @@ const defaultOptions = {
"left_toolbar", // 隐藏左侧绘图工具栏
"timeframes_toolbar", // 隐藏底部时间框架工具栏
"volume_force_overlay", // 禁用成交量覆盖在价格图表上
// 禁用顶部工具栏
"header_widget",
"header_compare",
"header_symbol_search",
"header_fullscreen_button",
"header_settings",
// "header_widget", // 禁用顶部工具栏
"header_compare", // 禁用比较功能
"header_symbol_search", // 禁用符号搜索
// "header_fullscreen_button", // 禁用全屏按钮
// "header_settings", // 禁用设置按钮
"header_screenshot", // 禁用截图功能
"header_undo_redo", // 禁用撤销/重做按钮
"header_saveload", // 禁用保存/加载按钮
"control_bar", // 禁用底部控制栏
"edit_buttons_in_legend", // 禁用图例中的编辑按钮
"border_around_the_chart", // 移除图表边框
"main_series_scale_menu", // 禁用主序列比例菜单
"display_market_status", // 禁用市场状态显示
// "create_volume_indicator_by_default", // 默认不创建成交量指标
// "go_to_date", // 禁用跳转到日期功能
"pane_context_menu", // 禁用窗格右键菜单
// "legend_context_menu", // 禁用图例右键菜单
// "legend_inplace_edit", // 禁用图例内联编辑
// "legend_widget", // 禁用图例小部件
],
// 启用移动端友好的功能
enabled_features: [
"show_zoom_and_move_buttons_on_touch", // 在触摸设备上显示缩放和移动按钮
"adaptive_logo", // 在小屏幕设备上隐藏 TradingView logo
"hide_left_toolbar_by_default", // 默认隐藏左侧工具栏
"items_favoriting", // 启用收藏功能
],
// 图表样式配置
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#0ecb81", // 涨(绿色)
"mainSeriesProperties.candleStyle.downColor": "#f6465d", // 跌(红色)
"mainSeriesProperties.candleStyle.borderUpColor": "#0ecb81",
"mainSeriesProperties.candleStyle.borderDownColor": "#f6465d",
"mainSeriesProperties.candleStyle.wickUpColor": "#0ecb81",
"mainSeriesProperties.candleStyle.wickDownColor": "#f6465d",
},
// 时间刻度配置
time_scale: {
min_bar_spacing: 6,
},
} satisfies ChartingLibraryWidgetOptions;
export const TradingViewChart = defineComponent({
@@ -37,7 +67,6 @@ export const TradingViewChart = defineComponent({
props: {
symbol: {
type: String,
default: "BTCUSDT",
},
options: {
type: Object as PropType<Partial<ChartingLibraryWidgetOptions>>,
@@ -46,20 +75,25 @@ export const TradingViewChart = defineComponent({
},
setup(props) {
const el = ref<HTMLDivElement | null>(null);
const { isDark } = useTheme();
const widget = ref<IChartingLibraryWidget | null>(null);
onMounted(() => {
// eslint-disable-next-line new-cap
const widget = new TradingView.widget({
widget.value = new TradingView.widget({
...defaultOptions,
...props.options,
theme: isDark.value ? "dark" : "light",
container: el.value!,
});
widget.onChartReady(() => {
widget.value.onChartReady(() => {
console.log("[TradingView]: Chart is ready");
});
console.log("widget", widget.value.activeChart);
});
return () => <div ref={el} class="w-full h-70" />;
return () => <div ref={el} class="w-full h-100" />;
},
});

View File

@@ -33,12 +33,7 @@ const mode = ref<"buy" | "sell">("buy");
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<TradingViewChart
class="mb-5" :options="{
symbol: 'BTC/USDT',
}"
/>
<div class="grid grid-cols-5">
<div class="grid grid-cols-5 px-4">
<div class="col-span-3 space-y-2">
<TradeSwitch v-model:active="mode" />
<TradeWay />
@@ -52,7 +47,8 @@ const mode = ref<"buy" | "sell">("buy");
</div>
<div class="col-span-2" />
</div>
<div class="mt-6">
<TradingViewChart class="my-5" />
<div class="mt-6 px-4">
<OrdersPanel />
</div>
</ion-content>