You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

655 lines
20 KiB

4 weeks ago
  1. <template>
  2. <view style="width: 750rpx; height: 750rpx;">
  3. <l-echart ref="chartRef" @finished="initChart"></l-echart>
  4. </view>
  5. </template>
  6. <script setup>
  7. import { ref, computed, onMounted } from 'vue';
  8. const chartRef = ref(null)
  9. // 获取系统信息,替代 window.innerWidth
  10. const systemInfo = uni.getSystemInfoSync()
  11. const screenWidth = ref(systemInfo.screenWidth || 375) // 默认值 375px
  12. // 生成30天的交易信号数据
  13. const generateAIGoldBullData = () => {
  14. const data = []
  15. for (let i = 0; i < 30; i++) {
  16. // 模拟交易信号 [索引, 买入信号, 卖出信号, 持有信号, 强度, 成交量]
  17. const buySignal = Math.random() > 0.7 ? 1 : 0 // 30%概率买入信号
  18. const sellSignal = Math.random() > 0.8 ? 1 : 0 // 20%概率卖出信号
  19. const holdSignal = Math.random() > 0.5 ? 1 : 0 // 50%概率持有信号
  20. const strength = Math.floor(Math.random() * 3) + 1 // 信号强度1-3
  21. const volume = Math.floor(Math.random() * 2000) + 500 // 成交量500-2500
  22. data.push([i, buySignal, sellSignal, holdSignal, strength, volume])
  23. }
  24. return data
  25. }
  26. // 添加缺失的变量定义
  27. var option
  28. const AIGoldBull = ref({
  29. JN: generateAIGoldBullData()
  30. })
  31. // 模拟多语言数据
  32. const t = ref({
  33. suoxie: 'zh',
  34. tianxian: '天线',
  35. feixian: '飞线',
  36. zhoongxian: '中线',
  37. liuxian: '流线',
  38. Klinetext_5: 'K线5',
  39. Klinetext_6: 'K线6',
  40. maipan: '买盘',
  41. maipan1: '卖盘'
  42. })
  43. // 生成30天的模拟K线数据 [日期, 开盘, 最高, 最低, 收盘]
  44. const generateKLineData = () => {
  45. const data = []
  46. let basePrice = 2450 // 黄金基础价格
  47. for (let i = 0; i < 30; i++) {
  48. const date = new Date(2024, 0, i + 1).toISOString().split('T')[0]
  49. // 模拟价格波动
  50. const volatility = (Math.random() - 0.5) * 50 // ±25的波动
  51. const open = basePrice + volatility
  52. const highVolatility = Math.random() * 30 + 10 // 10-40的向上波动
  53. const lowVolatility = Math.random() * 30 + 10 // 10-40的向下波动
  54. const high = Math.max(open, open + highVolatility)
  55. const low = Math.min(open, open - lowVolatility)
  56. const closeVolatility = (Math.random() - 0.5) * 20
  57. const close = Math.max(low, Math.min(high, open + closeVolatility))
  58. data.push([date,
  59. Math.round(open * 100) / 100,
  60. Math.round(high * 100) / 100,
  61. Math.round(low * 100) / 100,
  62. Math.round(close * 100) / 100
  63. ])
  64. basePrice = close // 下一天的基础价格
  65. }
  66. return data
  67. }
  68. // 生成30天的成交量和技术指标数据 [日期, 成交量1, 成交量2, 指标1, 指标2, 指标3]
  69. const generateWaveVolData = () => {
  70. const data = []
  71. for (let i = 0; i < 30; i++) {
  72. const date = new Date(2024, 0, i + 1).toISOString().split('T')[0]
  73. // 模拟成交量数据
  74. const vol1 = Math.floor(Math.random() * 2000) + 800 // 800-2800
  75. const vol2 = Math.floor(Math.random() * 1500) + 600 // 600-2100
  76. // 模拟技术指标
  77. const indicator1 = Math.floor(Math.random() * 30) + 40 // 40-70
  78. const indicator2 = Math.floor(Math.random() * 40) + 50 // 50-90
  79. const indicator3 = Math.floor(Math.random() * 35) + 60 // 60-95
  80. data.push([date, vol1, vol2, indicator1, indicator2, indicator3])
  81. }
  82. return data
  83. }
  84. // 生成30天的移动平均线数据 [MA5, MA10, MA20, MA30]
  85. const generateFTLineData = () => {
  86. const data = []
  87. let ma5Base = 2450
  88. let ma10Base = 2445
  89. let ma20Base = 2440
  90. let ma30Base = 2435
  91. for (let i = 0; i < 30; i++) {
  92. // 模拟移动平均线的平滑变化
  93. ma5Base += (Math.random() - 0.5) * 10
  94. ma10Base += (Math.random() - 0.5) * 8
  95. ma20Base += (Math.random() - 0.5) * 6
  96. ma30Base += (Math.random() - 0.5) * 4
  97. data.push([
  98. Math.round(ma5Base * 100) / 100,
  99. Math.round(ma10Base * 100) / 100,
  100. Math.round(ma20Base * 100) / 100,
  101. Math.round(ma30Base * 100) / 100
  102. ])
  103. }
  104. return data
  105. }
  106. // 生成模拟数据
  107. const mockKLineData = generateKLineData()
  108. const mockWaveVolData = generateWaveVolData()
  109. const mockFTLineData = generateFTLineData()
  110. // 生成RSI指标数据 (相对强弱指数)
  111. const generateRSIData = () => {
  112. const data = []
  113. for (let i = 0; i < 30; i++) {
  114. const rsi = Math.random() * 60 + 20 // RSI值在20-80之间
  115. data.push(Math.round(rsi * 100) / 100)
  116. }
  117. return data
  118. }
  119. // 生成MACD指标数据
  120. const generateMACDData = () => {
  121. const data = []
  122. for (let i = 0; i < 30; i++) {
  123. const macd = (Math.random() - 0.5) * 20 // MACD值在-10到10之间
  124. const signal = (Math.random() - 0.5) * 15 // 信号线
  125. const histogram = macd - signal // 柱状图
  126. data.push([
  127. Math.round(macd * 100) / 100,
  128. Math.round(signal * 100) / 100,
  129. Math.round(histogram * 100) / 100
  130. ])
  131. }
  132. return data
  133. }
  134. // 生成布林带数据
  135. const generateBollingerData = () => {
  136. const data = []
  137. let middleLine = 2450
  138. for (let i = 0; i < 30; i++) {
  139. middleLine += (Math.random() - 0.5) * 10
  140. const upperBand = middleLine + Math.random() * 30 + 20 // 上轨
  141. const lowerBand = middleLine - Math.random() * 30 - 20 // 下轨
  142. data.push([
  143. Math.round(upperBand * 100) / 100,
  144. Math.round(middleLine * 100) / 100,
  145. Math.round(lowerBand * 100) / 100
  146. ])
  147. }
  148. return data
  149. }
  150. // 生成成交量分析数据
  151. const generateVolumeAnalysisData = () => {
  152. const data = []
  153. for (let i = 0; i < 30; i++) {
  154. const buyVolume = Math.floor(Math.random() * 1500) + 500 // 买入量
  155. const sellVolume = Math.floor(Math.random() * 1500) + 500 // 卖出量
  156. const netVolume = buyVolume - sellVolume // 净买入量
  157. data.push([buyVolume, sellVolume, netVolume])
  158. }
  159. return data
  160. }
  161. // 生成市场情绪数据
  162. const generateMarketSentimentData = () => {
  163. const sentiments = ['极度恐慌', '恐慌', '中性', '贪婪', '极度贪婪']
  164. const data = []
  165. for (let i = 0; i < 30; i++) {
  166. const sentimentIndex = Math.floor(Math.random() * 100) // 0-100的情绪指数
  167. const sentimentLabel = sentiments[Math.floor(sentimentIndex / 20)]
  168. data.push({
  169. date: new Date(2024, 0, i + 1).toISOString().split('T')[0],
  170. index: sentimentIndex,
  171. label: sentimentLabel,
  172. fearGreedRatio: Math.random() * 100
  173. })
  174. }
  175. return data
  176. }
  177. // 生成重要新闻事件数据
  178. const generateNewsEventsData = () => {
  179. const events = [
  180. '美联储利率决议',
  181. '非农就业数据发布',
  182. '通胀数据公布',
  183. '地缘政治紧张',
  184. '央行政策变化',
  185. '经济数据超预期',
  186. '市场技术突破',
  187. '大宗商品价格波动'
  188. ]
  189. const data = []
  190. for (let i = 0; i < 10; i++) { // 生成10个随机事件
  191. const randomDay = Math.floor(Math.random() * 30) + 1
  192. const event = events[Math.floor(Math.random() * events.length)]
  193. const impact = Math.floor(Math.random() * 5) + 1 // 影响力1-5
  194. data.push({
  195. date: new Date(2024, 0, randomDay).toISOString().split('T')[0],
  196. event: event,
  197. impact: impact,
  198. type: Math.random() > 0.5 ? 'positive' : 'negative'
  199. })
  200. }
  201. return data.sort((a, b) => new Date(a.date) - new Date(b.date))
  202. }
  203. // 生成价格预测数据
  204. const generatePricePredictionData = () => {
  205. const data = []
  206. let currentPrice = 2450
  207. for (let i = 0; i < 7; i++) { // 未来7天预测
  208. const date = new Date(2024, 1, i + 1).toISOString().split('T')[0] // 2月份
  209. // 模拟AI预测的价格区间
  210. const prediction = currentPrice + (Math.random() - 0.5) * 100
  211. const confidence = Math.random() * 40 + 60 // 60-100%的置信度
  212. const upperBound = prediction + Math.random() * 50
  213. const lowerBound = prediction - Math.random() * 50
  214. data.push({
  215. date: date,
  216. predicted_price: Math.round(prediction * 100) / 100,
  217. confidence: Math.round(confidence),
  218. upper_bound: Math.round(upperBound * 100) / 100,
  219. lower_bound: Math.round(lowerBound * 100) / 100
  220. })
  221. currentPrice = prediction
  222. }
  223. return data
  224. }
  225. // 模拟提取的绘图数据
  226. const extractedDrawData = {
  227. KLine20: mockKLineData,
  228. WAVEVOL: mockWaveVolData,
  229. FTLINE: mockFTLineData,
  230. RSI: generateRSIData(),
  231. MACD: generateMACDData(),
  232. BOLLINGER: generateBollingerData(),
  233. VOLUME_ANALYSIS: generateVolumeAnalysisData(),
  234. MARKET_SENTIMENT: generateMarketSentimentData(),
  235. NEWS_EVENTS: generateNewsEventsData(),
  236. PRICE_PREDICTION: generatePricePredictionData()
  237. }
  238. const fnShowEcharts4 = (extractedDrawData) => {
  239. const splitData = (b) => {
  240. const a = JSON.parse(JSON.stringify(b))
  241. let categoryData = []
  242. let values = []
  243. for (let i = 0; i < a.length; i++) {
  244. categoryData.push(a[i].splice(0, 1)[0])
  245. values.push(a[i])
  246. }
  247. return {
  248. categoryData,
  249. values
  250. }
  251. }
  252. var bodongliang = splitData(extractedDrawData.WAVEVOL)
  253. function bodongliangData(values, i) {
  254. return values.map((subArray) => subArray[i])
  255. }
  256. function calculateMA(index, data) {
  257. let result = []
  258. if (data.FTLINE) {
  259. data.FTLINE.forEach((item) => {
  260. result.push(item[index])
  261. })
  262. }
  263. return result
  264. }
  265. function vwToPx(vw) {
  266. return (screenWidth.value * vw) / 100
  267. }
  268. var dealData = splitData(extractedDrawData.KLine20)
  269. var dealGnBullData = AIGoldBull.value.JN
  270. const textEcharts = t.value
  271. const firstLegend = computed(() => {
  272. if (screenWidth.value < 768) {
  273. if (textEcharts.suoxie === 'en' || textEcharts.suoxie === 'th') {
  274. return '2%'
  275. } else if (textEcharts.suoxie === 'kr') {
  276. return '2%'
  277. } else {
  278. return '2%'
  279. }
  280. } else {
  281. return textEcharts.suoxie === 'en' ||
  282. textEcharts.suoxie === 'th' ||
  283. textEcharts.suoxie === 'kr'
  284. ? '9%'
  285. : '9%'
  286. }
  287. })
  288. const processBarData = (data) => {
  289. const barData = []
  290. data.forEach((item) => {
  291. let color
  292. switch (item[4]) {
  293. case 1:
  294. color = '#13E113'
  295. break
  296. case 2:
  297. color = '#FF0E00'
  298. break
  299. case 3:
  300. color = '#0000FE'
  301. break
  302. case 4:
  303. color = '#1397FF'
  304. break
  305. }
  306. barData.push({
  307. value: item[5],
  308. itemStyle: {
  309. normal: {
  310. color: color
  311. }
  312. }
  313. })
  314. })
  315. return { barData }
  316. }
  317. const { barData } = processBarData(dealGnBullData)
  318. option = {
  319. tooltip: {
  320. trigger: 'axis',
  321. axisPointer: {
  322. type: 'cross'
  323. },
  324. backgroundColor: 'rgba(119, 120, 125, 0.6)',
  325. borderWidth: 1,
  326. borderColor: '#77787D',
  327. padding: 10,
  328. textStyle: {
  329. color: '#fff'
  330. }
  331. },
  332. axisPointer: {
  333. link: [
  334. {
  335. xAxisIndex: 'all'
  336. }
  337. ],
  338. label: {
  339. backgroundColor: '#77787D'
  340. }
  341. },
  342. toolbox: {
  343. show: false
  344. },
  345. grid: [
  346. {
  347. left: screenWidth.value > 768 ? '10%' : '12%',
  348. right: screenWidth.value > 768 ? '4%' : '6%',
  349. top: screenWidth.value > 768 ? '10%' : '12%',
  350. height: screenWidth.value > 768 ? '35%' : '34%',
  351. containLabel: false
  352. },
  353. {
  354. left: screenWidth.value > 768 ? '10%' : '12%',
  355. right: screenWidth.value > 768 ? '4%' : '6%',
  356. top: screenWidth.value > 768 ? '48%' : '48%',
  357. height: screenWidth.value > 768 ? '19%' : '21%',
  358. containLabel: false
  359. },
  360. {
  361. left: screenWidth.value > 768 ? '10%' : '12%',
  362. right: screenWidth.value > 768 ? '4%' : '6%',
  363. top: screenWidth.value > 768 ? '70%' : '71%',
  364. height: screenWidth.value > 768 ? '19%' : '21%',
  365. containLabel: false
  366. }
  367. ],
  368. xAxis: [
  369. {
  370. type: 'category',
  371. data: dealData.categoryData,
  372. boundaryGap: true,
  373. axisLine: { onZero: false },
  374. splitLine: { show: false },
  375. min: 'dataMin',
  376. max: 'dataMax',
  377. axisPointer: {
  378. z: 100,
  379. label: {
  380. show: false // 不显示标签
  381. }
  382. },
  383. axisLine: {
  384. lineStyle: {
  385. color: 'black'
  386. }
  387. }, //
  388. axisLabel: { show: false },
  389. axisTick: { show: false }
  390. },
  391. {
  392. type: 'category',
  393. gridIndex: 1,
  394. data: dealData.categoryData,
  395. boundaryGap: true,
  396. axisPointer: {
  397. z: 100,
  398. label: {
  399. show: false // 不显示标签
  400. }
  401. },
  402. axisLine: { lineStyle: { color: 'black' } },
  403. axisLabel: {
  404. show: false,
  405. interval: 'auto'
  406. },
  407. axisTick: { show: false }
  408. },
  409. {
  410. type: 'category',
  411. gridIndex: 2,
  412. data: dealData.categoryData,
  413. boundaryGap: true,
  414. axisLine: { lineStyle: { color: 'black' } },
  415. axisLabel: {
  416. show: true,
  417. interval: 'auto',
  418. fontSize: screenWidth.value > 768 ? 15 : 9
  419. },
  420. axisTick: { show: false }
  421. }
  422. ],
  423. yAxis: [
  424. {
  425. scale: true,
  426. gridIndex: 0,
  427. position: 'left',
  428. axisLabel: {
  429. inside: false,
  430. align: 'right',
  431. fontSize: screenWidth.value > 768 ? 15 : 9
  432. },
  433. axisLine: {
  434. show: true,
  435. lineStyle: {
  436. fontSize: '',
  437. color: 'black'
  438. }
  439. },
  440. axisTick: { show: false },
  441. splitLine: { show: false }
  442. },
  443. {
  444. scale: true,
  445. gridIndex: 1,
  446. splitNumber: 4,
  447. min: 0,
  448. minInterval: 1,
  449. axisLabel: {
  450. show: true,
  451. fontSize: screenWidth.value > 768 ? 15 : 9,
  452. margin: 8,
  453. },
  454. axisLine: { show: true, lineStyle: { color: 'black' } },
  455. axisTick: { show: false },
  456. splitLine: { show: true, lineStyle: { type: 'dashed' } },
  457. boundaryGap: ['20%', '20%']
  458. },
  459. {
  460. scale: true,
  461. gridIndex: 2,
  462. splitNumber: 2,
  463. axisLabel: {
  464. show: true,
  465. fontSize: screenWidth.value > 768 ? 15 : 9
  466. },
  467. axisLine: { show: true, lineStyle: { color: 'black' } },
  468. axisTick: { show: false },
  469. splitLine: { show: false }
  470. }
  471. ],
  472. dataZoom: [
  473. {
  474. type: 'inside',
  475. xAxisIndex: [0, 1, 2],
  476. start: 50,
  477. end: 100
  478. },
  479. {
  480. show: true,
  481. xAxisIndex: [0, 1, 2],
  482. type: 'slider',
  483. start: 50,
  484. end: 100
  485. }
  486. ],
  487. series: [
  488. {
  489. type: 'candlestick',
  490. name: '日K',
  491. xAxisIndex: 0,
  492. yAxisIndex: 0,
  493. data: dealData.values,
  494. itemStyle: {
  495. normal: {
  496. color0: 'red',
  497. color: 'green',
  498. borderColor0: 'red',
  499. borderColor: 'green'
  500. }
  501. },
  502. gridIndex: 1
  503. },
  504. {
  505. name: '成交量',
  506. type: 'bar',
  507. barWidth: '70%',
  508. xAxisIndex: 1,
  509. yAxisIndex: 1,
  510. data: barData,
  511. },
  512. // {
  513. // name: textEcharts.feixian,
  514. // type: 'line',
  515. // data: calculateMA(1, extractedDrawData),
  516. // smooth: true,
  517. // symbol: 'none',
  518. // xAxisIndex: 2,
  519. // yAxisIndex: 2,
  520. // itemStyle: {
  521. // normal: {
  522. // color: '#00a32e',
  523. // lineStyle: {
  524. // color: '#00a32e',
  525. // width: 2,
  526. // type: 'solid'
  527. // }
  528. // }
  529. // }
  530. // },
  531. // {
  532. // name: textEcharts.zhoongxian,
  533. // type: 'line',
  534. // data: calculateMA(2, extractedDrawData),
  535. // smooth: true,
  536. // symbol: 'none',
  537. // xAxisIndex: 2,
  538. // yAxisIndex: 2,
  539. // itemStyle: {
  540. // normal: {
  541. // color: '#de0000',
  542. // lineStyle: {
  543. // color: '#de0000',
  544. // width: 2,
  545. // type: 'solid'
  546. // }
  547. // }
  548. // }
  549. // },
  550. // {
  551. // name: textEcharts.tianxian,
  552. // type: 'line',
  553. // data: calculateMA(3, extractedDrawData),
  554. // smooth: true,
  555. // symbol: 'none',
  556. // xAxisIndex: 2,
  557. // yAxisIndex: 2,
  558. // itemStyle: {
  559. // normal: {
  560. // color: '#ffb300',
  561. // lineStyle: {
  562. // color: '#ffb300',
  563. // width: 2,
  564. // type: 'solid'
  565. // }
  566. // }
  567. // }
  568. // },
  569. // {
  570. // name: textEcharts.liuxian,
  571. // type: 'line',
  572. // data: calculateMA(4, extractedDrawData),
  573. // smooth: true,
  574. // symbol: 'none',
  575. // xAxisIndex: 2,
  576. // yAxisIndex: 2,
  577. // itemStyle: {
  578. // normal: {
  579. // color: '#00c8ff',
  580. // lineStyle: {
  581. // color: '#00c8ff',
  582. // width: 2,
  583. // type: 'solid'
  584. // }
  585. // }
  586. // }
  587. // },
  588. ]
  589. }
  590. initChart()
  591. }
  592. // 组件挂载时初始化图表
  593. onMounted(() => {
  594. // 调用图表初始化函数
  595. fnShowEcharts4(extractedDrawData)
  596. })
  597. // 初始化图表
  598. const initChart = async () => {
  599. if (!chartRef.value) return
  600. try {
  601. const chart = await chartRef.value.init(echarts)
  602. chart.setOption(option)
  603. } catch (error) {
  604. console.error('图表初始化失败:', error)
  605. }
  606. }
  607. </script>