Prefer SQLite signals in stock explain view
This commit is contained in:
@@ -153,6 +153,71 @@ function resolveEventCategory(event) {
|
|||||||
return 'signal';
|
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 = {
|
const EVENT_CATEGORY_META = {
|
||||||
all: { label: '全部事件', color: '#111111' },
|
all: { label: '全部事件', color: '#111111' },
|
||||||
discussion: { label: '讨论', color: '#555555' },
|
discussion: { label: '讨论', color: '#555555' },
|
||||||
@@ -210,7 +275,7 @@ export default function StockExplainView({
|
|||||||
[holdings, selectedSymbol]
|
[holdings, selectedSymbol]
|
||||||
);
|
);
|
||||||
|
|
||||||
const tickerTrades = useMemo(
|
const fallbackTrades = useMemo(
|
||||||
() => trades
|
() => trades
|
||||||
.filter((trade) => trade.ticker === selectedSymbol)
|
.filter((trade) => trade.ticker === selectedSymbol)
|
||||||
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()),
|
.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 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 [];
|
if (!selectedSymbol) return [];
|
||||||
return (Array.isArray(leaderboard) ? leaderboard : []).flatMap((agent) => {
|
return (Array.isArray(leaderboard) ? leaderboard : []).flatMap((agent) => {
|
||||||
const signals = Array.isArray(agent.signals) ? agent.signals : [];
|
const signals = Array.isArray(agent.signals) ? agent.signals : [];
|
||||||
@@ -231,7 +302,7 @@ export default function StockExplainView({
|
|||||||
normalizedDirection: normalizeSignalDirection(signal.signal)
|
normalizedDirection: normalizeSignalDirection(signal.signal)
|
||||||
}));
|
}));
|
||||||
}).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
}).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||||
}, [leaderboard, selectedSymbol]);
|
}, [explainEventsSnapshot, leaderboard, selectedSymbol]);
|
||||||
|
|
||||||
const signalSummary = useMemo(() => {
|
const signalSummary = useMemo(() => {
|
||||||
const summary = { bullish: 0, bearish: 0, neutral: 0 };
|
const summary = { bullish: 0, bearish: 0, neutral: 0 };
|
||||||
@@ -241,7 +312,7 @@ export default function StockExplainView({
|
|||||||
return summary;
|
return summary;
|
||||||
}, [tickerSignals]);
|
}, [tickerSignals]);
|
||||||
|
|
||||||
const recentMentions = useMemo(() => {
|
const fallbackRecentMentions = useMemo(() => {
|
||||||
const flattened = flattenFeedMessages(feed);
|
const flattened = flattenFeedMessages(feed);
|
||||||
return flattened
|
return flattened
|
||||||
.filter((message) => message.agent !== 'System' && includesTicker(message.content, selectedSymbol))
|
.filter((message) => message.agent !== 'System' && includesTicker(message.content, selectedSymbol))
|
||||||
@@ -249,6 +320,29 @@ export default function StockExplainView({
|
|||||||
.slice(0, 8);
|
.slice(0, 8);
|
||||||
}, [feed, selectedSymbol]);
|
}, [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 latestSignal = tickerSignals[0] || null;
|
||||||
const priceColor = selectedTicker?.change > 0 ? '#00C853' : selectedTicker?.change < 0 ? '#FF1744' : '#000000';
|
const priceColor = selectedTicker?.change > 0 ? '#00C853' : selectedTicker?.change < 0 ? '#FF1744' : '#000000';
|
||||||
const exposureWeight = holding && Number.isFinite(Number(holding.weight)) ? Number(holding.weight) * 100 : null;
|
const exposureWeight = holding && Number.isFinite(Number(holding.weight)) ? Number(holding.weight) * 100 : null;
|
||||||
@@ -769,7 +863,7 @@ export default function StockExplainView({
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ height: 1, background: '#e0e0e0', margin: '4px 0' }} />
|
<div style={{ height: 1, background: '#e0e0e0', margin: '4px 0' }} />
|
||||||
<div style={{ fontSize: 12, lineHeight: 1.7, color: '#666666' }}>
|
<div style={{ fontSize: 12, lineHeight: 1.7, color: '#666666' }}>
|
||||||
这版解释以运行中的 agent 输出为主,不依赖外部新闻库或单股历史事件数据库。
|
这版解释优先读取当前 run 的 SQLite 历史快照,拿不到时再回退到运行中的内存态事件。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -809,7 +903,7 @@ export default function StockExplainView({
|
|||||||
: '暂无'}
|
: '暂无'}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 8, fontSize: 11, color: '#666666' }}>
|
<div style={{ marginTop: 8, fontSize: 11, color: '#666666' }}>
|
||||||
{latestSignal ? `${latestSignal.agentName} · ${latestSignal.date}` : '还没有历史信号'}
|
{latestSignal ? `${latestSignal.agentName} · ${latestSignal.date || eventDateKey(latestSignal.timestamp)}` : '还没有历史信号'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -846,8 +940,8 @@ export default function StockExplainView({
|
|||||||
: '#666666';
|
: '#666666';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={`${signal.agentId}-${signal.date}-${index}`}>
|
<tr key={signal.id || `${signal.agentId}-${signal.date}-${index}`}>
|
||||||
<td>{signal.date}</td>
|
<td>{signal.date || eventDateKey(signal.timestamp) || '-'}</td>
|
||||||
<td>
|
<td>
|
||||||
<div style={{ fontWeight: 700 }}>{signal.agentName}</div>
|
<div style={{ fontWeight: 700 }}>{signal.agentName}</div>
|
||||||
<div style={{ fontSize: 10, color: '#666666' }}>{signal.role}</div>
|
<div style={{ fontSize: 10, color: '#666666' }}>{signal.role}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user