/* === 产品净值指标页面 === */ /* global React, useToast, LineChart */ const { useState, useEffect } = React; function PageOperationIndicator() { const toast = useToast(); // 当前活动tab const [activeTab, setActiveTab] = useState('calculate'); // 'calculate' | 'sync' | 'strategy-calculate' | 'strategy-sync' // 数据状态 const [productList, setProductList] = useState([]); const [summaryList, setSummaryList] = useState([]); // wrtb_info_indicator 数据 const [historyList, setHistoryList] = useState([]); // wrtb_info_indicator_history 数据 const [selectedProduct, setSelectedProduct] = useState(null); const [timeSeries, setTimeSeries] = useState([]); const [loading, setLoading] = useState(false); const [calculating, setCalculating] = useState(false); // 筛选条件 const [filterProductId, setFilterProductId] = useState(''); const [filterDate, setFilterDate] = useState(''); const [resetAll, setResetAll] = useState(false); // 分页状态 - 指标计算 const [pagination, setPagination] = useState({ page: 1, pageSize: 20, total: 0, totalPages: 1 }); // 分页状态 - 指标同步 const [summaryPagination, setSummaryPagination] = useState({ page: 1, pageSize: 20, total: 0, totalPages: 1 }); // 获取产品列表(从wrtb_info_product表获取全部产品) const fetchProductList = async () => { try { const data = await window.apiFetch('/api/info-products/all/list'); if (data.data) { setProductList(data.data.map(p => ({ productId: p.productId, productName: p.productName }))); } } catch (error) { console.error('获取产品列表失败:', error); toast.push('获取产品列表失败', 'error'); } }; // 获取指标汇总(wrtb_info_indicator) const fetchSummary = async (productId = null, page = summaryPagination.page, pageSize = summaryPagination.pageSize) => { try { let url = '/api/indicators/summary'; const params = new URLSearchParams(); const targetProductId = productId !== null ? productId : filterProductId; if (targetProductId && targetProductId !== 'all' && targetProductId !== '') { params.append('product_id', targetProductId); } params.append('page', page); params.append('page_size', pageSize); const queryString = params.toString(); if (queryString) { url += '?' + queryString; } const data = await window.apiFetch(url); if (data.data) { setSummaryList(data.data); // 更新分页信息 setSummaryPagination({ page: data.page || 1, pageSize: data.page_size || 20, total: data.total || 0, totalPages: data.total_pages || 1 }); } } catch (error) { console.error('获取指标汇总失败:', error); toast.push('获取指标汇总失败', 'error'); } }; // 获取指标历史列表(wrtb_info_indicator_history) const fetchHistoryList = async (productId = null, page = pagination.page, pageSize = pagination.pageSize) => { try { // 根据tab选择不同的API:净值指标用/indicators,策略指标用/strategy-indicators const isStrategyTab = activeTab === 'strategy-calculate'; let url = isStrategyTab ? '/api/strategy-indicators/history/list' : '/api/indicators/history/list'; const params = new URLSearchParams(); const targetProductId = productId !== null && productId !== undefined ? productId : filterProductId; console.log('fetchHistoryList called:', { productId, filterProductId, targetProductId, page, pageSize, isStrategyTab, url }); if (targetProductId && targetProductId !== 'all' && targetProductId !== '') { params.append('product_id', targetProductId); } if (filterDate) { params.append('date', filterDate); } params.append('page', page); params.append('page_size', pageSize); const queryString = params.toString(); if (queryString) { url += '?' + queryString; } console.log('Fetching URL:', url); const data = await window.apiFetch(url); if (data.data) { setHistoryList(data.data); // 更新分页信息 setPagination({ page: data.page || 1, pageSize: data.page_size || 20, total: data.total || 0, totalPages: data.total_pages || 1 }); } } catch (error) { console.error('获取指标历史失败:', error); toast.push('获取指标历史失败', 'error'); } }; // 获取时序数据(用于图表展示) const fetchTimeSeries = async (productId) => { try { const data = await window.apiFetch(`/api/indicators/history/${productId}`); if (data.data) { setTimeSeries(data.data); } } catch (error) { console.error('获取时序数据失败:', error); toast.push('获取时序数据失败', 'error'); } }; // 计算指标 const calculateIndicator = async () => { setCalculating(true); try { let url = '/api/indicators/calculate/batch'; let options = { method: 'POST', headers: { 'Content-Type': 'application/json' } }; const params = new URLSearchParams(); if (filterProductId && filterProductId !== 'all') { url = `/api/indicators/calculate/${filterProductId}`; } if (filterDate) { params.append('end_date', filterDate); } if (resetAll) { params.append('reset_all', 'true'); } const queryString = params.toString(); if (queryString) { url += '?' + queryString; } const data = await window.apiFetch(url, options); if (data.success) { toast.push(data.message || '计算完成', 'success'); await fetchHistoryList(); await fetchSummary(); if (selectedProduct) { await fetchTimeSeries(selectedProduct.productId); } } else { toast.push(data.message || '计算失败', 'error'); } } catch (error) { console.error('计算指标失败:', error); toast.push('计算指标失败: ' + (error.message || '未知错误'), 'error'); } finally { setCalculating(false); } }; // 同步指标到汇总表 const syncIndicator = async () => { setCalculating(true); try { let url = '/api/indicators/sync/batch'; let options = { method: 'POST', headers: { 'Content-Type': 'application/json' } }; if (filterProductId && filterProductId !== 'all') { url = `/api/indicators/sync/${filterProductId}`; } const data = await window.apiFetch(url, options); if (data.success) { toast.push(`同步完成,成功 ${data.success_count} 条`, 'success'); await fetchSummary(); } else { toast.push(data.message || '同步失败', 'error'); } } catch (error) { console.error('同步指标失败:', error); toast.push('同步指标失败: ' + (error.message || '未知错误'), 'error'); } finally { setCalculating(false); } }; // 计算策略指标 const calculateStrategyIndicator = async () => { setCalculating(true); try { let url = '/api/strategy-indicators/calculate/batch'; let options = { method: 'POST', headers: { 'Content-Type': 'application/json' } }; const params = new URLSearchParams(); if (filterProductId && filterProductId !== 'all') { url = `/api/strategy-indicators/calculate/${filterProductId}`; } if (filterDate) { params.append('end_date', filterDate); } if (resetAll) { params.append('reset_all', 'true'); } const queryString = params.toString(); if (queryString) { url += '?' + queryString; } const data = await window.apiFetch(url, options); if (data.success) { toast.push(data.message || '策略指标计算完成', 'success'); await fetchHistoryList(); await fetchSummary(); } else { toast.push(data.message || '策略指标计算失败', 'error'); } } catch (error) { console.error('计算策略指标失败:', error); toast.push('计算策略指标失败: ' + (error.message || '未知错误'), 'error'); } finally { setCalculating(false); } }; // 同步策略指标到汇总表 const syncStrategyIndicator = async () => { setCalculating(true); try { let url = '/api/strategy-indicators/sync/batch'; let options = { method: 'POST', headers: { 'Content-Type': 'application/json' } }; if (filterProductId && filterProductId !== 'all') { url = `/api/strategy-indicators/sync/${filterProductId}`; } const data = await window.apiFetch(url, options); if (data.success) { toast.push(`策略指标同步完成,成功 ${data.success_count} 条`, 'success'); await fetchSummary(); } else { toast.push(data.message || '策略指标同步失败', 'error'); } } catch (error) { console.error('同步策略指标失败:', error); toast.push('同步策略指标失败: ' + (error.message || '未知错误'), 'error'); } finally { setCalculating(false); } }; // 初始化 useEffect(() => { fetchProductList(); fetchSummary(); fetchHistoryList(); }, []); // 选择产品 const selectProduct = async (item) => { setSelectedProduct(item); await fetchTimeSeries(item.productId); }; // 格式化百分比 const formatPct = (value) => { if (value === null || value === undefined) return '-'; return (value * 100).toFixed(2) + '%'; }; // 格式化数值 const formatNum = (value, decimals = 4) => { if (value === null || value === undefined) return '-'; return value.toFixed(decimals); }; // 获取值的样式 const getValueClass = (value, inverse = false) => { if (value === null || value === undefined) return ''; if (value > 0) { return inverse ? 'num danger' : 'num'; } else if (value < 0) { return inverse ? 'num' : 'num danger'; } return 'num'; }; // 渲染tab内容 const renderTabContent = () => { if (activeTab === 'calculate') { return (

指标历史数据

(数据来源: wrtb_info_indicator_history)
{historyList.length === 0 ? ( ) : ( historyList.map((item) => ( selectProduct({ productId: item.product_id, productName: item.product_name })} style={{ cursor: 'pointer', background: selectedProduct?.productId === item.product_id ? 'var(--bg)' : 'transparent' }} > )) )}
产品ID 产品名称 统计日期 最新净值 累计收益 年化收益 当前回撤 最大回撤 年化波动 夏普比率 卡玛比率
暂无数据,请先计算指标
{item.product_id} {item.product_name || '-'} {item.date || '-'} {formatNum(item.latest_nav, 4)} {formatPct(item.cumulative_return)} {formatPct(item.annual_return)} {formatPct(item.current_drawdown)} {formatPct(item.max_drawdown)} {formatPct(item.annual_volatility)} {formatNum(item.sharpe, 2)} {formatNum(item.calmar, 2)}
{/* 分页控件 */} {historyList.length > 0 && (
共 {pagination.total} 条记录,当前第 {pagination.page} / {pagination.totalPages} 页
第 {pagination.page} 页
每页
)}
); } else if (activeTab === 'sync') { return (

指标汇总数据

(数据来源: wrtb_info_indicator)
{summaryList.length === 0 ? ( ) : ( summaryList.map((item) => ( selectProduct({ productId: item.product_id, productName: item.product_name })} style={{ cursor: 'pointer', background: selectedProduct?.productId === item.product_id ? 'var(--bg)' : 'transparent' }} > )) )}
产品ID 产品名称 最新日期 最新净值 累计收益 年化收益 当前回撤 最大回撤 年化波动 夏普比率 卡玛比率 操作
暂无数据,请先同步指标
{item.product_id} {item.product_name || '-'} {item.latest_date || '-'} {formatNum(item.latest_nav, 4)} {formatPct(item.cumulative_return)} {formatPct(item.annual_return)} {formatPct(item.current_drawdown)} {formatPct(item.max_drawdown)} {formatPct(item.annual_volatility)} {formatNum(item.sharpe, 2)} {formatNum(item.calmar, 2)}
{/* 分页控件 */} {summaryList.length > 0 && (
共 {summaryPagination.total} 条记录,当前第 {summaryPagination.page} / {summaryPagination.totalPages} 页
第 {summaryPagination.page} 页
每页
)}
); } else if (activeTab === 'strategy-calculate') { return (

策略指标历史数据

(数据来源: wrtb_info_indicator_history)
{historyList.length === 0 ? ( ) : ( historyList.map((item) => ( selectProduct({ productId: item.product_id, productName: item.product_name })} style={{ cursor: 'pointer', background: selectedProduct?.productId === item.product_id ? 'var(--bg)' : 'transparent' }} > )) )}
产品ID 产品名称 统计日期 单期最大调仓 累计调仓幅度 策略集中度HHI 平均现金仓位 最新现金仓位
暂无数据,请先计算策略指标
{item.product_id} {item.product_name || '-'} {item.date || '-'} {formatPct(item.max_single_adjustment)} {formatPct(item.cumulative_turnover)} {formatNum(item.hhi, 4)} {formatPct(item.average_cash_weight)} {formatPct(item.latest_cash_weight)}
{/* 分页控件 */} {historyList.length > 0 && (
共 {pagination.total} 条记录,当前第 {pagination.page} / {pagination.totalPages} 页
第 {pagination.page} 页
每页
)}
); } else { return (

策略指标汇总数据

(数据来源: wrtb_info_indicator)
{summaryList.length === 0 ? ( ) : ( summaryList.map((item) => ( selectProduct({ productId: item.product_id, productName: item.product_name })} style={{ cursor: 'pointer', background: selectedProduct?.productId === item.product_id ? 'var(--bg)' : 'transparent' }} > )) )}
产品ID 产品名称 最新日期 单期最大调仓 累计调仓幅度 策略集中度HHI 平均现金仓位 最新现金仓位 操作
暂无数据,请先同步策略指标
{item.product_id} {item.product_name || '-'} {item.latest_date || '-'} {formatPct(item.max_single_adjustment)} {formatPct(item.cumulative_turnover)} {formatNum(item.hhi, 4)} {formatPct(item.average_cash_weight)} {formatPct(item.latest_cash_weight)}
{/* 分页控件 */} {summaryList.length > 0 && (
共 {summaryPagination.total} 条记录,当前第 {summaryPagination.page} / {summaryPagination.totalPages} 页
第 {summaryPagination.page} 页
每页
)}
); } }; return (
{/* 操作栏 */}
{/* Tab切换 */}
{/* 筛选条件 */}
{/* 产品选择 */}
{/* 净值日期(仅指标计算tab显示) */} {activeTab === 'calculate' && (
{ setFilterDate(e.target.value); setPagination(prev => ({ ...prev, page: 1 })); fetchHistoryList(null, 1); }} placeholder="选择日期" /> 📅
)} {/* 重置复选框(仅指标计算和策略指标计算tab显示) */} {(activeTab === 'calculate' || activeTab === 'strategy-calculate') && (
setResetAll(e.target.checked)} /> (删除历史数据,重新计算所有日期)
)} {/* 操作按钮 */}
{activeTab === 'calculate' ? ( ) : activeTab === 'sync' ? ( ) : activeTab === 'strategy-calculate' ? ( ) : ( )}
{/* Tab内容 */} {renderTabContent()} {/* 时序数据展示 */} {selectedProduct && timeSeries.length > 0 && (

{selectedProduct.productId} - 时序指标

{/* 净值曲线 */}

净值曲线

d.latest_nav) } ]} dates={timeSeries.map(d => d.date)} height={180} format="nav" showXLabels={true} />
{/* 回撤曲线 */}

回撤曲线

d.current_drawdown) }, { name: '最大回撤', color: '#d69e2e', data: timeSeries.map(d => d.max_drawdown) } ]} dates={timeSeries.map(d => d.date)} height={180} format="pct" showXLabels={true} />
{/* 收益曲线 */}

收益曲线

d.cumulative_return) } ]} dates={timeSeries.map(d => d.date)} height={180} format="pct" showXLabels={true} />
{/* 风险指标 */}

风险指标

d.annual_volatility) }, { name: '夏普比率', color: '#3182ce', data: timeSeries.map(d => d.sharpe) }, { name: '卡玛比率', color: '#2f855a', data: timeSeries.map(d => d.calmar) } ]} dates={timeSeries.map(d => d.date)} height={180} format="num" showXLabels={true} />
)} {/* 温馨提示 - 计算逻辑说明 */}
温馨提示:

指标计算:

  • 如果产品选择"全部"且日期为空:计算所有产品,从历史数据最后日期到当前日期
  • 如果选择特定产品:仅计算该产品的指标
  • 如果选择日期:计算指定日期的数据
  • 如果勾选"数据全部重置":删除历史数据,从产品最早日期重新计算到当前

指标同步:

  • 将 wrtb_info_indicator_history 表中的最新数据同步到 wrtb_info_indicator 表
  • 支持按产品筛选,同步单个产品或全部产品

数据来源:

  • wrtb_info_indicator_history:产品每日指标历史数据(时序数据)
  • wrtb_info_indicator:产品最新指标汇总数据
); } window.PageOperationIndicator = PageOperationIndicator;