Prefer SQLite signals in stock explain view
This commit is contained in:
@@ -153,6 +153,71 @@ function resolveEventCategory(event) {
|
||||
return 'signal';
|
||||
}
|
||||
|
||||
function normalizeTradeRow(row, fallbackIndex = 0) {
|
||||
if (!row || typeof row !== 'object') return null;
|
||||
const timestamp = row.timestamp || row.ts || row.created_at || null;
|
||||
const ticker = row.ticker || '';
|
||||
const side = row.side || '';
|
||||
const qtyValue = Number(row.qty ?? row.quantity ?? 0);
|
||||
const priceValue = Number(row.price ?? 0);
|
||||
return {
|
||||
id: row.id || `trade-${ticker}-${timestamp || fallbackIndex}-${fallbackIndex}`,
|
||||
timestamp,
|
||||
trading_date: row.trading_date || row.trade_date || null,
|
||||
ticker,
|
||||
side,
|
||||
qty: Number.isFinite(qtyValue) ? qtyValue : 0,
|
||||
price: Number.isFinite(priceValue) ? priceValue : 0
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSignalRow(row, fallbackIndex = 0) {
|
||||
if (!row || typeof row !== 'object') return null;
|
||||
const timestamp = row.timestamp || row.created_at || null;
|
||||
const date = row.date || row.trade_date || eventDateKey(timestamp) || '';
|
||||
const rawSignal = row.signal || row.title || '';
|
||||
const normalizedDirection = normalizeSignalDirection(rawSignal);
|
||||
const confidenceValue = Number(row.confidence);
|
||||
const realReturnValue = Number(row.real_return);
|
||||
const parsedCorrect = typeof row.is_correct === 'string'
|
||||
? row.is_correct.toLowerCase() === 'true'
|
||||
? true
|
||||
: row.is_correct.toLowerCase() === 'false'
|
||||
? false
|
||||
: null
|
||||
: typeof row.is_correct === 'boolean'
|
||||
? row.is_correct
|
||||
: null;
|
||||
|
||||
return {
|
||||
id: row.id || `signal-${row.agent_id || row.agentId || 'agent'}-${date || fallbackIndex}-${fallbackIndex}`,
|
||||
timestamp,
|
||||
date,
|
||||
ticker: row.ticker || '',
|
||||
signal: rawSignal,
|
||||
confidence: Number.isFinite(confidenceValue) ? confidenceValue : null,
|
||||
real_return: Number.isFinite(realReturnValue) ? realReturnValue : null,
|
||||
is_correct: parsedCorrect,
|
||||
agentId: row.agent_id || row.agentId || '',
|
||||
agentName: row.agent_name || row.agentName || row.meta || '未知分析师',
|
||||
role: row.role || row.meta || '',
|
||||
normalizedDirection
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeMentionRow(row, fallbackIndex = 0) {
|
||||
if (!row || typeof row !== 'object') return null;
|
||||
return {
|
||||
id: row.id || `mention-${fallbackIndex}`,
|
||||
feedId: row.id || `mention-${fallbackIndex}`,
|
||||
timestamp: row.timestamp || null,
|
||||
agent: row.agent || row.agentName || '未知角色',
|
||||
content: row.body || row.content || '',
|
||||
conferenceTitle: row.meta || '',
|
||||
feedType: 'sqlite'
|
||||
};
|
||||
}
|
||||
|
||||
const EVENT_CATEGORY_META = {
|
||||
all: { label: '全部事件', color: '#111111' },
|
||||
discussion: { label: '讨论', color: '#555555' },
|
||||
@@ -210,7 +275,7 @@ export default function StockExplainView({
|
||||
[holdings, selectedSymbol]
|
||||
);
|
||||
|
||||
const tickerTrades = useMemo(
|
||||
const fallbackTrades = useMemo(
|
||||
() => trades
|
||||
.filter((trade) => trade.ticker === selectedSymbol)
|
||||
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()),
|
||||
@@ -218,6 +283,12 @@ export default function StockExplainView({
|
||||
);
|
||||
|
||||
const tickerSignals = useMemo(() => {
|
||||
const snapshotSignals = Array.isArray(explainEventsSnapshot?.signals)
|
||||
? explainEventsSnapshot.signals.map((signal, index) => normalizeSignalRow(signal, index)).filter(Boolean)
|
||||
: [];
|
||||
if (snapshotSignals.length > 0) {
|
||||
return snapshotSignals.sort((a, b) => new Date(b.timestamp || b.date).getTime() - new Date(a.timestamp || a.date).getTime());
|
||||
}
|
||||
if (!selectedSymbol) return [];
|
||||
return (Array.isArray(leaderboard) ? leaderboard : []).flatMap((agent) => {
|
||||
const signals = Array.isArray(agent.signals) ? agent.signals : [];
|
||||
@@ -231,7 +302,7 @@ export default function StockExplainView({
|
||||
normalizedDirection: normalizeSignalDirection(signal.signal)
|
||||
}));
|
||||
}).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||
}, [leaderboard, selectedSymbol]);
|
||||
}, [explainEventsSnapshot, leaderboard, selectedSymbol]);
|
||||
|
||||
const signalSummary = useMemo(() => {
|
||||
const summary = { bullish: 0, bearish: 0, neutral: 0 };
|
||||
@@ -241,7 +312,7 @@ export default function StockExplainView({
|
||||
return summary;
|
||||
}, [tickerSignals]);
|
||||
|
||||
const recentMentions = useMemo(() => {
|
||||
const fallbackRecentMentions = useMemo(() => {
|
||||
const flattened = flattenFeedMessages(feed);
|
||||
return flattened
|
||||
.filter((message) => message.agent !== 'System' && includesTicker(message.content, selectedSymbol))
|
||||
@@ -249,6 +320,29 @@ export default function StockExplainView({
|
||||
.slice(0, 8);
|
||||
}, [feed, selectedSymbol]);
|
||||
|
||||
const tickerTrades = useMemo(() => {
|
||||
const snapshotTrades = Array.isArray(explainEventsSnapshot?.trades)
|
||||
? explainEventsSnapshot.trades.map((trade, index) => normalizeTradeRow(trade, index)).filter(Boolean)
|
||||
: [];
|
||||
if (snapshotTrades.length > 0) {
|
||||
return snapshotTrades.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
||||
}
|
||||
return fallbackTrades;
|
||||
}, [explainEventsSnapshot, fallbackTrades]);
|
||||
|
||||
const recentMentions = useMemo(() => {
|
||||
const snapshotMentions = Array.isArray(explainEventsSnapshot?.events)
|
||||
? explainEventsSnapshot.events
|
||||
.map((event, index) => normalizeMentionRow(event, index))
|
||||
.filter(Boolean)
|
||||
.slice(0, 8)
|
||||
: [];
|
||||
if (snapshotMentions.length > 0) {
|
||||
return snapshotMentions.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
||||
}
|
||||
return fallbackRecentMentions;
|
||||
}, [explainEventsSnapshot, fallbackRecentMentions]);
|
||||
|
||||
const latestSignal = tickerSignals[0] || null;
|
||||
const priceColor = selectedTicker?.change > 0 ? '#00C853' : selectedTicker?.change < 0 ? '#FF1744' : '#000000';
|
||||
const exposureWeight = holding && Number.isFinite(Number(holding.weight)) ? Number(holding.weight) * 100 : null;
|
||||
@@ -769,7 +863,7 @@ export default function StockExplainView({
|
||||
</div>
|
||||
<div style={{ height: 1, background: '#e0e0e0', margin: '4px 0' }} />
|
||||
<div style={{ fontSize: 12, lineHeight: 1.7, color: '#666666' }}>
|
||||
这版解释以运行中的 agent 输出为主,不依赖外部新闻库或单股历史事件数据库。
|
||||
这版解释优先读取当前 run 的 SQLite 历史快照,拿不到时再回退到运行中的内存态事件。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -809,7 +903,7 @@ export default function StockExplainView({
|
||||
: '暂无'}
|
||||
</div>
|
||||
<div style={{ marginTop: 8, fontSize: 11, color: '#666666' }}>
|
||||
{latestSignal ? `${latestSignal.agentName} · ${latestSignal.date}` : '还没有历史信号'}
|
||||
{latestSignal ? `${latestSignal.agentName} · ${latestSignal.date || eventDateKey(latestSignal.timestamp)}` : '还没有历史信号'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -846,8 +940,8 @@ export default function StockExplainView({
|
||||
: '#666666';
|
||||
|
||||
return (
|
||||
<tr key={`${signal.agentId}-${signal.date}-${index}`}>
|
||||
<td>{signal.date}</td>
|
||||
<tr key={signal.id || `${signal.agentId}-${signal.date}-${index}`}>
|
||||
<td>{signal.date || eventDateKey(signal.timestamp) || '-'}</td>
|
||||
<td>
|
||||
<div style={{ fontWeight: 700 }}>{signal.agentName}</div>
|
||||
<div style={{ fontSize: 10, color: '#666666' }}>{signal.role}</div>
|
||||
|
||||
Reference in New Issue
Block a user