| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- import React from 'react';
- import StockHeatmap from '@rongmz/react-stock-heatmap';
- import '@rongmz/react-stock-heatmap/example/src/index.css';
- function formatTimestamp(timestamp) {
- // 创建一个新的 Date 对象
- const date = new Date(timestamp);
- // 获取小时、分钟和秒
- const hours = date.getHours();
- const minutes = date.getMinutes();
- const seconds = date.getSeconds();
- // 获取毫秒
- const milliseconds = date.getMilliseconds();
- // 将小时、分钟、秒、毫秒格式化为两位数字(除了毫秒可能是三位)
- const formattedHours = hours.toString().padStart(2, '0');
- const formattedMinutes = minutes.toString().padStart(2, '0');
- const formattedSeconds = seconds.toString().padStart(2, '0');
- const formattedMilliseconds = milliseconds.toString().padStart(3, '0');
- // 返回格式化的时间字符串
- return `${formattedHours}:${formattedMinutes}:${formattedSeconds}.${formattedMilliseconds}`;
- }
- function parseStockData(data) {
- // Extracting values from the input data
- const { asks, bids, last_price, last_qty, total_qty, time, side } = data;
- // Convert asks and bids to the required format
- const processOrders = (orders) => orders.map(([rate, qty]) => ({
- rate: rate,
- orders: 1, // Assuming each price level has one order
- qty
- }));
- // Calculate additional values
- const high = Math.max(...asks.map(a => a[0]), last_price);
- const low = Math.min(...bids.map(b => b[0]), last_price);
- const open = bids[0][0]; // Assuming the first bid rate as open
- const close = last_price;
- const volume = total_qty; // Total traded volume
- // Calculate average price (simplified as an average of high and low)
- const avgPrice = (high + low) / 2;
- // Construct the final object
- return {
- marketDepth: {
- lastBuyPrice: side === 'buy' ? last_price : 0,
- lastBuyQty: side === 'buy' ? last_qty : 0,
- lastSellPrice: side === 'sell' ? last_price : 0,
- lastSellQty: side === 'sell' ? last_qty : 0,
- priceChangeAmt: last_price - open, // Simplified price change amount
- priceChangePct: ((last_price - open) / open * 100).toFixed(2),
- lastTradedTS: Date.now(),
- open,
- high,
- low,
- close,
- volume,
- avgPrice,
- buyOrderVolume: bids.reduce((sum, [, qty]) => sum + qty, 0),
- buys: processOrders(bids),
- sellOrderVolume: asks.reduce((sum, [, qty]) => sum + qty, 0),
- sells: processOrders(asks),
- side: side
- },
- ts: formatTimestamp(time),
- time: time,
- tradingsymbol: "XYZ123",
- pendingOrders: []
- };
- }
- export default () => {
- const [isActivation, setIsActivation] = React.useState(false);
- const [activationCode, setActivationCode] = React.useState();
- const [loading, setLoading] = React.useState(true);
- const progressRef = React.useRef(null);
- /** @type {React.MutableRefObject<StockHeatmap>} */
- const heatmapRef = React.useRef(null);
- const [windowDim, setWindowDim] = React.useState([0, 0]);
- const [autoScroll, setAutoScroll] = React.useState(true);
- const toggleAutoScroll = (value) => {
- setAutoScroll(value);
- };
- // ------------ Load data -------------
- React.useEffect(() => {
- const ws = new WebSocket('ws://localhost:6789');
- let ref = heatmapRef.current
- console.log('ws创建完成')
- ws.onmessage = function(event) {
- const message = JSON.parse(event.data);
- let stock = parseStockData(message)
- ref.addData(stock)
- if (progressRef.current !== null) {
- progressRef.current.innerHTML = ` 等待数据推送 ${(100 * ref.data.length / ref.windowLength + 1).toFixed(0)}% ...`
- if (ref.data.length >= ref.windowLength) {
- setLoading(false)
- }
- }
- };
- ws.onerror = function(event) {
- console.error("WebSocket error observed:", event);
- };
- }, []);
- // ------------ Load data -------------
- const handleActivation = ()=>{
- console.log(activationCode)
- setIsActivation(true)
- }
- // ---------- window update ------------
- React.useEffect(() => {
- const updateFn = () => {
- setWindowDim([
- window.innerWidth,
- window.innerHeight
- ]);
- }
- updateFn();
- window.addEventListener('resize', updateFn);
- return () => window.removeEventListener('resize', updateFn);
- }, []);
- // ---------- window update ------------
- return (
- <div className="wapper">
- <div className={isActivation ? "visibilityVisible" : "visibilityHidden"}>
- <React.Fragment>
- {loading &&
- <div className="loadingIndicator">
- <div className="loadingSpinner">
- <div className="loader">等待数据推送...</div>
- </div>
- <div ref={progressRef}> 等待数据推送 0%</div>
- </div>
- }
- <StockHeatmap ref={heatmapRef} width={windowDim[0]} height={windowDim[1]} autoScroll={autoScroll}
- toggleAutoScroll={toggleAutoScroll} />
- <div className="btnContainer">
- <button onClick={() => {
- if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60)
- }}>1分钟视域
- </button>
- <button onClick={() => {
- if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60 * 5)
- }}>5分钟视域
- </button>
- <button onClick={() => {
- if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60 * 10)
- }}>10分钟视域
- </button>
- <button onClick={() => {
- if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60 * 15)
- }}>15分钟视域
- </button>
- <button onClick={() => {
- if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60 * 30)
- }}>30分钟视域
- </button>
- <button onClick={() => {
- if (heatmapRef.current !== null) heatmapRef.current.setZoomLevel(60 * 60)
- }}>60分钟视域
- </button>
- {!autoScroll &&
- <div className="playBtn" onClick={() => {
- setAutoScroll(true)
- }}> 继续
- </div>
- }
- {/* <button onClick={() => { */}
- {/* const HHmmss = window.prompt('Enter HH:mm:ss', '00:00:00'); */}
- {/* let split = HHmmss.split(':'); */}
- {/* let position = (+split[0]-9)*3600 + (+split[1]*60) + (+split[2]); */}
- {/* if (heatmapRef.current !== null) heatmapRef.current.moveDataWindow(position); */}
- {/* }}>Set Position</button> */}
- </div>
- </React.Fragment>
- </div>
- {!isActivation &&
- <div className="layoutContainer">
- <div className="activationBox">
- <div className="title">
- 软件激活
- </div>
- <div className="iptWp">
- <input placeholder="请输入激活码" onChange={(e)=>{
- setActivationCode(e.target.value)
- }} />
- </div>
- <div className="btnWp">
- <div className="btn" onClick={handleActivation}>激 活</div>
- </div>
- </div>
- </div>
- }
- </div>
- )
- }
|