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.

306 lines
10 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import React, { useState, useEffect, useRef } from 'react';
import { Card, Row, Col, Button, Select, Tabs, Statistic, Typography, Badge, Table } from 'antd';
import { ArrowUpOutlined, ArrowDownOutlined, LineChartOutlined, BarChartOutlined, PieChartOutlined } from '@ant-design/icons';
import { generateFutureData, generateKlineData } from '../../utils/mockData';
import useTheme from '../../hooks/useTheme';
// 导入Lightweight Charts
import { createChart } from 'lightweight-charts';
const { Title, Text } = Typography;
const { Option } = Select;
const { TabPane } = Tabs;
const { Column } = Table;
const Detail = () => {
const [selectedFuture, setSelectedFuture] = useState('MA');
const [futureData, setFutureData] = useState(generateFutureData('MA', '甲醇'));
const [timePeriod, setTimePeriod] = useState('1DAY');
const [chart, setChart] = useState(null);
const chartRef = useRef(null);
// 品种列表
const futuresList = [
{ code: 'MA', name: '甲醇' },
{ code: 'CU', name: '铜' },
{ code: 'SC', name: '原油' },
{ code: 'RB', name: '螺纹钢' },
{ code: 'P', name: '棕榈油' }
];
// K线数据
const klineData = generateKlineData(30);
// 切换品种
const handleFutureChange = (code) => {
const future = futuresList.find(f => f.code === code);
setSelectedFuture(code);
setFutureData(generateFutureData(code, future.name));
};
// 切换时间周期
const handleTimePeriodChange = (period) => {
setTimePeriod(period);
};
// 初始化K线图表
useEffect(() => {
if (chartRef.current) {
// 清除之前的图表
if (chart) {
chart.destroy();
}
// 创建新图表
const newChart = createChart(chartRef.current, {
width: chartRef.current.clientWidth,
height: 400,
layout: {
backgroundColor: '#ffffff',
textColor: '#333333'
},
grid: {
vertLines: {
color: '#f0f0f0'
},
horzLines: {
color: '#f0f0f0'
}
},
priceScale: {
borderColor: '#f0f0f0'
},
timeScale: {
borderColor: '#f0f0f0'
}
});
// 添加蜡烛图系列
const candleSeries = newChart.addCandlestickSeries({
upColor: '#52c41a',
downColor: '#ff4d4f',
borderUpColor: '#52c41a',
borderDownColor: '#ff4d4f',
wickUpColor: '#52c41a',
wickDownColor: '#ff4d4f'
});
// 添加成交量系列
const volumeSeries = newChart.addHistogramSeries({
color: '#82ca9d',
priceFormat: {
type: 'volume'
},
priceScaleId: '',
scaleMargins: {
top: 0.8, // 给蜡烛图留出空间
bottom: 0
}
});
// 准备数据
const candleData = klineData.map(item => ({
time: item.time,
open: parseFloat(item.open),
high: parseFloat(item.high),
low: parseFloat(item.low),
close: parseFloat(item.close)
}));
const volumeData = klineData.map(item => ({
time: item.time,
value: parseFloat(item.volume),
color: parseFloat(item.close) >= parseFloat(item.open) ? '#52c41a' : '#ff4d4f'
}));
// 设置数据
candleSeries.setData(candleData);
volumeSeries.setData(volumeData);
// 适配窗口大小
const handleResize = () => {
if (newChart) {
newChart.resize(chartRef.current.clientWidth, 400);
}
};
window.addEventListener('resize', handleResize);
setChart(newChart);
// 清理函数
return () => {
window.removeEventListener('resize', handleResize);
if (newChart) {
newChart.destroy();
}
};
}
}, []);
return (
<div>
{/* 页面头部 */}
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
<Col flex="auto">
<Title level={3}>{futureData.name} ({futureData.code}) 详情分析</Title>
<Text>{futureData.fullName}</Text>
</Col>
<Col flex="none">
<Select
defaultValue="MA"
style={{ width: 120, marginRight: 16 }}
onChange={handleFutureChange}
>
{futuresList.map(future => (
<Option key={future.code} value={future.code}>{future.name}</Option>
))}
</Select>
</Col>
</Row>
{/* 品种基本信息 */}
<Card style={{ marginBottom: 24 }}>
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={8} lg={6}>
<Statistic
title="当前价格"
value={futureData.currentPrice}
valueStyle={{ color: parseFloat(futureData.changePercent) > 0 ? '#52c41a' : '#ff4d4f' }}
suffix="元"
/>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<Statistic
title="涨跌幅"
value={futureData.changePercent}
valueStyle={{ color: parseFloat(futureData.changePercent) > 0 ? '#52c41a' : '#ff4d4f' }}
suffix="%"
/>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<Statistic title="胜率" value={futureData.winRate} suffix="%" />
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<Statistic title="ATR" value={futureData.atr} />
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<Statistic title="ADX" value={futureData.adx} />
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<Statistic title="趋势状态" value={futureData.adxStatus} />
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<Statistic title="风险等级" value={futureData.riskLevel} />
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<Statistic title="波动率" value={futureData.volatility} />
</Col>
</Row>
</Card>
{/* K线图表区 */}
<Card title="K线图表" style={{ marginBottom: 24 }}>
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<Select
defaultValue="1DAY"
style={{ width: 120 }}
onChange={handleTimePeriodChange}
>
<Option value="5MIN">5分钟</Option>
<Option value="30MIN">30分钟</Option>
<Option value="1HOUR">1小时</Option>
<Option value="1DAY">1</Option>
<Option value="1WEEK">1</Option>
</Select>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<Button icon={<LineChartOutlined />}>MA</Button>
<Button icon={<BarChartOutlined />}>MACD</Button>
<Button icon={<PieChartOutlined />}>KDJ</Button>
</div>
</div>
<div ref={chartRef} style={{ width: '100%', height: 400 }} />
</Card>
{/* 技术指标和AI研判区 */}
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
<Col xs={24} lg={12}>
<Card title="技术指标">
<Table dataSource={Object.entries(futureData.indicators).map(([key, value]) => ({ key, value }))} rowKey="key" size="small">
<Column title="指标" dataIndex="key" render={(text) => {
const indicatorNames = {
macd: 'MACD',
rsi: 'RSI',
bollinger: '布林带',
kdj: 'KDJ'
};
return indicatorNames[text] || text;
}} />
<Column title="状态" dataIndex="value" />
</Table>
</Card>
</Col>
<Col xs={24} lg={12}>
<Card title="多周期趋势">
<Table dataSource={Object.entries(futureData.trends).map(([period, data]) => ({ period, ...data }))} rowKey="period" size="small">
<Column title="周期" dataIndex="period" />
<Column title="方向" dataIndex="direction" render={(text) => (
<Badge status={text === '看多' ? 'success' : 'error'} text={text} />
)} />
<Column title="状态" dataIndex="status" />
<Column title="RSI" dataIndex="rsi" />
</Table>
</Card>
</Col>
</Row>
{/* 交易建议和AI研判区 */}
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
<Col xs={24} lg={12}>
<Card title="交易建议">
<Row gutter={[16, 16]}>
<Col span={8}>
<Statistic title="入场价" value={futureData.tradingAdvice.entry} suffix="元" />
</Col>
<Col span={8}>
<Statistic title="止损价" value={futureData.tradingAdvice.stopLoss} suffix="元" />
</Col>
<Col span={8}>
<Statistic title="目标价" value={futureData.tradingAdvice.target} suffix="元" />
</Col>
</Row>
<div style={{ marginTop: 16, padding: 16, backgroundColor: '#f6ffed', border: '1px solid #b7eb8f', borderRadius: 4 }}>
<Text strong>AI建议</Text>
<Text>根据多周期分析当前{futureData.name}处于{futureData.trends[timePeriod].status}建议{futureData.trends[timePeriod].direction === '看多' ? '逢低做多' : '逢高做空'}</Text>
</div>
</Card>
</Col>
<Col xs={24} lg={12}>
<Card title="AI研判">
<div style={{ marginBottom: 16 }}>
<Text strong>整体判断</Text>
<Text>{futureData.name}当前处于{futureData.adxStatus}{futureData.trends[timePeriod].direction === '看多' ? '多头力量较强' : '空头力量较强'}</Text>
</div>
<div style={{ marginBottom: 16 }}>
<Text strong>技术面分析</Text>
<ul style={{ marginLeft: 20, marginTop: 8 }}>
<li>MACD{futureData.indicators.macd}</li>
<li>RSI{futureData.indicators.rsi}</li>
<li>布林带{futureData.indicators.bollinger}</li>
<li>KDJ{futureData.indicators.kdj}</li>
</ul>
</div>
<div>
<Text strong>风险提示</Text>
<Text>当前风险等级为{futureData.riskLevel}波动率{futureData.volatility}建议控制仓位严格设置止损</Text>
</div>
</Card>
</Col>
</Row>
</div>
);
};
export default Detail;