feat: 重构 TradingView 数据源实现,更新数据源类名为 RWADatafeed;优化 TradingViewChart 组件的使用方式
This commit is contained in:
@@ -13,16 +13,19 @@ import type {
|
|||||||
SubscribeBarsCallback,
|
SubscribeBarsCallback,
|
||||||
} from "#/datafeed-api";
|
} from "#/datafeed-api";
|
||||||
|
|
||||||
|
const { VITE_TRADINGVIEW_DATA_API_URL } = useEnv();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义 TradingView Datafeed 实现
|
* 自定义 TradingView Datafeed 实现
|
||||||
* 用于从后端 API 获取 K 线数据
|
* 用于从后端 API 获取 K 线数据
|
||||||
*/
|
*/
|
||||||
export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
|
export class RWADatafeed extends Datafeeds.UDFCompatibleDatafeed {
|
||||||
private apiUrl: string;
|
private apiUrl: string = VITE_TRADINGVIEW_DATA_API_URL;
|
||||||
private updateInterval: number;
|
private updateInterval: number;
|
||||||
private subscribers: Map<string, { callback: SubscribeBarsCallback; resolution: ResolutionString }> = new Map();
|
private subscribers: Map<string, { callback: SubscribeBarsCallback; resolution: ResolutionString }> = new Map();
|
||||||
|
|
||||||
constructor(apiUrl: string, updateInterval = 1000) {
|
constructor(apiUrl: string, updateInterval = 1000) {
|
||||||
|
super(apiUrl);
|
||||||
this.apiUrl = apiUrl;
|
this.apiUrl = apiUrl;
|
||||||
this.updateInterval = updateInterval;
|
this.updateInterval = updateInterval;
|
||||||
}
|
}
|
||||||
@@ -31,7 +34,7 @@ export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
|
|||||||
* 初始化配置
|
* 初始化配置
|
||||||
*/
|
*/
|
||||||
onReady(callback: OnReadyCallback): void {
|
onReady(callback: OnReadyCallback): void {
|
||||||
console.log("[CustomDatafeed]: onReady called");
|
console.log("[RWADatafeed]: onReady called");
|
||||||
|
|
||||||
const config: DatafeedConfiguration = {
|
const config: DatafeedConfiguration = {
|
||||||
// 支持的交易所
|
// 支持的交易所
|
||||||
@@ -73,7 +76,7 @@ export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
|
|||||||
symbolType: string,
|
symbolType: string,
|
||||||
onResult: SearchSymbolsCallback,
|
onResult: SearchSymbolsCallback,
|
||||||
): void {
|
): void {
|
||||||
console.log("[CustomDatafeed]: searchSymbols", userInput, exchange, symbolType);
|
console.log("[RWADatafeed]: searchSymbols", userInput, exchange, symbolType);
|
||||||
|
|
||||||
// TODO: 调用后端 API 搜索品种
|
// TODO: 调用后端 API 搜索品种
|
||||||
// const results = await client.api.market.search.get({ query: { keyword: userInput, exchange, type: symbolType } })
|
// 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,
|
onResolve: ResolveCallback,
|
||||||
onError: (reason: string) => void,
|
onError: (reason: string) => void,
|
||||||
): void {
|
): void {
|
||||||
console.log("[CustomDatafeed]: resolveSymbol", symbolName);
|
console.log("[RWADatafeed]: resolveSymbol", symbolName);
|
||||||
|
|
||||||
// TODO: 调用后端 API 获取品种信息
|
// TODO: 调用后端 API 获取品种信息
|
||||||
// const symbolInfo = await client.api.market.symbol[symbolName].get()
|
// const symbolInfo = await client.api.market.symbol[symbolName].get()
|
||||||
@@ -152,7 +155,7 @@ export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
|
|||||||
onError: (reason: string) => void,
|
onError: (reason: string) => void,
|
||||||
): void {
|
): void {
|
||||||
const { from, to, firstDataRequest } = periodParams;
|
const { from, to, firstDataRequest } = periodParams;
|
||||||
console.log("[CustomDatafeed]: getBars", {
|
console.log("[RWADatafeed]: getBars", {
|
||||||
symbol: symbolInfo.name,
|
symbol: symbolInfo.name,
|
||||||
resolution,
|
resolution,
|
||||||
from: new Date(from * 1000).toISOString(),
|
from: new Date(from * 1000).toISOString(),
|
||||||
@@ -203,7 +206,7 @@ export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
|
|||||||
listenerGuid: string,
|
listenerGuid: string,
|
||||||
onResetCacheNeededCallback: () => void,
|
onResetCacheNeededCallback: () => void,
|
||||||
): void {
|
): void {
|
||||||
console.log("[CustomDatafeed]: subscribeBars", {
|
console.log("[RWADatafeed]: subscribeBars", {
|
||||||
symbol: symbolInfo.name,
|
symbol: symbolInfo.name,
|
||||||
resolution,
|
resolution,
|
||||||
listenerGuid,
|
listenerGuid,
|
||||||
@@ -231,7 +234,7 @@ export class CustomDatafeed implements IDatafeedChartApi, IExternalDatafeed {
|
|||||||
* 取消订阅实时数据
|
* 取消订阅实时数据
|
||||||
*/
|
*/
|
||||||
unsubscribeBars(listenerGuid: string): void {
|
unsubscribeBars(listenerGuid: string): void {
|
||||||
console.log("[CustomDatafeed]: unsubscribeBars", listenerGuid);
|
console.log("[RWADatafeed]: unsubscribeBars", listenerGuid);
|
||||||
this.subscribers.delete(listenerGuid);
|
this.subscribers.delete(listenerGuid);
|
||||||
|
|
||||||
// TODO: 关闭 WebSocket 连接
|
// TODO: 关闭 WebSocket 连接
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import type { ChartingLibraryWidgetOptions } from "#/charting_library";
|
import type { ChartingLibraryWidgetOptions, IChartingLibraryWidget } from "#/charting_library";
|
||||||
import type { ResolutionString } from "#/datafeed-api";
|
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 { VITE_TRADINGVIEW_LIBRARY_URL, VITE_TRADINGVIEW_DATA_API_URL } = useEnv();
|
||||||
|
|
||||||
|
const datafeed = new RWADatafeed(VITE_TRADINGVIEW_DATA_API_URL, 60000);
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
container: "tradingview_chart_container",
|
container: "tradingview_chart_container",
|
||||||
locale: "zh",
|
locale: "zh",
|
||||||
library_path: `${VITE_TRADINGVIEW_LIBRARY_URL}/charting_library/`,
|
library_path: `${VITE_TRADINGVIEW_LIBRARY_URL}/charting_library/`,
|
||||||
datafeed: new CustomDatafeed(VITE_TRADINGVIEW_DATA_API_URL, 60000),
|
datafeed,
|
||||||
symbol: "AAPL",
|
|
||||||
interval: "1D" as ResolutionString,
|
interval: "1D" as ResolutionString,
|
||||||
|
symbol: "AAPL",
|
||||||
debug: true,
|
debug: true,
|
||||||
autosize: true,
|
autosize: true,
|
||||||
// 禁用移动端不友好的功能
|
// 禁用移动端不友好的功能
|
||||||
@@ -18,18 +20,46 @@ const defaultOptions = {
|
|||||||
"left_toolbar", // 隐藏左侧绘图工具栏
|
"left_toolbar", // 隐藏左侧绘图工具栏
|
||||||
"timeframes_toolbar", // 隐藏底部时间框架工具栏
|
"timeframes_toolbar", // 隐藏底部时间框架工具栏
|
||||||
"volume_force_overlay", // 禁用成交量覆盖在价格图表上
|
"volume_force_overlay", // 禁用成交量覆盖在价格图表上
|
||||||
// 禁用顶部工具栏
|
// "header_widget", // 禁用顶部工具栏
|
||||||
"header_widget",
|
"header_compare", // 禁用比较功能
|
||||||
"header_compare",
|
"header_symbol_search", // 禁用符号搜索
|
||||||
"header_symbol_search",
|
// "header_fullscreen_button", // 禁用全屏按钮
|
||||||
"header_fullscreen_button",
|
// "header_settings", // 禁用设置按钮
|
||||||
"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: [
|
enabled_features: [
|
||||||
"show_zoom_and_move_buttons_on_touch", // 在触摸设备上显示缩放和移动按钮
|
"show_zoom_and_move_buttons_on_touch", // 在触摸设备上显示缩放和移动按钮
|
||||||
"adaptive_logo", // 在小屏幕设备上隐藏 TradingView logo
|
"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;
|
} satisfies ChartingLibraryWidgetOptions;
|
||||||
|
|
||||||
export const TradingViewChart = defineComponent({
|
export const TradingViewChart = defineComponent({
|
||||||
@@ -37,7 +67,6 @@ export const TradingViewChart = defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
symbol: {
|
symbol: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "BTCUSDT",
|
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
type: Object as PropType<Partial<ChartingLibraryWidgetOptions>>,
|
type: Object as PropType<Partial<ChartingLibraryWidgetOptions>>,
|
||||||
@@ -46,20 +75,25 @@ export const TradingViewChart = defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const el = ref<HTMLDivElement | null>(null);
|
const el = ref<HTMLDivElement | null>(null);
|
||||||
|
const { isDark } = useTheme();
|
||||||
|
const widget = ref<IChartingLibraryWidget | null>(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
const widget = new TradingView.widget({
|
widget.value = new TradingView.widget({
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
...props.options,
|
...props.options,
|
||||||
|
theme: isDark.value ? "dark" : "light",
|
||||||
container: el.value!,
|
container: el.value!,
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.onChartReady(() => {
|
widget.value.onChartReady(() => {
|
||||||
console.log("[TradingView]: Chart is ready");
|
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" />;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,12 +33,7 @@ const mode = ref<"buy" | "sell">("buy");
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content :fullscreen="true">
|
<ion-content :fullscreen="true">
|
||||||
<TradingViewChart
|
<div class="grid grid-cols-5 px-4">
|
||||||
class="mb-5" :options="{
|
|
||||||
symbol: 'BTC/USDT',
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
<div class="grid grid-cols-5">
|
|
||||||
<div class="col-span-3 space-y-2">
|
<div class="col-span-3 space-y-2">
|
||||||
<TradeSwitch v-model:active="mode" />
|
<TradeSwitch v-model:active="mode" />
|
||||||
<TradeWay />
|
<TradeWay />
|
||||||
@@ -52,7 +47,8 @@ const mode = ref<"buy" | "sell">("buy");
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-span-2" />
|
<div class="col-span-2" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6">
|
<TradingViewChart class="my-5" />
|
||||||
|
<div class="mt-6 px-4">
|
||||||
<OrdersPanel />
|
<OrdersPanel />
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
Reference in New Issue
Block a user