App.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import React from 'react';
  2. import StockHeatmap from '@rongmz/react-stock-heatmap';
  3. import '@rongmz/react-stock-heatmap/example/src/index.css';
  4. function formatTimestamp(timestamp) {
  5. // 创建一个新的 Date 对象
  6. const date = new Date(timestamp);
  7. // 获取小时、分钟和秒
  8. const hours = date.getHours();
  9. const minutes = date.getMinutes();
  10. const seconds = date.getSeconds();
  11. // 获取毫秒
  12. const milliseconds = date.getMilliseconds();
  13. // 将小时、分钟、秒、毫秒格式化为两位数字(除了毫秒可能是三位)
  14. const formattedHours = hours.toString().padStart(2, '0');
  15. const formattedMinutes = minutes.toString().padStart(2, '0');
  16. const formattedSeconds = seconds.toString().padStart(2, '0');
  17. const formattedMilliseconds = milliseconds.toString().padStart(3, '0');
  18. // 返回格式化的时间字符串
  19. return `${formattedHours}:${formattedMinutes}:${formattedSeconds}.${formattedMilliseconds}`;
  20. }
  21. function parseStockData(data) {
  22. // Extracting values from the input data
  23. const { asks, bids, last_price, last_qty, total_qty, time, side } = data;
  24. // Convert asks and bids to the required format
  25. const processOrders = (orders) => orders.map(([rate, qty]) => ({
  26. rate: rate,
  27. orders: 1, // Assuming each price level has one order
  28. qty
  29. }));
  30. // Calculate additional values
  31. const high = Math.max(...asks.map(a => a[0]), last_price);
  32. const low = Math.min(...bids.map(b => b[0]), last_price);
  33. const open = bids[0][0]; // Assuming the first bid rate as open
  34. const close = last_price;
  35. const volume = total_qty; // Total traded volume
  36. // Calculate average price (simplified as an average of high and low)
  37. const avgPrice = (high + low) / 2;
  38. // Construct the final object
  39. return {
  40. marketDepth: {
  41. lastBuyPrice: side === 'buy' ? last_price : 0,
  42. lastBuyQty: side === 'buy' ? last_qty : 0,
  43. lastSellPrice: side === 'sell' ? last_price : 0,
  44. lastSellQty: side === 'sell' ? last_qty : 0,
  45. priceChangeAmt: last_price - open, // Simplified price change amount
  46. priceChangePct: ((last_price - open) / open * 100).toFixed(2),
  47. lastTradedTS: Date.now(),
  48. open,
  49. high,
  50. low,
  51. close,
  52. volume,
  53. avgPrice,
  54. buyOrderVolume: bids.reduce((sum, [, qty]) => sum + qty, 0),
  55. buys: processOrders(bids),
  56. sellOrderVolume: asks.reduce((sum, [, qty]) => sum + qty, 0),
  57. sells: processOrders(asks),
  58. side: side
  59. },
  60. ts: formatTimestamp(time),
  61. time: time,
  62. tradingsymbol: "XYZ123",
  63. pendingOrders: []
  64. };
  65. }
  66. export default () => {
  67. const [isActivation, setIsActivation] = React.useState(false);
  68. const [activationCode, setActivationCode] = React.useState();
  69. const [loading, setLoading] = React.useState(true);
  70. const progressRef = React.useRef(null);
  71. /** @type {React.MutableRefObject<StockHeatmap>} */
  72. const heatmapRef = React.useRef(null);
  73. const [windowDim, setWindowDim] = React.useState([0, 0]);
  74. const [autoScroll, setAutoScroll] = React.useState(true);
  75. const toggleAutoScroll = (value) => {
  76. setAutoScroll(value);
  77. };
  78. // ------------ Load data -------------
  79. React.useEffect(() => {
  80. const ws = new WebSocket('ws://localhost:6789');
  81. let ref = heatmapRef.current
  82. console.log('ws创建完成')
  83. ws.onmessage = function(event) {
  84. const message = JSON.parse(event.data);
  85. let stock = parseStockData(message)
  86. ref.addData(stock)
  87. if (progressRef.current !== null) {
  88. progressRef.current.innerHTML = ` 等待数据推送 ${(100 * ref.data.length / ref.windowLength + 1).toFixed(0)}% ...`
  89. if (ref.data.length >= ref.windowLength) {
  90. setLoading(false)
  91. }
  92. }
  93. };
  94. ws.onerror = function(event) {
  95. console.error("WebSocket error observed:", event);
  96. };
  97. }, []);
  98. // ------------ Load data -------------
  99. const handleActivation = ()=>{
  100. console.log(activationCode)
  101. setIsActivation(true)
  102. }
  103. // ---------- window update ------------
  104. React.useEffect(() => {
  105. const updateFn = () => {
  106. setWindowDim([
  107. window.innerWidth,
  108. window.innerHeight
  109. ]);
  110. }
  111. updateFn();
  112. window.addEventListener('resize', updateFn);
  113. return () => window.removeEventListener('resize', updateFn);
  114. }, []);
  115. // ---------- window update ------------
  116. return (
  117. <div className="wapper">
  118. <div className={isActivation ? "visibilityVisible" : "visibilityHidden"}>
  119. <React.Fragment>
  120. {loading &&
  121. <div className="loadingIndicator">
  122. <div className="loadingSpinner">
  123. <div className="loader">等待数据推送...</div>
  124. </div>
  125. <div ref={progressRef}> 等待数据推送 0%</div>
  126. </div>
  127. }
  128. <StockHeatmap ref={heatmapRef} width={windowDim[0]} height={windowDim[1]} autoScroll={autoScroll}
  129. toggleAutoScroll={toggleAutoScroll} />
  130. <div className="btnContainer">
  131. <button onClick={() => {
  132. if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60)
  133. }}>1分钟视域
  134. </button>
  135. <button onClick={() => {
  136. if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60 * 5)
  137. }}>5分钟视域
  138. </button>
  139. <button onClick={() => {
  140. if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60 * 10)
  141. }}>10分钟视域
  142. </button>
  143. <button onClick={() => {
  144. if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60 * 15)
  145. }}>15分钟视域
  146. </button>
  147. <button onClick={() => {
  148. if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60 * 30)
  149. }}>30分钟视域
  150. </button>
  151. <button onClick={() => {
  152. if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60 * 60)
  153. }}>60分钟视域
  154. </button>
  155. {!autoScroll &&
  156. <div className="playBtn" onClick={() => {
  157. setAutoScroll(true)
  158. }}> 继续
  159. </div>
  160. }
  161. {/* <button onClick={() => { */}
  162. {/* const HHmmss = window.prompt('Enter HH:mm:ss', '00:00:00'); */}
  163. {/* let split = HHmmss.split(':'); */}
  164. {/* let position = (+split[0]-9)*3600 + (+split[1]*60) + (+split[2]); */}
  165. {/* if (heatmapRef.current !== null) heatmapRef.current.moveDataWindow(position); */}
  166. {/* }}>Set Position</button> */}
  167. </div>
  168. </React.Fragment>
  169. </div>
  170. {!isActivation &&
  171. <div className="layoutContainer">
  172. <div className="activationBox">
  173. <div className="title">
  174. 软件激活
  175. </div>
  176. <div className="iptWp">
  177. <input placeholder="请输入激活码" onChange={(e)=>{
  178. setActivationCode(e.target.value)
  179. }} />
  180. </div>
  181. <div className="btnWp">
  182. <div className="btn" onClick={handleActivation}>激 活</div>
  183. </div>
  184. </div>
  185. </div>
  186. }
  187. </div>
  188. )
  189. }