|
|
@@ -327,7 +327,10 @@
|
|
|
<span style="color: #555;">
|
|
|
图表上的标记点显示所有交易事件。点击标记可查看详情。
|
|
|
<span style="margin-left: 10px;">
|
|
|
- 📈开多 | 📉开空 | 🔼平多 | 🔽平空
|
|
|
+ ⬆️ 买入(向上箭头) | ⬇️ 卖出(向下箭头)
|
|
|
+ </span>
|
|
|
+ <span style="margin-left: 10px; font-size: 0.9em;">
|
|
|
+ �开仓 �平仓
|
|
|
</span>
|
|
|
</span>
|
|
|
</div>
|
|
|
@@ -582,7 +585,7 @@
|
|
|
if (nearbyEvents.length > 0) {
|
|
|
result += '<br/><strong style="color: #667eea;">📌 交易事件:</strong><br/>';
|
|
|
nearbyEvents.forEach(event => {
|
|
|
- const icon = getEventIcon(event.event_type);
|
|
|
+ const icon = getEventIcon(event.event_type, event.side);
|
|
|
const eventName = formatEventType(event.event_type);
|
|
|
result += `${icon} ${eventName} @ ${event.price ? event.price.toFixed(6) : '-'}<br/>`;
|
|
|
});
|
|
|
@@ -744,7 +747,7 @@
|
|
|
if (nearbyEvents.length > 0) {
|
|
|
result += '<br/><strong style="color: #667eea;">📌 交易事件:</strong><br/>';
|
|
|
nearbyEvents.forEach(event => {
|
|
|
- const icon = getEventIcon(event.event_type);
|
|
|
+ const icon = getEventIcon(event.event_type, event.side);
|
|
|
const eventName = formatEventType(event.event_type);
|
|
|
result += `${icon} ${eventName} @ ${event.price ? event.price.toFixed(6) : '-'}<br/>`;
|
|
|
});
|
|
|
@@ -899,34 +902,38 @@
|
|
|
tradingEventsData.forEach(event => {
|
|
|
const timestamp = event.timestamp * 1000; // 转换为毫秒
|
|
|
const eventType = event.event_type;
|
|
|
+ const side = event.side; // long 或 short
|
|
|
+
|
|
|
+ // 确定颜色和符号 - 根据方向:买=向上,卖=向下
|
|
|
+ let color, symbol, symbolSize, label, symbolRotate = 0;
|
|
|
|
|
|
- // 确定颜色和符号
|
|
|
- let color, symbol, symbolSize, label;
|
|
|
+ // 判断是开仓还是平仓
|
|
|
+ const isOpen = eventType.includes('open');
|
|
|
+ const isClose = eventType.includes('close');
|
|
|
|
|
|
- if (eventType.includes('open_long')) {
|
|
|
- color = '#28a745'; // 绿色
|
|
|
- symbol = 'arrow'; // 向上箭头
|
|
|
+ // 根据side确定箭头方向
|
|
|
+ if (side === 'long') {
|
|
|
+ // 买入 = 向上箭头
|
|
|
+ symbol = 'arrow';
|
|
|
symbolSize = 15;
|
|
|
- label = '开多';
|
|
|
- } else if (eventType.includes('open_short')) {
|
|
|
- color = '#dc3545'; // 红色
|
|
|
- symbol = 'arrow'; // 向下箭头(会旋转180度)
|
|
|
+ symbolRotate = 0; // 向上
|
|
|
+
|
|
|
+ color = '#28a745';
|
|
|
+ label = '买入';
|
|
|
+ } else if (side === 'short') {
|
|
|
+ // 卖出 = 向下箭头
|
|
|
+ symbol = 'arrow';
|
|
|
symbolSize = 15;
|
|
|
- label = '开空';
|
|
|
- } else if (eventType.includes('close_long')) {
|
|
|
- color = '#007bff'; // 蓝色
|
|
|
- symbol = 'triangle'; // 三角形
|
|
|
- symbolSize = 12;
|
|
|
- label = '平多';
|
|
|
- } else if (eventType.includes('close_short')) {
|
|
|
- color = '#6f42c1'; // 紫色
|
|
|
- symbol = 'triangle'; // 三角形
|
|
|
- symbolSize = 12;
|
|
|
- label = '平空';
|
|
|
+ symbolRotate = 180; // 向下
|
|
|
+
|
|
|
+ color = '#dc3545';
|
|
|
+ label = '卖出';
|
|
|
} else {
|
|
|
+ // 未知方向
|
|
|
color = '#6c757d'; // 灰色
|
|
|
symbol = 'circle';
|
|
|
symbolSize = 10;
|
|
|
+ symbolRotate = 0;
|
|
|
label = '其他';
|
|
|
}
|
|
|
|
|
|
@@ -950,7 +957,7 @@
|
|
|
value: label,
|
|
|
symbol: symbol,
|
|
|
symbolSize: symbolSize,
|
|
|
- symbolRotate: eventType.includes('short') ? 180 : 0, // 空头箭头向下
|
|
|
+ symbolRotate: symbolRotate, // 使用计算好的旋转角度
|
|
|
itemStyle: {
|
|
|
color: color,
|
|
|
borderColor: '#fff',
|
|
|
@@ -1040,7 +1047,7 @@
|
|
|
const details = `
|
|
|
<div style="background: white; padding: 20px; border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.2); max-width: 500px; margin: 20px auto;">
|
|
|
<h3 style="margin-top: 0; color: #667eea; border-bottom: 2px solid #667eea; padding-bottom: 10px;">
|
|
|
- ${getEventIcon(event.event_type)} ${formatEventType(event.event_type)}
|
|
|
+ ${getEventIcon(event.event_type, event.side)} ${formatEventType(event.event_type)}
|
|
|
</h3>
|
|
|
<table style="width: 100%; border-collapse: collapse;">
|
|
|
<tr>
|
|
|
@@ -1294,7 +1301,7 @@
|
|
|
<td>${new Date(event.timestamp * 1000).toLocaleString()}</td>
|
|
|
<td>${event.symbol || '-'}</td>
|
|
|
<td class="${getEventClass(event.event_type)}">
|
|
|
- ${getEventIcon(event.event_type)} ${formatEventType(event.event_type)}
|
|
|
+ ${getEventIcon(event.event_type, event.side)} ${formatEventType(event.event_type)}
|
|
|
</td>
|
|
|
<td>${event.price ? event.price.toFixed(6) : '-'}</td>
|
|
|
<td>${event.quantity || '-'}</td>
|
|
|
@@ -1326,14 +1333,28 @@
|
|
|
return '';
|
|
|
}
|
|
|
|
|
|
- // 获取事件图标
|
|
|
- function getEventIcon(eventType) {
|
|
|
- if (eventType.includes('open_long')) return '📈';
|
|
|
- if (eventType.includes('open_short')) return '📉';
|
|
|
- if (eventType.includes('close_long')) return '🔼';
|
|
|
- if (eventType.includes('close_short')) return '🔽';
|
|
|
- if (eventType.includes('open')) return '🟢';
|
|
|
- if (eventType.includes('close')) return '🔴';
|
|
|
+ // 获取事件图标 - 根据side:买=向上,卖=向下
|
|
|
+ function getEventIcon(eventType, side) {
|
|
|
+ // 如果没有传side参数,尝试从eventType推断
|
|
|
+ if (!side) {
|
|
|
+ if (eventType.includes('long')) side = 'long';
|
|
|
+ else if (eventType.includes('short')) side = 'short';
|
|
|
+ }
|
|
|
+
|
|
|
+ const isOpen = eventType.includes('open');
|
|
|
+ const isClose = eventType.includes('close');
|
|
|
+
|
|
|
+ if (side === 'long') {
|
|
|
+ // 买入 = 向上箭头
|
|
|
+ if (isOpen) return '�'; // 开多
|
|
|
+ if (isClose) return '🔼'; // 平多
|
|
|
+ return '⬆️'; // 默认买入
|
|
|
+ } else if (side === 'short') {
|
|
|
+ // 卖出 = 向下箭头
|
|
|
+ if (isOpen) return '�'; // 开空
|
|
|
+ if (isClose) return '�'; // 平空
|
|
|
+ return '⬇️'; // 默认卖出
|
|
|
+ }
|
|
|
return '⚪';
|
|
|
}
|
|
|
|
|
|
@@ -1463,46 +1484,54 @@
|
|
|
// 根据选中的时间范围更新主图表
|
|
|
function updateMainChartsWithSelectedRange() {
|
|
|
if (!selectedTimeRange) return;
|
|
|
-
|
|
|
+
|
|
|
// 计算选中时间范围的小时数
|
|
|
const timeRangeHours = (selectedTimeRange.end - selectedTimeRange.start) / 3600;
|
|
|
const symbol = document.getElementById('symbolFilter').value.trim();
|
|
|
-
|
|
|
- // 从完整的价格数据API获取选定时间范围的数据
|
|
|
- fetch(`/api/price_data?hours=${timeRangeHours}&symbol=${symbol}&start_time=${selectedTimeRange.start}`)
|
|
|
- .then(response => response.json())
|
|
|
- .then(result => {
|
|
|
- if (result.success && result.data && result.data.length > 0) {
|
|
|
- // 过滤出选中时间范围内的数据
|
|
|
- const filteredData = result.data.filter(d =>
|
|
|
- d.timestamp >= selectedTimeRange.start && d.timestamp <= selectedTimeRange.end
|
|
|
- );
|
|
|
-
|
|
|
- if (filteredData.length > 0) {
|
|
|
- // 更新价格图表
|
|
|
- displayPriceChart(filteredData);
|
|
|
- // 更新BPS图表
|
|
|
- displayBpsChart(filteredData);
|
|
|
-
|
|
|
- // 更新时间范围显示
|
|
|
- updateTimeRangeDisplay();
|
|
|
+
|
|
|
+ // 同时获取价格数据和交易事件
|
|
|
+ Promise.all([
|
|
|
+ fetch(`/api/price_data?hours=${timeRangeHours}&symbol=${symbol}&start_time=${selectedTimeRange.start}&db_path=${currentDbPath}`).then(r => r.json()),
|
|
|
+ fetch(`/api/trading_events?hours=${timeRangeHours}&symbol=${symbol}&db_path=${currentDbPath}`).then(r => r.json())
|
|
|
+ ])
|
|
|
+ .then(([priceResult, eventsResult]) => {
|
|
|
+ if (priceResult.success && priceResult.data && priceResult.data.length > 0) {
|
|
|
+ // 过滤出选中时间范围内的数据
|
|
|
+ const filteredData = priceResult.data.filter(d =>
|
|
|
+ d.timestamp >= selectedTimeRange.start && d.timestamp <= selectedTimeRange.end
|
|
|
+ );
|
|
|
+
|
|
|
+ if (filteredData.length > 0) {
|
|
|
+ // 更新价格图表
|
|
|
+ displayPriceChart(filteredData);
|
|
|
+ // 更新BPS图表
|
|
|
+ displayBpsChart(filteredData);
|
|
|
+
|
|
|
+ // 更新时间范围显示
|
|
|
+ updateTimeRangeDisplay();
|
|
|
+
|
|
|
+ // 更新交易事件数据
|
|
|
+ if (eventsResult.success && eventsResult.data) {
|
|
|
+ tradingEventsData = eventsResult.data.filter(e =>
|
|
|
+ e.timestamp >= selectedTimeRange.start && e.timestamp <= selectedTimeRange.end
|
|
|
+ );
|
|
|
+ displayTradingEvents(tradingEventsData);
|
|
|
|
|
|
// 重新添加事件标记
|
|
|
- if (tradingEventsData && tradingEventsData.length > 0) {
|
|
|
- setTimeout(() => {
|
|
|
- addEventMarkersToCharts();
|
|
|
- }, 200);
|
|
|
- }
|
|
|
- } else {
|
|
|
- console.log('过滤后没有数据');
|
|
|
+ setTimeout(() => {
|
|
|
+ addEventMarkersToCharts();
|
|
|
+ }, 200);
|
|
|
}
|
|
|
} else {
|
|
|
- console.log('API返回数据为空或失败:', result);
|
|
|
+ console.log('过滤后没有数据');
|
|
|
}
|
|
|
- })
|
|
|
- .catch(error => {
|
|
|
- console.error('获取选定时间范围数据失败:', error);
|
|
|
- });
|
|
|
+ } else {
|
|
|
+ console.log('API返回数据为空或失败:', priceResult);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('获取选定时间范围数据失败:', error);
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
// 更新时间范围显示
|