{{ event.name }}
- {{ event.country }}
+ {{ event.currency }}
@@ -356,7 +360,7 @@ export default {
const symbolEvents = ref([])
const selectedImportance = ref(null)
- const selectedCountry = ref(null)
+ const selectedCurrency = ref(null)
const selectedSymbol = ref('GOLD')
const ws = ref(null)
@@ -373,13 +377,27 @@ export default {
{ text: '低影响', value: 1 }
]
- const countryOptions = [
- { text: '美国 (US)', value: 'US' },
- { text: '日本 (JP)', value: 'JP' },
- { text: '欧洲 (EU)', value: 'EU' },
- { text: '英国 (UK)', value: 'UK' },
- { text: '中国 (CN)', value: 'CN' }
- ]
+ // 货币选项(动态生成)
+ const currencyOptions = computed(() => {
+ const currencies = new Set()
+ calendar.value.forEach(e => {
+ if (e.currency) currencies.add(e.currency)
+ })
+ const currencyNames = {
+ 'USD': '美元 (USD)',
+ 'EUR': '欧元 (EUR)',
+ 'GBP': '英镑 (GBP)',
+ 'JPY': '日元 (JPY)',
+ 'AUD': '澳元 (AUD)',
+ 'CAD': '加元 (CAD)',
+ 'CHF': '瑞郎 (CHF)',
+ 'NZD': '纽元 (NZD)'
+ }
+ return Array.from(currencies).sort().map(c => ({
+ text: currencyNames[c] || c,
+ value: c
+ }))
+ })
const symbolOptions = [
{ text: '黄金 (GOLD)', value: 'GOLD' },
@@ -392,7 +410,7 @@ export default {
const calendarHeaders = [
{ text: '重要性', value: 'importance', width: 100 },
{ text: '事件', value: 'name', width: 200 },
- { text: '国家', value: 'country', width: 80 },
+ { text: '货币', value: 'currency', width: 80 },
{ text: '发布时间', value: 'publish_time', width: 150 },
{ text: '数值', value: 'values', width: 120 },
{ text: '结果', value: 'result', width: 100 },
@@ -406,8 +424,8 @@ export default {
items = items.filter(e => e.importance === selectedImportance.value)
}
- if (selectedCountry.value) {
- items = items.filter(e => e.country === selectedCountry.value)
+ if (selectedCurrency.value) {
+ items = items.filter(e => e.currency === selectedCurrency.value)
}
return items
@@ -634,10 +652,10 @@ export default {
flashNews,
symbolEvents,
selectedImportance,
- selectedCountry,
+ selectedCurrency,
selectedSymbol,
importanceOptions,
- countryOptions,
+ currencyOptions,
symbolOptions,
calendarHeaders,
filteredCalendar,
diff --git a/market/news_store.py b/market/news_store.py
index 9076bb1..59ea0ce 100644
--- a/market/news_store.py
+++ b/market/news_store.py
@@ -10,6 +10,66 @@ from datetime import datetime, timedelta
from typing import List, Dict, Optional
import threading
from dataclasses import dataclass, field
+import re
+
+
+# MT5中表示无效值的特殊数值
+MT5_INVALID_VALUE = -9223372036854775808.0
+
+
+def _clean_invalid_value(value: str) -> str:
+ """清理MT5返回的无效值"""
+ if not value:
+ return ""
+ try:
+ # 检查是否是无效值
+ num = float(value)
+ if num == MT5_INVALID_VALUE or num < -1e15:
+ return ""
+ # 格式化有效数值
+ if num == int(num):
+ return str(int(num))
+ return value
+ except (ValueError, TypeError):
+ return value
+
+
+def _clean_text(text: str) -> str:
+ """清理文本中的控制字符和无效Unicode"""
+ if not text:
+ return ""
+ # 移除控制字符 (0x00-0x1F 和 0x7F)
+ cleaned = re.sub(r'[\x00-\x1f\x7f]', '', text)
+ # 移除不可打印字符(保留ASCII、中文等常用字符)
+ # 保留:ASCII (0x20-0x7E), 中文 (0x4E00-0x9FFF), 其他常用Unicode
+ result = []
+ for char in cleaned:
+ code = ord(char)
+ if (0x20 <= code <= 0x7E or # ASCII可打印字符
+ 0x4E00 <= code <= 0x9FFF or # CJK统一汉字
+ 0x3000 <= code <= 0x303F or # CJK标点
+ 0xFF00 <= code <= 0xFFEF or # 全角字符
+ code > 0x9FFF): # 其他Unicode字符
+ result.append(char)
+ return ''.join(result)
+
+
+def _is_valid_name(name: str) -> bool:
+ """检查名称是否有效(不是乱码)"""
+ if not name or len(name) < 2:
+ return False
+ # 计算可打印字符的比例
+ printable_count = 0
+ for char in name:
+ code = ord(char)
+ if (0x20 <= code <= 0x7E or # ASCII可打印字符
+ 0x4E00 <= code <= 0x9FFF or # CJK统一汉字
+ 0x3000 <= code <= 0x303F or # CJK标点
+ 0xFF00 <= code <= 0xFFEF): # 全角字符
+ printable_count += 1
+ # 如果可打印字符比例低于70%,认为是乱码
+ ratio = printable_count / len(name) if name else 0
+ return ratio >= 0.7
@dataclass
@@ -167,18 +227,24 @@ class NewsStore:
if publish_time < expiry_threshold:
continue
+ # 清理并检查名称有效性
+ cleaned_name = _clean_text(event_data.get('name', ''))
+ if not _is_valid_name(cleaned_name):
+ # 名称无效(乱码),跳过此事件
+ continue
+
# 创建事件对象
event = CalendarEvent(
id=event_id,
- name=event_data.get('name', ''),
- name_en=event_data.get('name_en', ''),
- country=event_data.get('country', ''),
+ name=cleaned_name,
+ name_en=_clean_text(event_data.get('name_en', '')),
+ country=_clean_text(event_data.get('country', '')),
currency=event_data.get('currency', ''),
importance=event_data.get('importance', 0),
publish_time=publish_time,
- forecast=event_data.get('forecast', ''),
- previous=event_data.get('previous', ''),
- actual=event_data.get('actual', ''),
+ forecast=_clean_invalid_value(event_data.get('forecast', '')),
+ previous=_clean_invalid_value(event_data.get('previous', '')),
+ actual=_clean_invalid_value(event_data.get('actual', '')),
unit=event_data.get('unit', ''),
symbols=event_data.get('symbols', []),
event_type=event_data.get('event_type', '')
diff --git a/routes_ea.py b/routes_ea.py
index 4006446..0b86737 100644
--- a/routes_ea.py
+++ b/routes_ea.py
@@ -274,14 +274,14 @@ def create_ea_routes(server: TradingServer) -> APIRouter:
print(f"[calendar] 收到请求, 数据长度: {len(raw_text)} 字节")
- # 清理所有控制字符 (0x00-0x1F, 除了 \t \n \r)
- # 保留 tab(0x09), LF(0x0A), CR(0x0D)
+ # 清理所有控制字符 (0x00-0x1F 和 0x7F)
+ # JSON字符串值中不能包含任何原始控制字符
def clean_control_chars(text):
# 使用正则表达式一次性清理所有控制字符
- # 除了 tab(0x09), LF(0x0A), CR(0x0D)
+ # 包括 TAB(0x09), LF(0x0A), CR(0x0D), DEL(0x7F) 在内
+ # 因为 JSON 字符串值中这些字符必须转义,不能直接出现
import re
- # 匹配所有控制字符 (0x00-0x1F) 除了 \t \n \r
- pattern = re.compile(r'[\x00-\x08\x0b\x0c\x0e-\x1f]')
+ pattern = re.compile(r'[\x00-\x1f\x7f]')
cleaned = pattern.sub('', text)
removed_count = len(text) - len(cleaned)
if removed_count > 0:
diff --git a/wangxxGold.mq5 b/wangxxGold.mq5
index affc062..3587336 100644
--- a/wangxxGold.mq5
+++ b/wangxxGold.mq5
@@ -1525,7 +1525,8 @@ void SendCalendarToPython()
//+------------------------------------------------------------------+
//| JSON字符串转义 |
-//| 转义所有JSON无效的控制字符(0x00-0x1F) |
+//| 转义所有JSON无效的控制字符(0x00-0x1F)和非ASCII字符 |
+//| 非ASCII字符使用\uXXXX格式转义,确保Unicode正确传递 |
//+------------------------------------------------------------------+
string EscapeJsonString(string str)
{
@@ -1544,15 +1545,22 @@ string EscapeJsonString(string str)
case 0x08: result += "\\b"; break; // backspace
case 0x0C: result += "\\f"; break; // form feed
default:
- // 转义所有其他控制字符 (0x00-0x1F)
- if(ch < 32)
+ // 转义所有控制字符 (0x00-0x1F) 和 DEL (0x7F)
+ if(ch < 32 || ch == 0x7F)
{
// 使用 \uXXXX 格式转义
result += "\\u" + StringFormat("%04X", ch);
}
+ else if(ch < 128)
+ {
+ // ASCII 字符直接添加
+ result += CharToString((uchar)ch);
+ }
else
{
- result += CharToString((uchar)ch);
+ // 非ASCII字符(如中文)使用 \uXXXX 格式转义
+ // 这样可以避免 StringToCharArray 的编码问题
+ result += "\\u" + StringFormat("%04X", ch);
}
}
}