Files
guaiwoluo2020 a705593955 feat: 添加新闻监控、持仓管理、系统日志等功能
- 新增新闻爬取和监控模块 (news_crawler, news_monitor)
- 新增 LLM 分析模块 (llm_analyzer)
- 新增持仓管理和交易历史存储
- 新增系统日志功能
- 新增前端页面: News, Positions, Settings, SystemLog
- 更新路由和 API 接口
- 更新 .gitignore 排除敏感文件
2026-03-17 11:32:37 +08:00

197 lines
5.7 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
系统运行日志模块
保存在内存中,保留最新200条日志
"""
from collections import deque
from datetime import datetime
from typing import Dict, List, Optional, Any
import threading
import json
class SystemLog:
"""系统运行日志"""
# 日志事件类型
EVENT_TYPES = {
# 大模型相关
"llm_analysis_start": "大模型分析开始",
"llm_analysis_complete": "大模型分析完成",
"llm_analysis_error": "大模型分析错误",
# EA数据推送
"ea_statistics": "EA推送统计数据",
"ea_kline_full": "EA推送全量K线",
"ea_kline_incremental": "EA推送增量K线",
"ea_kline_stale": "K线数据过期",
"ea_trade_request": "EA请求交易指令",
# MT5财经日历推送
"mt5_calendar_update": "MT5财经日历上报",
"mt5_event_result": "MT5事件结果上报",
# 转折点相关
"pivot_detected": "转折点检测完成",
"pivot_alert": "转折点提醒",
# 交易指令
"order_generated": "交易指令生成",
"order_confirmed": "交易指令确认",
"order_rejected": "交易指令拒绝",
"close_position": "平仓指令",
# 持仓相关
"position_update": "持仓数据更新",
# 新闻爬虫相关
"news_crawler_start": "新闻爬虫启动",
"news_crawler_stop": "新闻爬虫停止",
"news_calendar_fetch": "财经日历获取",
"news_calendar_fetch_error": "财经日历获取失败",
"news_calendar_update": "财经日历更新",
"news_flash_fetch": "快讯获取",
"news_flash_fetch_error": "快讯获取失败",
"news_event_scheduled": "事件调度创建",
"news_event_reminder": "事件发布前提醒",
"news_event_result": "事件结果获取",
"news_impact_analysis": "影响分析完成",
"news_ws_broadcast": "新闻WebSocket推送",
# 系统事件
"system_startup": "系统启动",
"system_shutdown": "系统关闭",
"websocket_connect": "WebSocket连接",
"websocket_disconnect": "WebSocket断开",
}
def __init__(self, max_size: int = 200):
self._logs = deque(maxlen=max_size)
self._lock = threading.RLock()
self._ws_clients = set()
self._ws_lock = threading.Lock()
self._main_loop = None
print(f"[SystemLog] 系统日志已初始化,最大保留 {max_size} 条")
def set_event_loop(self, loop):
"""设置主事件循环引用"""
self._main_loop = loop
def add_log(self, event_type: str, detail: Dict[str, Any] = None,
symbol: str = None, message: str = None):
"""
添加日志
Args:
event_type: 事件类型
detail: 事件详情
symbol: 相关品种
message: 自定义消息
"""
log_entry = {
"timestamp": datetime.now().isoformat(),
"event_type": event_type,
"event_name": self.EVENT_TYPES.get(event_type, event_type),
"symbol": symbol,
"message": message,
"detail": detail or {}
}
with self._lock:
self._logs.append(log_entry)
# 广播到WebSocket客户端
self._broadcast_log(log_entry)
# 打印到控制台
log_str = f"[SystemLog] {log_entry['timestamp']} | {log_entry['event_name']}"
if symbol:
log_str += f" | {symbol}"
if message:
log_str += f" | {message}"
print(log_str)
def get_logs(self, count: int = 50, event_types: List[str] = None,
symbol: str = None) -> List[Dict]:
"""
获取日志
Args:
count: 获取数量
event_types: 过滤事件类型列表(支持多选)
symbol: 过滤品种
Returns:
日志列表(按时间倒序)
"""
with self._lock:
logs = list(self._logs)
# 过滤
if event_types:
logs = [l for l in logs if l['event_type'] in event_types]
if symbol:
logs = [l for l in logs if l.get('symbol') == symbol]
# 按时间倒序,取最新的
logs = logs[::-1][:count]
return logs
def clear_logs(self):
"""清空日志"""
with self._lock:
self._logs.clear()
print("[SystemLog] 日志已清空")
def add_ws_client(self, client):
"""添加WebSocket客户端"""
with self._ws_lock:
self._ws_clients.add(client)
def remove_ws_client(self, client):
"""移除WebSocket客户端"""
with self._ws_lock:
self._ws_clients.discard(client)
def _broadcast_log(self, log_entry: Dict):
"""广播日志到WebSocket客户端"""
if not self._main_loop:
return
import asyncio
message = json.dumps({
"type": "system_log",
"data": log_entry
})
with self._ws_lock:
clients = list(self._ws_clients)
if not clients:
return
# 在主事件循环中发送
for client in clients:
try:
asyncio.run_coroutine_threadsafe(
client.send_text(message),
self._main_loop
)
except Exception as e:
print(f"[SystemLog] 广播日志失败: {e}")
# 全局单例
_system_log = None
def get_system_log() -> SystemLog:
"""获取系统日志单例"""
global _system_log
if _system_log is None:
_system_log = SystemLog()
return _system_log