update pay function
This commit is contained in:
243
mcp_database.py
243
mcp_database.py
@@ -781,3 +781,246 @@ async def remove_favorite_event(user_id: str, event_id: int) -> Dict[str, Any]:
|
||||
return {"success": True, "message": "删除自选事件成功"}
|
||||
else:
|
||||
return {"success": False, "message": "未找到该自选事件"}
|
||||
|
||||
|
||||
# ==================== ClickHouse 分钟频数据查询 ====================
|
||||
|
||||
from clickhouse_driver import Client as ClickHouseClient
|
||||
|
||||
# ClickHouse 连接配置
|
||||
CLICKHOUSE_CONFIG = {
|
||||
'host': '222.128.1.157',
|
||||
'port': 18000,
|
||||
'user': 'default',
|
||||
'password': 'Zzl33818!',
|
||||
'database': 'stock'
|
||||
}
|
||||
|
||||
# ClickHouse 客户端(懒加载)
|
||||
_clickhouse_client = None
|
||||
|
||||
|
||||
def get_clickhouse_client():
|
||||
"""获取 ClickHouse 客户端(单例模式)"""
|
||||
global _clickhouse_client
|
||||
if _clickhouse_client is None:
|
||||
_clickhouse_client = ClickHouseClient(
|
||||
host=CLICKHOUSE_CONFIG['host'],
|
||||
port=CLICKHOUSE_CONFIG['port'],
|
||||
user=CLICKHOUSE_CONFIG['user'],
|
||||
password=CLICKHOUSE_CONFIG['password'],
|
||||
database=CLICKHOUSE_CONFIG['database']
|
||||
)
|
||||
logger.info("ClickHouse client created")
|
||||
return _clickhouse_client
|
||||
|
||||
|
||||
async def get_stock_minute_data(
|
||||
code: str,
|
||||
start_time: Optional[str] = None,
|
||||
end_time: Optional[str] = None,
|
||||
limit: int = 240
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取股票分钟频数据
|
||||
|
||||
Args:
|
||||
code: 股票代码(例如:'600519' 或 '600519.SH')
|
||||
start_time: 开始时间,格式:YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DD
|
||||
end_time: 结束时间,格式:YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DD
|
||||
limit: 返回条数,默认240(一个交易日的分钟数据)
|
||||
|
||||
Returns:
|
||||
分钟频数据列表
|
||||
"""
|
||||
try:
|
||||
client = get_clickhouse_client()
|
||||
|
||||
# 标准化股票代码(去除后缀)
|
||||
stock_code = code.split('.')[0] if '.' in code else code
|
||||
|
||||
# 构建查询
|
||||
query = """
|
||||
SELECT
|
||||
code,
|
||||
timestamp,
|
||||
open,
|
||||
high,
|
||||
low,
|
||||
close,
|
||||
volume,
|
||||
amt
|
||||
FROM stock_minute
|
||||
WHERE code = %(code)s
|
||||
"""
|
||||
|
||||
params = {'code': stock_code}
|
||||
|
||||
if start_time:
|
||||
query += " AND timestamp >= %(start_time)s"
|
||||
params['start_time'] = start_time
|
||||
|
||||
if end_time:
|
||||
query += " AND timestamp <= %(end_time)s"
|
||||
params['end_time'] = end_time
|
||||
|
||||
query += " ORDER BY timestamp DESC LIMIT %(limit)s"
|
||||
params['limit'] = limit
|
||||
|
||||
# 执行查询
|
||||
result = client.execute(query, params, with_column_types=True)
|
||||
|
||||
rows = result[0]
|
||||
columns = [col[0] for col in result[1]]
|
||||
|
||||
# 转换为字典列表
|
||||
data = []
|
||||
for row in rows:
|
||||
record = {}
|
||||
for i, col in enumerate(columns):
|
||||
value = row[i]
|
||||
# 处理 datetime 类型
|
||||
if hasattr(value, 'isoformat'):
|
||||
record[col] = value.isoformat()
|
||||
else:
|
||||
record[col] = value
|
||||
data.append(record)
|
||||
|
||||
logger.info(f"[ClickHouse] 查询分钟数据: code={stock_code}, 返回 {len(data)} 条记录")
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[ClickHouse] 查询分钟数据失败: {e}", exc_info=True)
|
||||
return []
|
||||
|
||||
|
||||
async def get_stock_minute_aggregation(
|
||||
code: str,
|
||||
date: str,
|
||||
interval: int = 5
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取股票分钟频数据的聚合(按指定分钟间隔)
|
||||
|
||||
Args:
|
||||
code: 股票代码
|
||||
date: 日期,格式:YYYY-MM-DD
|
||||
interval: 聚合间隔(分钟),默认5分钟
|
||||
|
||||
Returns:
|
||||
聚合后的K线数据
|
||||
"""
|
||||
try:
|
||||
client = get_clickhouse_client()
|
||||
|
||||
# 标准化股票代码
|
||||
stock_code = code.split('.')[0] if '.' in code else code
|
||||
|
||||
# 使用 ClickHouse 的时间函数进行聚合
|
||||
query = f"""
|
||||
SELECT
|
||||
code,
|
||||
toStartOfInterval(timestamp, INTERVAL {interval} MINUTE) as interval_start,
|
||||
argMin(open, timestamp) as open,
|
||||
max(high) as high,
|
||||
min(low) as low,
|
||||
argMax(close, timestamp) as close,
|
||||
sum(volume) as volume,
|
||||
sum(amt) as amt
|
||||
FROM stock_minute
|
||||
WHERE code = %(code)s
|
||||
AND toDate(timestamp) = %(date)s
|
||||
GROUP BY code, interval_start
|
||||
ORDER BY interval_start
|
||||
"""
|
||||
|
||||
params = {'code': stock_code, 'date': date}
|
||||
|
||||
result = client.execute(query, params, with_column_types=True)
|
||||
|
||||
rows = result[0]
|
||||
columns = [col[0] for col in result[1]]
|
||||
|
||||
data = []
|
||||
for row in rows:
|
||||
record = {}
|
||||
for i, col in enumerate(columns):
|
||||
value = row[i]
|
||||
if hasattr(value, 'isoformat'):
|
||||
record[col] = value.isoformat()
|
||||
else:
|
||||
record[col] = value
|
||||
data.append(record)
|
||||
|
||||
logger.info(f"[ClickHouse] 聚合分钟数据: code={stock_code}, date={date}, interval={interval}min, 返回 {len(data)} 条记录")
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[ClickHouse] 聚合分钟数据失败: {e}", exc_info=True)
|
||||
return []
|
||||
|
||||
|
||||
async def get_stock_intraday_statistics(
|
||||
code: str,
|
||||
date: str
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
获取股票日内统计数据
|
||||
|
||||
Args:
|
||||
code: 股票代码
|
||||
date: 日期,格式:YYYY-MM-DD
|
||||
|
||||
Returns:
|
||||
日内统计数据(开盘价、最高价、最低价、收盘价、成交量、成交额、波动率等)
|
||||
"""
|
||||
try:
|
||||
client = get_clickhouse_client()
|
||||
|
||||
stock_code = code.split('.')[0] if '.' in code else code
|
||||
|
||||
query = """
|
||||
SELECT
|
||||
code,
|
||||
toDate(timestamp) as trade_date,
|
||||
argMin(open, timestamp) as open,
|
||||
max(high) as high,
|
||||
min(low) as low,
|
||||
argMax(close, timestamp) as close,
|
||||
sum(volume) as total_volume,
|
||||
sum(amt) as total_amount,
|
||||
count(*) as data_points,
|
||||
min(timestamp) as first_time,
|
||||
max(timestamp) as last_time,
|
||||
(max(high) - min(low)) / min(low) * 100 as intraday_range_pct,
|
||||
stddevPop(close) as price_volatility
|
||||
FROM stock_minute
|
||||
WHERE code = %(code)s
|
||||
AND toDate(timestamp) = %(date)s
|
||||
GROUP BY code, trade_date
|
||||
"""
|
||||
|
||||
params = {'code': stock_code, 'date': date}
|
||||
|
||||
result = client.execute(query, params, with_column_types=True)
|
||||
|
||||
if not result[0]:
|
||||
return {"success": False, "error": f"未找到 {stock_code} 在 {date} 的分钟数据"}
|
||||
|
||||
row = result[0][0]
|
||||
columns = [col[0] for col in result[1]]
|
||||
|
||||
data = {}
|
||||
for i, col in enumerate(columns):
|
||||
value = row[i]
|
||||
if hasattr(value, 'isoformat'):
|
||||
data[col] = value.isoformat()
|
||||
else:
|
||||
data[col] = float(value) if isinstance(value, (int, float)) else value
|
||||
|
||||
logger.info(f"[ClickHouse] 日内统计: code={stock_code}, date={date}")
|
||||
return {"success": True, "data": data}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[ClickHouse] 日内统计失败: {e}", exc_info=True)
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
115
mcp_server.py
115
mcp_server.py
@@ -701,6 +701,75 @@ TOOLS: List[ToolDefinition] = [
|
||||
"required": []
|
||||
}
|
||||
),
|
||||
# ==================== 分钟频数据工具 ====================
|
||||
ToolDefinition(
|
||||
name="get_stock_minute_data",
|
||||
description="获取股票分钟频K线数据。适用于分析日内走势、寻找交易时机、技术分析等场景。",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": "股票代码,例如:'600519' 或 '600519.SH'"
|
||||
},
|
||||
"start_time": {
|
||||
"type": "string",
|
||||
"description": "开始时间,格式:YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DD"
|
||||
},
|
||||
"end_time": {
|
||||
"type": "string",
|
||||
"description": "结束时间,格式:YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DD"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "返回条数,默认240(约一个交易日)",
|
||||
"default": 240
|
||||
}
|
||||
},
|
||||
"required": ["code"]
|
||||
}
|
||||
),
|
||||
ToolDefinition(
|
||||
name="get_stock_minute_aggregation",
|
||||
description="获取股票分钟频数据的聚合K线(5分钟、15分钟、30分钟等周期)。适用于中短期技术分析。",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": "股票代码"
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"description": "交易日期,格式:YYYY-MM-DD"
|
||||
},
|
||||
"interval": {
|
||||
"type": "integer",
|
||||
"description": "聚合间隔(分钟),可选:5、15、30、60",
|
||||
"default": 5
|
||||
}
|
||||
},
|
||||
"required": ["code", "date"]
|
||||
}
|
||||
),
|
||||
ToolDefinition(
|
||||
name="get_stock_intraday_statistics",
|
||||
description="获取股票日内统计数据,包括开高低收、成交量、成交额、日内波动率等汇总指标。",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": "股票代码"
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"description": "交易日期,格式:YYYY-MM-DD"
|
||||
}
|
||||
},
|
||||
"required": ["code", "date"]
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
# ==================== MCP协议端点 ====================
|
||||
@@ -1114,6 +1183,48 @@ async def handle_get_user_following_events(args: Dict[str, Any]) -> Any:
|
||||
"data": []
|
||||
}
|
||||
|
||||
|
||||
# ==================== 分钟频数据处理函数 ====================
|
||||
|
||||
async def handle_get_stock_minute_data(args: Dict[str, Any]) -> Any:
|
||||
"""处理股票分钟频数据查询"""
|
||||
code = args["code"]
|
||||
start_time = args.get("start_time")
|
||||
end_time = args.get("end_time")
|
||||
limit = args.get("limit", 240)
|
||||
|
||||
result = await db.get_stock_minute_data(code, start_time, end_time, limit)
|
||||
return {
|
||||
"success": True,
|
||||
"data": result,
|
||||
"count": len(result)
|
||||
}
|
||||
|
||||
|
||||
async def handle_get_stock_minute_aggregation(args: Dict[str, Any]) -> Any:
|
||||
"""处理股票分钟频数据聚合查询"""
|
||||
code = args["code"]
|
||||
date = args["date"]
|
||||
interval = args.get("interval", 5)
|
||||
|
||||
result = await db.get_stock_minute_aggregation(code, date, interval)
|
||||
return {
|
||||
"success": True,
|
||||
"data": result,
|
||||
"count": len(result),
|
||||
"interval": f"{interval}分钟"
|
||||
}
|
||||
|
||||
|
||||
async def handle_get_stock_intraday_statistics(args: Dict[str, Any]) -> Any:
|
||||
"""处理股票日内统计数据查询"""
|
||||
code = args["code"]
|
||||
date = args["date"]
|
||||
|
||||
result = await db.get_stock_intraday_statistics(code, date)
|
||||
return result
|
||||
|
||||
|
||||
# 工具处理函数映射
|
||||
TOOL_HANDLERS = {
|
||||
"search_news": handle_search_news,
|
||||
@@ -1136,6 +1247,10 @@ TOOL_HANDLERS = {
|
||||
"get_stock_comparison": handle_get_stock_comparison,
|
||||
"get_user_watchlist": handle_get_user_watchlist,
|
||||
"get_user_following_events": handle_get_user_following_events,
|
||||
# 分钟频数据工具
|
||||
"get_stock_minute_data": handle_get_stock_minute_data,
|
||||
"get_stock_minute_aggregation": handle_get_stock_minute_aggregation,
|
||||
"get_stock_intraday_statistics": handle_get_stock_intraday_statistics,
|
||||
}
|
||||
|
||||
# ==================== Agent系统实现 ====================
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"react-to-print": "^3.0.3",
|
||||
"react-tsparticles": "^2.12.2",
|
||||
"recharts": "^3.1.2",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sass": "^1.49.9",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"styled-components": "^5.3.11",
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// 支持 ECharts 图表的 Markdown 渲染组件
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Alert, AlertIcon, Text, VStack, Code, useColorModeValue } from '@chakra-ui/react';
|
||||
import { Box, Alert, AlertIcon, Text, VStack, Code, useColorModeValue, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { EChartsRenderer } from './EChartsRenderer';
|
||||
import { logger } from '@utils/logger';
|
||||
|
||||
@@ -83,6 +84,7 @@ export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
|
||||
return (
|
||||
<Box key={index}>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
// 自定义渲染样式
|
||||
p: ({ children }) => (
|
||||
@@ -142,6 +144,55 @@ export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
|
||||
{children}
|
||||
</Box>
|
||||
),
|
||||
// 表格渲染
|
||||
table: ({ children }) => (
|
||||
<TableContainer
|
||||
mb={4}
|
||||
borderRadius="md"
|
||||
border="1px solid"
|
||||
borderColor={isDark ? 'rgba(255, 255, 255, 0.1)' : 'gray.200'}
|
||||
overflowX="auto"
|
||||
>
|
||||
<Table size="sm" variant="simple">
|
||||
{children}
|
||||
</Table>
|
||||
</TableContainer>
|
||||
),
|
||||
thead: ({ children }) => (
|
||||
<Thead bg={isDark ? 'rgba(255, 255, 255, 0.05)' : 'gray.50'}>
|
||||
{children}
|
||||
</Thead>
|
||||
),
|
||||
tbody: ({ children }) => <Tbody>{children}</Tbody>,
|
||||
tr: ({ children }) => (
|
||||
<Tr
|
||||
_hover={{
|
||||
bg: isDark ? 'rgba(255, 255, 255, 0.03)' : 'gray.50'
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Tr>
|
||||
),
|
||||
th: ({ children }) => (
|
||||
<Th
|
||||
fontSize="xs"
|
||||
color={headingColor}
|
||||
borderColor={isDark ? 'rgba(255, 255, 255, 0.1)' : 'gray.200'}
|
||||
py={2}
|
||||
>
|
||||
{children}
|
||||
</Th>
|
||||
),
|
||||
td: ({ children }) => (
|
||||
<Td
|
||||
fontSize="sm"
|
||||
color={textColor}
|
||||
borderColor={isDark ? 'rgba(255, 255, 255, 0.1)' : 'gray.200'}
|
||||
py={2}
|
||||
>
|
||||
{children}
|
||||
</Td>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{part.content}
|
||||
|
||||
@@ -190,6 +190,9 @@ export const useAgentChat = ({
|
||||
setCurrentSessionId(data.session_id);
|
||||
}
|
||||
|
||||
// 获取执行步骤(后端返回 step_results 字段)
|
||||
const stepResults = data.step_results || data.steps || [];
|
||||
|
||||
// 显示执行计划(如果有)
|
||||
if (data.plan) {
|
||||
addMessage({
|
||||
@@ -200,24 +203,24 @@ export const useAgentChat = ({
|
||||
}
|
||||
|
||||
// 显示执行步骤(如果有)
|
||||
if (data.steps && data.steps.length > 0) {
|
||||
if (stepResults.length > 0) {
|
||||
addMessage({
|
||||
type: MessageTypes.AGENT_EXECUTING,
|
||||
content: '正在执行步骤...',
|
||||
plan: data.plan,
|
||||
stepResults: data.steps,
|
||||
stepResults: stepResults,
|
||||
});
|
||||
}
|
||||
|
||||
// 移除 "执行中" 消息
|
||||
setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_EXECUTING));
|
||||
|
||||
// 显示最终回复
|
||||
// 显示最终回复(使用 final_summary 或 final_answer 或 message)
|
||||
addMessage({
|
||||
type: MessageTypes.AGENT_RESPONSE,
|
||||
content: data.final_answer || data.message || '处理完成',
|
||||
content: data.final_summary || data.final_answer || data.message || '处理完成',
|
||||
plan: data.plan,
|
||||
stepResults: data.steps,
|
||||
stepResults: stepResults,
|
||||
metadata: data.metadata,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user