|
|
|
@ -1,164 +1,62 @@
|
|
|
|
import React, { useEffect, useRef, useState } from 'react';
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
|
|
import { Card, Button, Row, Col, Select, Tabs, Tag, Statistic, Alert, Spin } from 'antd';
|
|
|
|
import { Card, Row, Col, Button, Select, Tag, Statistic, Alert, Spin } from 'antd';
|
|
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
|
|
import { ArrowUpOutlined, ArrowDownOutlined, LineChartOutlined, BarChartOutlined, AlertOutlined, CalculatorOutlined } from '@ant-design/icons';
|
|
|
|
import { LineChartOutlined, BarChartOutlined, AreaChartOutlined, ArrowUpOutlined, AlertOutlined, RobotOutlined, SafetyOutlined } from '@ant-design/icons';
|
|
|
|
import { fetchFutureDetail } from '../../store/futuresSlice';
|
|
|
|
import { generateFutureData } from '../../utils/mockData';
|
|
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
|
|
|
|
|
|
import { generateKlineData, generateFutureData } from '../../utils/mockData';
|
|
|
|
|
|
|
|
import './Detail.css';
|
|
|
|
import './Detail.css';
|
|
|
|
|
|
|
|
|
|
|
|
// 导入TradingView Lightweight Charts
|
|
|
|
|
|
|
|
import { createChart } from 'lightweight-charts';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { Option } = Select;
|
|
|
|
const { Option } = Select;
|
|
|
|
|
|
|
|
const { TabPane } = Tabs;
|
|
|
|
|
|
|
|
|
|
|
|
const Detail = () => {
|
|
|
|
const Detail = () => {
|
|
|
|
const dispatch = useDispatch();
|
|
|
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
const navigate = useNavigate();
|
|
|
|
const location = useLocation();
|
|
|
|
const { code } = useParams();
|
|
|
|
const chartRef = useRef(null);
|
|
|
|
const [data, setData] = useState(null);
|
|
|
|
const chartInstance = useRef(null);
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const { selectedFuture, loading, error } = useSelector(state => state.futures);
|
|
|
|
const [currentPeriod, setCurrentPeriod] = useState('1H');
|
|
|
|
const [timeframe, setTimeframe] = useState('1D');
|
|
|
|
const [currentIndicator, setCurrentIndicator] = useState('MA');
|
|
|
|
const [localData, setLocalData] = useState(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 解析URL参数获取品种信息
|
|
|
|
|
|
|
|
const getQueryParams = () => {
|
|
|
|
|
|
|
|
const params = new URLSearchParams(location.search);
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
code: params.get('code') || 'MA',
|
|
|
|
|
|
|
|
name: params.get('name') || '甲醇'
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { code, name } = getQueryParams();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 调试日志
|
|
|
|
console.log('Detail page loaded with code:', code);
|
|
|
|
console.log('Detail page loaded with:', { code, name });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
useEffect(() => {
|
|
|
|
// 尝试使用本地生成数据作为备选方案
|
|
|
|
// 模拟数据加载
|
|
|
|
const fallbackData = generateFutureData(code, name);
|
|
|
|
setTimeout(() => {
|
|
|
|
setLocalData(fallbackData);
|
|
|
|
const futureData = generateFutureData(code, '测试品种');
|
|
|
|
console.log('Generated fallback data:', fallbackData);
|
|
|
|
setData(futureData);
|
|
|
|
|
|
|
|
setLoading(false);
|
|
|
|
// 同时尝试从Redux获取数据
|
|
|
|
}, 500);
|
|
|
|
console.log('Dispatching fetchFutureDetail with:', { code, name });
|
|
|
|
}, [code]);
|
|
|
|
dispatch(fetchFutureDetail({ code, name }));
|
|
|
|
|
|
|
|
}, [dispatch, code, name]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
|
|
// 初始化K线图表
|
|
|
|
|
|
|
|
const dataToUse = selectedFuture || localData;
|
|
|
|
|
|
|
|
if (chartRef.current && dataToUse) {
|
|
|
|
|
|
|
|
console.log('Initializing chart with data:', dataToUse);
|
|
|
|
|
|
|
|
if (chartInstance.current) {
|
|
|
|
|
|
|
|
chartInstance.current.destroy();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const chart = createChart(chartRef.current, {
|
|
|
|
|
|
|
|
width: chartRef.current.clientWidth,
|
|
|
|
|
|
|
|
height: 400,
|
|
|
|
|
|
|
|
layout: {
|
|
|
|
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
|
|
|
|
textColor: '#262626'
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
|
|
vertLines: {
|
|
|
|
|
|
|
|
color: '#f0f0f0'
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
horzLines: {
|
|
|
|
|
|
|
|
color: '#f0f0f0'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
priceScale: {
|
|
|
|
|
|
|
|
borderColor: '#f0f0f0'
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
timeScale: {
|
|
|
|
|
|
|
|
borderColor: '#f0f0f0',
|
|
|
|
|
|
|
|
timeVisible: true,
|
|
|
|
|
|
|
|
secondsVisible: false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 添加K线系列
|
|
|
|
|
|
|
|
const candlestickSeries = chart.addCandlestickSeries({
|
|
|
|
|
|
|
|
upColor: '#52c41a',
|
|
|
|
|
|
|
|
downColor: '#ff4d4f',
|
|
|
|
|
|
|
|
borderUpColor: '#52c41a',
|
|
|
|
|
|
|
|
borderDownColor: '#ff4d4f',
|
|
|
|
|
|
|
|
wickUpColor: '#52c41a',
|
|
|
|
|
|
|
|
wickDownColor: '#ff4d4f'
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 生成K线数据
|
|
|
|
|
|
|
|
const klineData = generateKlineData(30);
|
|
|
|
|
|
|
|
candlestickSeries.setData(klineData);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 添加成交量系列
|
|
|
|
|
|
|
|
const volumeSeries = chart.addHistogramSeries({
|
|
|
|
|
|
|
|
color: '#82ca9d',
|
|
|
|
|
|
|
|
lineWidth: 1,
|
|
|
|
|
|
|
|
priceScaleId: '',
|
|
|
|
|
|
|
|
scaleMargins: {
|
|
|
|
|
|
|
|
top: 0.8,
|
|
|
|
|
|
|
|
bottom: 0
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const volumeData = klineData.map(item => ({
|
|
|
|
|
|
|
|
time: item.time,
|
|
|
|
|
|
|
|
value: item.volume,
|
|
|
|
|
|
|
|
color: item.close >= item.open ? '#52c41a' : '#ff4d4f'
|
|
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
volumeSeries.setData(volumeData);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 缩放到合适的范围
|
|
|
|
|
|
|
|
chart.timeScale().fitContent();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chartInstance.current = chart;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 响应窗口大小变化
|
|
|
|
|
|
|
|
const handleResize = () => {
|
|
|
|
|
|
|
|
if (chartInstance.current) {
|
|
|
|
|
|
|
|
chartInstance.current.resize(chartRef.current.clientWidth, 400);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
|
|
window.removeEventListener('resize', handleResize);
|
|
|
|
|
|
|
|
if (chartInstance.current) {
|
|
|
|
|
|
|
|
chartInstance.current.destroy();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}, [selectedFuture, localData]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleBack = () => {
|
|
|
|
const handleBack = () => {
|
|
|
|
navigate('/');
|
|
|
|
navigate('/');
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getChangeColor = (changePercent) => {
|
|
|
|
const handlePeriodChange = (value) => {
|
|
|
|
return changePercent >= 0 ? '#52c41a' : '#ff4d4f';
|
|
|
|
setCurrentPeriod(value);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getChangeIcon = (changePercent) => {
|
|
|
|
const handleIndicatorChange = (value) => {
|
|
|
|
return changePercent >= 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />;
|
|
|
|
setCurrentIndicator(value);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getTrendColor = (direction) => {
|
|
|
|
if (loading) {
|
|
|
|
if (direction === '看多') return '#52c41a';
|
|
|
|
return (
|
|
|
|
if (direction === '看空') return '#ff4d4f';
|
|
|
|
<div className="loading-container">
|
|
|
|
return '#faad14';
|
|
|
|
<Spin size="large" tip="加载数据中..." />
|
|
|
|
};
|
|
|
|
</div>
|
|
|
|
|
|
|
|
);
|
|
|
|
// 使用Redux数据或本地数据
|
|
|
|
}
|
|
|
|
const dataToDisplay = selectedFuture || localData;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 确保即使在加载状态下也能显示内容
|
|
|
|
if (!data) {
|
|
|
|
console.log('Rendering Detail component with state:', { loading, selectedFuture, localData, error });
|
|
|
|
return (
|
|
|
|
|
|
|
|
<div className="error-container">
|
|
|
|
|
|
|
|
<Alert message="未找到品种数据" type="error" />
|
|
|
|
|
|
|
|
<Button type="primary" onClick={handleBack} style={{ marginTop: 16 }}>
|
|
|
|
|
|
|
|
返回主页
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<div className="detail">
|
|
|
|
<div className="detail">
|
|
|
|
@ -167,235 +65,286 @@ const Detail = () => {
|
|
|
|
<Button type="default" onClick={handleBack} style={{ marginBottom: 16 }}>
|
|
|
|
<Button type="default" onClick={handleBack} style={{ marginBottom: 16 }}>
|
|
|
|
返回主页
|
|
|
|
返回主页
|
|
|
|
</Button>
|
|
|
|
</Button>
|
|
|
|
<h2>{dataToDisplay ? dataToDisplay.fullName : `${name}-${code}`}</h2>
|
|
|
|
<div className="header-info">
|
|
|
|
</div>
|
|
|
|
<h2>{data.name} ({data.code})</h2>
|
|
|
|
|
|
|
|
<div className="price-info">
|
|
|
|
{/* 错误信息 */}
|
|
|
|
|
|
|
|
{error && (
|
|
|
|
|
|
|
|
<Alert message="加载失败" description={error} type="error" showIcon style={{ marginBottom: 24 }} />
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 加载状态 */}
|
|
|
|
|
|
|
|
{loading && !dataToDisplay && (
|
|
|
|
|
|
|
|
<div className="loading-container">
|
|
|
|
|
|
|
|
<Spin size="large" tip="加载数据中..." />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 数据内容 */}
|
|
|
|
|
|
|
|
{dataToDisplay && (
|
|
|
|
|
|
|
|
<>
|
|
|
|
|
|
|
|
{/* 基本信息 */}
|
|
|
|
|
|
|
|
<Card className="detail-card" style={{ marginBottom: 24 }}>
|
|
|
|
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
|
|
<Statistic
|
|
|
|
<Statistic
|
|
|
|
title="当前价格"
|
|
|
|
title="当前价格"
|
|
|
|
value={dataToDisplay.currentPrice}
|
|
|
|
value={data.currentPrice}
|
|
|
|
valueStyle={{ color: '#262626' }}
|
|
|
|
precision={2}
|
|
|
|
|
|
|
|
valueStyle={{ color: data.changePercent >= 0 ? '#52c41a' : '#ff4d4f' }}
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
|
|
<Statistic
|
|
|
|
<Statistic
|
|
|
|
title="涨跌幅"
|
|
|
|
title="涨跌幅"
|
|
|
|
value={Math.abs(dataToDisplay.changePercent)}
|
|
|
|
value={data.changePercent}
|
|
|
|
suffix="%"
|
|
|
|
suffix="%"
|
|
|
|
valueStyle={{ color: getChangeColor(dataToDisplay.changePercent) }}
|
|
|
|
valueStyle={{ color: data.changePercent >= 0 ? '#52c41a' : '#ff4d4f' }}
|
|
|
|
prefix={getChangeIcon(dataToDisplay.changePercent)}
|
|
|
|
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
|
|
<Statistic
|
|
|
|
<Statistic
|
|
|
|
title="胜率"
|
|
|
|
title="胜率"
|
|
|
|
value={dataToDisplay.winRate}
|
|
|
|
value={data.winRate}
|
|
|
|
suffix="%"
|
|
|
|
suffix="%"
|
|
|
|
valueStyle={{ color: dataToDisplay.winRate > 60 ? '#52c41a' : dataToDisplay.winRate > 40 ? '#faad14' : '#ff4d4f' }}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
|
|
<Statistic
|
|
|
|
|
|
|
|
title="ATR"
|
|
|
|
|
|
|
|
value={dataToDisplay.atr}
|
|
|
|
|
|
|
|
valueStyle={{ color: '#1890ff' }}
|
|
|
|
valueStyle={{ color: '#1890ff' }}
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</Col>
|
|
|
|
</div>
|
|
|
|
<Col span={8}>
|
|
|
|
</div>
|
|
|
|
<Statistic
|
|
|
|
</div>
|
|
|
|
title="ADX"
|
|
|
|
|
|
|
|
value={dataToDisplay.adx}
|
|
|
|
|
|
|
|
valueStyle={{ color: '#1890ff' }}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
|
|
<Statistic
|
|
|
|
|
|
|
|
title="趋势状态"
|
|
|
|
|
|
|
|
value={dataToDisplay.adxStatus}
|
|
|
|
|
|
|
|
valueStyle={{ color: '#1890ff' }}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* K线图表 */}
|
|
|
|
{/* K线图表区 */}
|
|
|
|
<Card
|
|
|
|
<Card className="detail-card" style={{ marginBottom: 24 }}>
|
|
|
|
title={
|
|
|
|
<div className="chart-header">
|
|
|
|
<div className="chart-title">
|
|
|
|
<div className="chart-title">
|
|
|
|
<LineChartOutlined /> K线图表
|
|
|
|
<h3>K线图表</h3>
|
|
|
|
|
|
|
|
<div className="chart-controls">
|
|
|
|
|
|
|
|
<span style={{ marginRight: 16 }}>周期:</span>
|
|
|
|
<Select
|
|
|
|
<Select
|
|
|
|
defaultValue="1D"
|
|
|
|
value={currentPeriod}
|
|
|
|
style={{ width: 120, marginLeft: 16 }}
|
|
|
|
onChange={handlePeriodChange}
|
|
|
|
onChange={setTimeframe}
|
|
|
|
style={{ width: 120, marginRight: 16 }}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<Option value="5MIN">5分钟</Option>
|
|
|
|
<Option value="5M">5分钟</Option>
|
|
|
|
<Option value="30MIN">30分钟</Option>
|
|
|
|
<Option value="30M">30分钟</Option>
|
|
|
|
<Option value="1H">1小时</Option>
|
|
|
|
<Option value="1H">1小时</Option>
|
|
|
|
<Option value="1D">1天</Option>
|
|
|
|
<Option value="1D">1天</Option>
|
|
|
|
<Option value="1W">1周</Option>
|
|
|
|
<Option value="1W">1周</Option>
|
|
|
|
</Select>
|
|
|
|
</Select>
|
|
|
|
</div>
|
|
|
|
<span style={{ marginRight: 16 }}>指标:</span>
|
|
|
|
}
|
|
|
|
<Select
|
|
|
|
className="detail-card"
|
|
|
|
value={currentIndicator}
|
|
|
|
style={{ marginBottom: 24 }}
|
|
|
|
onChange={handleIndicatorChange}
|
|
|
|
>
|
|
|
|
style={{ width: 120 }}
|
|
|
|
<div ref={chartRef} className="kline-chart"></div>
|
|
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 多周期趋势分析 */}
|
|
|
|
|
|
|
|
<Card
|
|
|
|
|
|
|
|
title={<span><BarChartOutlined /> 多周期趋势分析</span>}
|
|
|
|
|
|
|
|
className="detail-card"
|
|
|
|
|
|
|
|
style={{ marginBottom: 24 }}
|
|
|
|
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
<Option value="MA">MA</Option>
|
|
|
|
{Object.entries(dataToDisplay.trends).map(([period, trend]) => (
|
|
|
|
<Option value="MACD">MACD</Option>
|
|
|
|
<Col span={6} key={period}>
|
|
|
|
<Option value="KDJ">KDJ</Option>
|
|
|
|
<Card className="trend-card">
|
|
|
|
<Option value="RSI">RSI</Option>
|
|
|
|
<div className="trend-header">
|
|
|
|
<Option value="BOLL">布林带</Option>
|
|
|
|
<h4>{period}</h4>
|
|
|
|
</Select>
|
|
|
|
<Tag color={getTrendColor(trend.direction)}>
|
|
|
|
|
|
|
|
{trend.direction}
|
|
|
|
|
|
|
|
</Tag>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="trend-status">
|
|
|
|
|
|
|
|
{trend.status}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="trend-rsi">
|
|
|
|
|
|
|
|
RSI: {trend.rsi}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Card>
|
|
|
|
<div className="kline-chart">
|
|
|
|
</Col>
|
|
|
|
{/* 这里将集成TradingView Lightweight Charts */}
|
|
|
|
))}
|
|
|
|
<div className="chart-placeholder">
|
|
|
|
</Row>
|
|
|
|
<LineChartOutlined style={{ fontSize: 48, color: '#1890ff', marginBottom: 16 }} />
|
|
|
|
|
|
|
|
<p>K线图表区域</p>
|
|
|
|
|
|
|
|
<p>周期: {currentPeriod} | 指标: {currentIndicator}</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</Card>
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 技术指标 */}
|
|
|
|
{/* 技术指标区 */}
|
|
|
|
<Card
|
|
|
|
<Card className="detail-card" style={{ marginBottom: 24 }}>
|
|
|
|
title="技术指标"
|
|
|
|
<div className="section-header">
|
|
|
|
className="detail-card"
|
|
|
|
<h3>技术指标</h3>
|
|
|
|
style={{ marginBottom: 24 }}
|
|
|
|
<BarChartOutlined />
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
<Col span={6}>
|
|
|
|
<Col xs={24} sm={12} md={8} lg={6}>
|
|
|
|
|
|
|
|
<div className="indicator-item">
|
|
|
|
|
|
|
|
<div className="indicator-label">MA5</div>
|
|
|
|
|
|
|
|
<div className="indicator-value">{data.technicalIndicators?.ma5 || 'N/A'}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col xs={24} sm={12} md={8} lg={6}>
|
|
|
|
|
|
|
|
<div className="indicator-item">
|
|
|
|
|
|
|
|
<div className="indicator-label">MA10</div>
|
|
|
|
|
|
|
|
<div className="indicator-value">{data.technicalIndicators?.ma10 || 'N/A'}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col xs={24} sm={12} md={8} lg={6}>
|
|
|
|
<div className="indicator-item">
|
|
|
|
<div className="indicator-item">
|
|
|
|
<div className="indicator-label">MACD</div>
|
|
|
|
<div className="indicator-label">MACD</div>
|
|
|
|
<div className="indicator-value">{dataToDisplay.indicators.macd}</div>
|
|
|
|
<div className="indicator-value">{data.technicalIndicators?.macd || 'N/A'}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Col>
|
|
|
|
</Col>
|
|
|
|
<Col span={6}>
|
|
|
|
<Col xs={24} sm={12} md={8} lg={6}>
|
|
|
|
<div className="indicator-item">
|
|
|
|
<div className="indicator-item">
|
|
|
|
<div className="indicator-label">RSI</div>
|
|
|
|
<div className="indicator-label">RSI</div>
|
|
|
|
<div className="indicator-value">{dataToDisplay.indicators.rsi}</div>
|
|
|
|
<div className="indicator-value">{data.technicalIndicators?.rsi || 'N/A'}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Col>
|
|
|
|
</Col>
|
|
|
|
<Col span={6}>
|
|
|
|
<Col xs={24} sm={12} md={8} lg={6}>
|
|
|
|
|
|
|
|
<div className="indicator-item">
|
|
|
|
|
|
|
|
<div className="indicator-label">KDJ</div>
|
|
|
|
|
|
|
|
<div className="indicator-value">{data.technicalIndicators?.kdj || 'N/A'}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col xs={24} sm={12} md={8} lg={6}>
|
|
|
|
<div className="indicator-item">
|
|
|
|
<div className="indicator-item">
|
|
|
|
<div className="indicator-label">布林带</div>
|
|
|
|
<div className="indicator-label">布林带</div>
|
|
|
|
<div className="indicator-value">{dataToDisplay.indicators.bollinger}</div>
|
|
|
|
<div className="indicator-value">{data.technicalIndicators?.bollinger || 'N/A'}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Col>
|
|
|
|
</Col>
|
|
|
|
<Col span={6}>
|
|
|
|
<Col xs={24} sm={12} md={8} lg={6}>
|
|
|
|
<div className="indicator-item">
|
|
|
|
<div className="indicator-item">
|
|
|
|
<div className="indicator-label">KDJ</div>
|
|
|
|
<div className="indicator-label">ATR</div>
|
|
|
|
<div className="indicator-value">{dataToDisplay.indicators.kdj}</div>
|
|
|
|
<div className="indicator-value">{data.atr}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col xs={24} sm={12} md={8} lg={6}>
|
|
|
|
|
|
|
|
<div className="indicator-item">
|
|
|
|
|
|
|
|
<div className="indicator-label">ADX</div>
|
|
|
|
|
|
|
|
<div className="indicator-value">{data.adx}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Col>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
</Row>
|
|
|
|
</Card>
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 交易建议 */}
|
|
|
|
{/* 多周期趋势和AI研判区 */}
|
|
|
|
<Card
|
|
|
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
|
|
|
title={<span><CalculatorOutlined /> 交易建议</span>}
|
|
|
|
{/* 多周期趋势 */}
|
|
|
|
className="detail-card"
|
|
|
|
<Col xs={24} lg={12}>
|
|
|
|
style={{ marginBottom: 24 }}
|
|
|
|
<Card className="detail-card">
|
|
|
|
>
|
|
|
|
<div className="section-header">
|
|
|
|
|
|
|
|
<h3>多周期趋势</h3>
|
|
|
|
|
|
|
|
<AreaChartOutlined />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
<Col span={8}>
|
|
|
|
{data.trends && Object.entries(data.trends).map(([period, trend]) => (
|
|
|
|
|
|
|
|
<Col xs={12} sm={6} key={period}>
|
|
|
|
|
|
|
|
<div className="trend-card">
|
|
|
|
|
|
|
|
<div className="trend-header">
|
|
|
|
|
|
|
|
<h4>{period.replace('MIN', 'min').replace('HOUR', 'min')}</h4>
|
|
|
|
|
|
|
|
<Tag color={trend.direction === '看多' ? 'green' : trend.direction === '看空' ? 'red' : 'orange'}>
|
|
|
|
|
|
|
|
{trend.direction}
|
|
|
|
|
|
|
|
</Tag>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="trend-status">{trend.status}</div>
|
|
|
|
|
|
|
|
<div className="trend-rsi">RSI: {trend.rsi}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* AI研判区 */}
|
|
|
|
|
|
|
|
<Col xs={24} lg={12}>
|
|
|
|
|
|
|
|
<Card className="detail-card">
|
|
|
|
|
|
|
|
<div className="section-header">
|
|
|
|
|
|
|
|
<h3>AI研判</h3>
|
|
|
|
|
|
|
|
<RobotOutlined />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="ai-analysis">
|
|
|
|
|
|
|
|
<div className="ai-overview">
|
|
|
|
<Statistic
|
|
|
|
<Statistic
|
|
|
|
title="入场价"
|
|
|
|
title="趋势预测"
|
|
|
|
value={dataToDisplay.tradingAdvice.entry}
|
|
|
|
value={data.aiPrediction?.trend || '中性'}
|
|
|
|
valueStyle={{ color: '#1890ff' }}
|
|
|
|
valueStyle={{ color: data.aiPrediction?.trend === '上涨' ? '#52c41a' : data.aiPrediction?.trend === '下跌' ? '#ff4d4f' : '#1890ff' }}
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
|
|
<Statistic
|
|
|
|
<Statistic
|
|
|
|
title="止损价"
|
|
|
|
title="预测胜率"
|
|
|
|
value={dataToDisplay.tradingAdvice.stopLoss}
|
|
|
|
value={data.aiPrediction?.winRate || 0}
|
|
|
|
valueStyle={{ color: '#ff4d4f' }}
|
|
|
|
suffix="%"
|
|
|
|
|
|
|
|
valueStyle={{ color: '#1890ff' }}
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
|
|
<Statistic
|
|
|
|
<Statistic
|
|
|
|
title="目标价"
|
|
|
|
title="预期收益"
|
|
|
|
value={dataToDisplay.tradingAdvice.target}
|
|
|
|
value={data.aiPrediction?.expectedReturn || 0}
|
|
|
|
|
|
|
|
suffix="%"
|
|
|
|
valueStyle={{ color: '#52c41a' }}
|
|
|
|
valueStyle={{ color: '#52c41a' }}
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="ai-details">
|
|
|
|
|
|
|
|
<h4>AI分析</h4>
|
|
|
|
|
|
|
|
<p>{data.aiAnalysis || 'AI正在分析中...'}</p>
|
|
|
|
|
|
|
|
<h4>关键因素</h4>
|
|
|
|
|
|
|
|
<div className="factor-tags">
|
|
|
|
|
|
|
|
{data.aiPrediction?.keyFactors?.map((factor, index) => (
|
|
|
|
|
|
|
|
<Tag key={index} color="blue">{factor}</Tag>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 交易建议区和风险评估区 */}
|
|
|
|
|
|
|
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
|
|
|
|
|
|
|
{/* 交易建议区 */}
|
|
|
|
|
|
|
|
<Col xs={24} lg={12}>
|
|
|
|
|
|
|
|
<Card className="detail-card">
|
|
|
|
|
|
|
|
<div className="section-header">
|
|
|
|
|
|
|
|
<h3>交易建议</h3>
|
|
|
|
|
|
|
|
<ArrowUpOutlined />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="trading-advice">
|
|
|
|
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
|
|
|
|
<Col xs={24} sm={8}>
|
|
|
|
|
|
|
|
<div className="advice-item">
|
|
|
|
|
|
|
|
<div className="advice-label">入场价</div>
|
|
|
|
|
|
|
|
<div className="advice-value">{data.tradingAdvice?.entryPrice || 'N/A'}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col xs={24} sm={8}>
|
|
|
|
|
|
|
|
<div className="advice-item">
|
|
|
|
|
|
|
|
<div className="advice-label">止损价</div>
|
|
|
|
|
|
|
|
<div className="advice-value" style={{ color: '#ff4d4f' }}>
|
|
|
|
|
|
|
|
{data.tradingAdvice?.stopLoss || 'N/A'}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col xs={24} sm={8}>
|
|
|
|
|
|
|
|
<div className="advice-item">
|
|
|
|
|
|
|
|
<div className="advice-label">目标价</div>
|
|
|
|
|
|
|
|
<div className="advice-value" style={{ color: '#52c41a' }}>
|
|
|
|
|
|
|
|
{data.tradingAdvice?.targetPrice || 'N/A'}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</Col>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
<div className="advice-details">
|
|
|
|
|
|
|
|
<h4>操作建议</h4>
|
|
|
|
|
|
|
|
<p>{data.tradingAdvice?.strategy || 'AI正在生成策略...'}</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</Card>
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 风险评估 */}
|
|
|
|
{/* 风险评估区 */}
|
|
|
|
<Card
|
|
|
|
<Col xs={24} lg={12}>
|
|
|
|
title={<span><AlertOutlined /> 风险评估</span>}
|
|
|
|
<Card className="detail-card">
|
|
|
|
className="detail-card"
|
|
|
|
<div className="section-header">
|
|
|
|
>
|
|
|
|
<h3>风险评估</h3>
|
|
|
|
|
|
|
|
<SafetyOutlined />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="risk-assessment">
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
<Col span={12}>
|
|
|
|
<Col xs={24} sm={8}>
|
|
|
|
<div className="risk-item">
|
|
|
|
<div className="risk-item">
|
|
|
|
<div className="risk-label">风险等级</div>
|
|
|
|
<div className="risk-label">风险等级</div>
|
|
|
|
<Tag color={dataToDisplay.riskLevel === '高' ? 'red' : dataToDisplay.riskLevel === '中等' ? 'orange' : 'green'}>
|
|
|
|
<Tag color={data.riskLevel === '高' ? 'red' : data.riskLevel === '中等' ? 'orange' : 'green'}>
|
|
|
|
{dataToDisplay.riskLevel}
|
|
|
|
{data.riskLevel}
|
|
|
|
</Tag>
|
|
|
|
</Tag>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Col>
|
|
|
|
</Col>
|
|
|
|
<Col span={12}>
|
|
|
|
<Col xs={24} sm={8}>
|
|
|
|
<div className="risk-item">
|
|
|
|
<div className="risk-item">
|
|
|
|
<div className="risk-label">波动率</div>
|
|
|
|
<div className="risk-label">波动率</div>
|
|
|
|
<Tag color={dataToDisplay.volatility === '高' ? 'red' : dataToDisplay.volatility === '中等' ? 'orange' : 'green'}>
|
|
|
|
<div className="risk-value">{data.volatility || 'N/A'}%</div>
|
|
|
|
{dataToDisplay.volatility}
|
|
|
|
</div>
|
|
|
|
</Tag>
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
<Col xs={24} sm={8}>
|
|
|
|
|
|
|
|
<div className="risk-item">
|
|
|
|
|
|
|
|
<div className="risk-label">最大回撤</div>
|
|
|
|
|
|
|
|
<div className="risk-value">{data.maxDrawdown || 'N/A'}%</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Col>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
<div className="risk-details">
|
|
|
|
|
|
|
|
<h4>风险提示</h4>
|
|
|
|
<Alert
|
|
|
|
<Alert
|
|
|
|
message="风险提示"
|
|
|
|
message={data.riskAlert || '无明显风险'}
|
|
|
|
description="期货交易具有高风险,请根据自身风险承受能力合理控制仓位,严格执行止损策略。"
|
|
|
|
type={data.riskLevel === '高' ? 'error' : data.riskLevel === '中等' ? 'warning' : 'info'}
|
|
|
|
type="warning"
|
|
|
|
|
|
|
|
showIcon
|
|
|
|
showIcon
|
|
|
|
style={{ marginTop: 16 }}
|
|
|
|
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
</>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 无数据状态 */}
|
|
|
|
|
|
|
|
{!loading && !dataToDisplay && !error && (
|
|
|
|
|
|
|
|
<div className="error-container">
|
|
|
|
|
|
|
|
<Alert message="未找到品种数据" type="error" />
|
|
|
|
|
|
|
|
<Button type="primary" onClick={handleBack} style={{ marginTop: 16 }}>
|
|
|
|
|
|
|
|
返回主页
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
</Row>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|