/* === 产品净值指标页面 === */ /* 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 (
| 产品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)} |
| 产品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)} | |
| 产品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)} |
| 产品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)} | |
指标计算:
指标同步:
数据来源:
wrtb_info_indicator_history:产品每日指标历史数据(时序数据)wrtb_info_indicator:产品最新指标汇总数据