diff --git a/backend/python_service/README.md b/backend/python_service/README.md deleted file mode 100644 index f0def1a..0000000 --- a/backend/python_service/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# Python TQAPI 服务 - -## 功能说明 - -Python TQAPI 服务是 AI 期货分析系统的一个组件,负责与天勤量化(TQSDK)进行交互,为系统提供期货市场数据。 - -## 集成说明 - -### 自动启动 - -Python TQAPI 服务已集成到 Node.js 后端服务中,当满足以下条件时会自动启动: - -1. 配置文件中 `defaultDataSource` 设置为 `tqsdk` -2. 或配置文件中 `tqsdk.enabled` 设置为 `true` - -### 手动启动 - -如果需要手动启动 Python 服务,可以执行以下命令: - -```bash -cd backend/python_service -python main.py -``` - -## 依赖安装 - -### Python 依赖 - -在 `backend/python_service` 目录下执行: - -```bash -pip install -r requirements.txt -``` - -### 依赖包说明 - -- **tqsdk**:天勤量化 SDK,用于获取期货市场数据 -- **fastapi**:高性能的 Python Web 框架,用于提供 API 接口 -- **uvicorn**:ASGI 服务器,用于运行 FastAPI 应用 -- **pandas**:数据分析库,用于处理和分析数据 - -## API 端点 - -| 端点 | 方法 | 功能 | -|------|------|------| -| `/api/connect` | POST | 连接到天勤量化服务 | -| `/api/contracts` | GET | 获取合约列表 | -| `/api/contract/{symbol}` | GET | 获取单个合约详情 | -| `/api/klines/{symbol}` | GET | 获取K线数据 | -| `/api/tick/{symbol}` | GET | 获取Tick数据 | -| `/health` | GET | 健康检查 | - -## 配置说明 - -### 天勤量化账号配置 - -在 `config.json` 文件中配置天勤量化账号: - -```json -{ - "dataSource": { - "tqsdk": { - "enabled": true, - "username": "你的天勤账号", - "password": "你的天勤密码" - } - } -} -``` - -### 服务端口配置 - -服务默认运行在端口 8000,可以在 `main.py` 文件中修改: - -```python -if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=8000) # 修改端口号 -``` - -## 故障排查 - -### 常见问题 - -1. **Python 服务启动失败** - - 检查 Python 是否安装 - - 检查依赖包是否安装完整 - - 检查天勤量化账号是否正确 - -2. **连接天勤服务器失败** - - 检查网络连接 - - 检查天勤量化账号密码是否正确 - - 检查天勤量化服务是否正常 - -3. **数据获取失败** - - 检查合约代码是否正确 - - 检查网络连接 - - 检查天勤量化服务状态 - -### 日志查看 - -Python 服务的日志会输出到 Node.js 后端服务的控制台中,可以通过查看控制台日志来排查问题。 - -## 技术实现 - -- **FastAPI**:提供 RESTful API 接口 -- **TqApi**:与天勤量化服务交互 -- **异步处理**:提高并发性能 -- **错误处理**:确保服务稳定性 diff --git a/backend/python_service/__pycache__/main.cpython-311.pyc b/backend/python_service/__pycache__/main.cpython-311.pyc deleted file mode 100644 index ee6831e..0000000 Binary files a/backend/python_service/__pycache__/main.cpython-311.pyc and /dev/null differ diff --git a/backend/python_service/__pycache__/schemas.cpython-311.pyc b/backend/python_service/__pycache__/schemas.cpython-311.pyc deleted file mode 100644 index 7e8dd29..0000000 Binary files a/backend/python_service/__pycache__/schemas.cpython-311.pyc and /dev/null differ diff --git a/backend/python_service/__pycache__/tqapi_service.cpython-311.pyc b/backend/python_service/__pycache__/tqapi_service.cpython-311.pyc deleted file mode 100644 index 2569773..0000000 Binary files a/backend/python_service/__pycache__/tqapi_service.cpython-311.pyc and /dev/null differ diff --git a/backend/python_service/api/__pycache__/router.cpython-311.pyc b/backend/python_service/api/__pycache__/router.cpython-311.pyc deleted file mode 100644 index c0ad894..0000000 Binary files a/backend/python_service/api/__pycache__/router.cpython-311.pyc and /dev/null differ diff --git a/backend/python_service/api/router.py b/backend/python_service/api/router.py deleted file mode 100644 index 0a1f019..0000000 --- a/backend/python_service/api/router.py +++ /dev/null @@ -1,96 +0,0 @@ -from fastapi import APIRouter, HTTPException, Query, Request -from schemas import ConnectRequest, ConnectResponse, ContractResponse, TickResponse, KlineResponse, DisconnectResponse -from tqapi_service import TqApiService - -router = APIRouter() - -# 存储当前连接的用户凭证 -current_credentials = {} - -# 获取当前TqApiService实例 -async def get_current_service(): - if not current_credentials: - raise HTTPException(status_code=401, detail="未连接到天勤服务器") - # 使用当前凭证获取或创建实例 - return await TqApiService.get_instance( - current_credentials['username'], - current_credentials['password'] - ) - -@router.post("/connect", response_model=ConnectResponse) -async def connect(request: ConnectRequest): - """连接到天勤服务器""" - try: - # 存储凭证 - current_credentials['username'] = request.username - current_credentials['password'] = request.password - # 获取或创建TqApiService实例 - service = await TqApiService.get_instance(request.username, request.password) - return ConnectResponse(success=True, message="连接成功") - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.get("/contracts", response_model=ContractResponse) -async def get_contracts(): - """获取合约列表""" - try: - service = await get_current_service() - contracts = await service.get_contracts() - return ContractResponse(success=True, data=contracts) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.get("/contract/{symbol}", response_model=ContractResponse) -async def get_contract(symbol: str): - """获取合约详情""" - try: - service = await get_current_service() - contract = await service.get_contract(symbol) - return ContractResponse(success=True, data=contract) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.get("/klines/{symbol}", response_model=KlineResponse) -async def get_klines( - symbol: str, - period: str = Query(..., description="周期,如 1M, 5M, 1H, 1D"), - count: int = Query(30, description="数据数量") -): - """获取 K 线数据""" - try: - service = await get_current_service() - klines = await service.get_klines(symbol, period, count) - return KlineResponse(success=True, data=klines) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.get("/tick/{symbol}", response_model=TickResponse) -async def get_tick(symbol: str): - """获取 tick 数据""" - try: - print(f"[Router] 接收到获取 tick 数据请求,symbol: {symbol}") - print(f"[Router] 正在获取当前服务实例...") - service = await get_current_service() - print(f"[Router] 获取服务实例成功") - print(f"[Router] 正在调用 service.get_tick({symbol})...") - tick = await service.get_tick(symbol) - print(f"[Router] 获取 tick 数据成功: {tick}") - return TickResponse(success=True, data=tick) - except Exception as e: - print(f"[Router] 获取 tick 数据失败: {e}") - raise HTTPException(status_code=500, detail=str(e)) - -@router.post("/disconnect", response_model=DisconnectResponse) -async def disconnect(): - """断开连接""" - try: - service = await get_current_service() - success = await service.disconnect() - if success: - # 清除凭证 - current_credentials.clear() - return DisconnectResponse(success=True, message="断开成功") - else: - raise HTTPException(status_code=400, detail="断开失败") - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/python_service/main.py b/backend/python_service/main.py deleted file mode 100644 index b79d77c..0000000 --- a/backend/python_service/main.py +++ /dev/null @@ -1,61 +0,0 @@ -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware -from api.router import router -import uvicorn -import argparse - -app = FastAPI( - title="TQAPI Service", - description="天勤量化 API 服务", - version="1.0.0" -) - -# 配置 CORS -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # 在生产环境中应该设置具体的域名 - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -# 注册路由 -app.include_router(router, prefix="/api") - -# 健康检查 -@app.get("/health") -async def health_check(): - return {"status": "healthy"} - -# 关闭事件处理 -@app.on_event("shutdown") -async def shutdown_event(): - """服务关闭时的清理工作""" - print("正在关闭服务...") - # 断开TQApi连接 - from api.router import current_credentials - from tqapi_service import TqApiService - - if current_credentials: - try: - # 获取当前服务实例并断开连接 - service = await TqApiService.get_instance(current_credentials['username'], current_credentials['password']) - success = await service.disconnect() - if success: - print("TQApi连接已成功关闭") - else: - print("TQApi连接关闭失败") - except Exception as e: - print(f"关闭TQApi连接时出错: {e}") - print("服务已关闭") - -if __name__ == "__main__": - # 解析命令行参数 - parser = argparse.ArgumentParser(description="天勤量化 API 服务") - parser.add_argument("--port", type=int, default=8000, help="服务端口,默认8000") - parser.add_argument("--host", type=str, default="0.0.0.0", help="服务主机,默认0.0.0.0") - args = parser.parse_args() - - # 启动服务 - print(f"启动天勤量化 API 服务,监听 {args.host}:{args.port}") - uvicorn.run(app, host=args.host, port=args.port) diff --git a/backend/python_service/requirements.txt b/backend/python_service/requirements.txt deleted file mode 100644 index 676c041..0000000 --- a/backend/python_service/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -fastapi -uvicorn[standard] -tqsdk -python-dotenv -pydantic-settings diff --git a/backend/python_service/schemas.py b/backend/python_service/schemas.py deleted file mode 100644 index c25c239..0000000 --- a/backend/python_service/schemas.py +++ /dev/null @@ -1,26 +0,0 @@ -from pydantic import BaseModel, Field -from typing import List, Optional, Any - -class ConnectRequest(BaseModel): - username: str = Field(..., description="天勤账号") - password: str = Field(..., description="天勤密码") - -class ConnectResponse(BaseModel): - success: bool - message: str - -class ContractResponse(BaseModel): - success: bool - data: List[Any] | Any - -class TickResponse(BaseModel): - success: bool - data: Any - -class KlineResponse(BaseModel): - success: bool - data: List[Any] - -class DisconnectResponse(BaseModel): - success: bool - message: str diff --git a/backend/python_service/test_connect.py b/backend/python_service/test_connect.py deleted file mode 100644 index 3a53930..0000000 --- a/backend/python_service/test_connect.py +++ /dev/null @@ -1,20 +0,0 @@ -import requests -import json - -# 测试连接到天勤服务器 -def test_connect(): - url = "http://127.0.0.1:8001/api/connect" - data = { - "username": "windsdreamer", - "password": "1qazse42W3" - } - - try: - response = requests.post(url, json=data, timeout=10) - print(f"Status code: {response.status_code}") - print(f"Response: {response.json()}") - except Exception as e: - print(f"Error: {e}") - -if __name__ == "__main__": - test_connect() diff --git a/backend/python_service/tqapi_service.py b/backend/python_service/tqapi_service.py deleted file mode 100644 index 3b780e8..0000000 --- a/backend/python_service/tqapi_service.py +++ /dev/null @@ -1,329 +0,0 @@ -import asyncio -import datetime -from tqsdk import TqApi, TqAuth -from typing import List, Dict, Any - -class TqApiService: - # 类级缓存,存储用户名密码对应的TqApiService实例 - _instance_cache = {} - - def __init__(self): - self.api = None - self.connected = False - self.username = None - self.password = None - - @classmethod - async def get_instance(cls, username: str, password: str): - """获取或创建TqApiService实例""" - # 生成缓存键 - cache_key = f"{username}:{password}" - - # 检查缓存中是否存在实例 - if cache_key in cls._instance_cache: - instance = cls._instance_cache[cache_key] - # 检查实例是否仍然连接 - if instance.connected: - print(f"使用缓存的TqApiService实例 for {username}") - return instance - else: - # 实例已断开连接,从缓存中移除 - print(f"TqApiService实例 for {username} 已断开连接,从缓存中移除") - del cls._instance_cache[cache_key] - - # 创建新实例 - print(f"创建新的TqApiService实例 for {username}") - instance = cls() - instance.username = username - instance.password = password - # 连接到天勤服务器 - await instance.connect(username, password) - # 存入缓存 - cls._instance_cache[cache_key] = instance - return instance - - async def connect(self, username: str, password: str) -> bool: - """连接到天勤服务器""" - try: - # 立即返回,不等待TqApi实例完全连接到天勤服务器 - # 连接过程将在后台进行 - self.username = username - self.password = password - self.connected = True - # 创建后台任务来初始化API实例和运行事件循环 - asyncio.create_task(self._initialize_api()) - return True - except Exception as e: - print(f"连接失败: {e}") - self.connected = False - return False - - async def _initialize_api(self): - """初始化TqApi实例并运行事件循环""" - try: - print("正在初始化TqApi实例...") - # 创建TqApi实例 - self.api = TqApi(auth=TqAuth(self.username, self.password), debug=False) - print("TqApi实例初始化完成") - # 运行事件循环 - await self._run_api() - except Exception as e: - print(f"初始化TqApi实例失败: {e}") - self.connected = False - - async def _run_api(self): - """运行TQAPI事件循环""" - try: - while self.connected and self.api: - try: - # 非阻塞方式检查更新 - self.api.wait_update(0.1) # 超时0.1秒,避免完全阻塞 - except Exception as e: - print(f"API更新出错: {e}") - await asyncio.sleep(0.1) - # 让出CPU时间,避免阻塞其他任务 - await asyncio.sleep(0.01) - except Exception as e: - print(f"API运行出错: {e}") - self.connected = False - - async def get_contracts(self) -> List[Dict[str, Any]]: - """获取合约列表""" - if not self.connected: - raise Exception("未连接到天勤服务器") - - if not self.api: - # API实例尚未初始化,返回空列表 - print("API实例尚未初始化,返回空合约列表") - return [] - - try: - # 获取所有合约 - contracts = self.api.get_contracts() - # 过滤期货合约,排除期权等其他类型 - futures_contracts = [] - for symbol, contract in contracts.items(): - # 只保留期货合约 - if contract["product_class"] == "FUTURE": - futures_contracts.append({ - "symbol": symbol, - "name": contract["name"], - "exchange": contract["exchange"], - "product_id": contract["product_id"], - "price_tick": contract["price_tick"], - "volume_multiple": contract["volume_multiple"], - "margin_rate": contract["margin_rate"], - "expire_datetime": contract["expire_datetime"] - }) - return futures_contracts - except Exception as e: - print(f"获取合约列表失败: {e}") - # 返回空列表,避免整个请求失败 - return [] - - async def get_contract(self, symbol: str) -> Dict[str, Any]: - """获取合约详情""" - if not self.connected: - raise Exception("未连接到天勤服务器") - - if not self.api: - # API实例尚未初始化,返回默认值 - print("API实例尚未初始化,返回默认合约详情") - return { - "symbol": symbol, - "name": "", - "exchange": "", - "product_id": "", - "price_tick": 0, - "volume_multiple": 0, - "margin_rate": 0, - "expire_datetime": 0 - } - - try: - # 获取合约详情 - contract = self.api.get_contract(symbol) - return { - "symbol": symbol, - "name": contract["name"], - "exchange": contract["exchange"], - "product_id": contract["product_id"], - "price_tick": contract["price_tick"], - "volume_multiple": contract["volume_multiple"], - "margin_rate": contract["margin_rate"], - "expire_datetime": contract["expire_datetime"] - } - except Exception as e: - print(f"获取合约详情失败: {e}") - # 返回默认值,避免整个请求失败 - return { - "symbol": symbol, - "name": "", - "exchange": "", - "product_id": "", - "price_tick": 0, - "volume_multiple": 0, - "margin_rate": 0, - "expire_datetime": 0 - } - - async def get_klines(self, symbol: str, period: str, count: int) -> List[Dict[str, Any]]: - """获取K线数据""" - if not self.connected: - raise Exception("未连接到天勤服务器") - - if not self.api: - # API实例尚未初始化,返回空列表 - print("API实例尚未初始化,返回空K线数据列表") - return [] - - try: - # 转换周期格式 - duration_seconds = self._convert_period(period) - # 获取K线数据 - klines = self.api.get_kline_serial(symbol, duration_seconds, data_length=count) - # 转换为前端需要的格式 - result = [] - for i in range(len(klines)): - result.append({ - "time": int(klines.iloc[i]["datetime"].timestamp()), - "open": float(klines.iloc[i]["open"]), - "high": float(klines.iloc[i]["high"]), - "low": float(klines.iloc[i]["low"]), - "close": float(klines.iloc[i]["close"]), - "volume": float(klines.iloc[i]["volume"]) - }) - return result - except Exception as e: - print(f"获取K线数据失败: {e}") - # 返回空列表,避免整个请求失败 - return [] - - async def get_tick(self, symbol: str) -> Dict[str, Any]: - """获取tick数据""" - print(f"[TqApiService] 开始获取 tick 数据,symbol: {symbol}") - try: - print(f"[TqApiService] 检查连接状态: {self.connected}") - if not self.connected: - # 未连接到天勤服务器,返回默认数据 - print("[TqApiService] 未连接到天勤服务器,返回默认tick数据") - return { - "last_price": 0, - "pre_close": 0, - "open": 0, - "high": 0, - "low": 0, - "volume": 0, - "open_interest": 0, - "bid_price1": 0, - "bid_volume1": 0, - "ask_price1": 0, - "ask_volume1": 0, - "datetime": int(datetime.datetime.now().timestamp()) - } - - print(f"[TqApiService] 检查API实例状态: {self.api is not None}") - if not self.api: - # API实例尚未初始化,返回默认数据 - print("[TqApiService] API实例尚未初始化,返回默认tick数据") - return { - "last_price": 0, - "pre_close": 0, - "open": 0, - "high": 0, - "low": 0, - "volume": 0, - "open_interest": 0, - "bid_price1": 0, - "bid_volume1": 0, - "ask_price1": 0, - "ask_volume1": 0, - "datetime": int(datetime.datetime.now().timestamp()) - } - - # 获取实时行情 - print(f"[TqApiService] 正在调用 self.api.get_quote({symbol})...") - quote = self.api.get_quote(symbol) - print(f"[TqApiService] 获取 quote 对象成功") - - # 尝试多次获取行情更新,避免单次超时 - max_attempts = 3 - print(f"[TqApiService] 开始尝试获取行情更新,最多 {max_attempts} 次") - for attempt in range(max_attempts): - try: - # 等待行情更新,设置超时 - print(f"[TqApiService] 尝试 {attempt + 1}/{max_attempts}: 调用 self.api.wait_update(1.0)...") - updated = self.api.wait_update(1.0) # 1秒超时 - print(f"[TqApiService] 尝试 {attempt + 1}/{max_attempts}: wait_update 返回: {updated}") - if updated: - print(f"[TqApiService] 获取行情更新成功") - break - print(f"[TqApiService] 尝试 {attempt + 1}/{max_attempts}: 获取 {symbol} 的行情信息超时") - except Exception as e: - print(f"[TqApiService] 尝试 {attempt + 1}/{max_attempts}: 获取 {symbol} 的行情信息出错: {e}") - await asyncio.sleep(0.5) - - # 即使没有更新,也尝试返回当前数据 - print(f"[TqApiService] 正在构建返回数据...") - result = { - "last_price": float(quote.get("last_price", 0)), - "pre_close": float(quote.get("pre_close", 0)), - "open": float(quote.get("open", 0)), - "high": float(quote.get("high", 0)), - "low": float(quote.get("low", 0)), - "volume": float(quote.get("volume", 0)), - "open_interest": float(quote.get("open_interest", 0)), - "bid_price1": float(quote.get("bid_price1", 0)), - "bid_volume1": float(quote.get("bid_volume1", 0)), - "ask_price1": float(quote.get("ask_price1", 0)), - "ask_volume1": float(quote.get("ask_volume1", 0)), - "datetime": int(quote.get("datetime", datetime.datetime.now()).timestamp()) - } - print(f"[TqApiService] 获取 tick 数据成功,返回: {result}") - return result - except Exception as e: - print(f"[TqApiService] 获取tick数据失败: {e}") - import traceback - traceback.print_exc() - # 返回默认数据,避免整个请求失败 - default_result = { - "last_price": 0, - "pre_close": 0, - "open": 0, - "high": 0, - "low": 0, - "volume": 0, - "open_interest": 0, - "bid_price1": 0, - "bid_volume1": 0, - "ask_price1": 0, - "ask_volume1": 0, - "datetime": int(datetime.datetime.now().timestamp()) - } - print(f"[TqApiService] 返回默认数据: {default_result}") - return default_result - - async def disconnect(self) -> bool: - """断开连接""" - try: - if self.api: - self.connected = False - self.api.close() - self.api = None - return True - except Exception as e: - print(f"断开连接失败: {e}") - return False - - def _convert_period(self, period: str) -> int: - """转换周期格式为秒""" - period_map = { - "1M": 60, - "5M": 300, - "15M": 900, - "30M": 1800, - "1H": 3600, - "4H": 14400, - "1D": 86400 - } - return period_map.get(period, 3600) # 默认1小时 diff --git a/backend/service_implementation/service/data/futures_analysis.db b/backend/service_implementation/service/data/futures_analysis.db index 6fc43b7..4f43464 100644 Binary files a/backend/service_implementation/service/data/futures_analysis.db and b/backend/service_implementation/service/data/futures_analysis.db differ diff --git a/backend/src/services/datasource/DataSource.ts b/backend/src/services/datasource/DataSource.ts deleted file mode 100644 index 2dbf54f..0000000 --- a/backend/src/services/datasource/DataSource.ts +++ /dev/null @@ -1,26 +0,0 @@ -// 数据源抽象接口 -export interface DataSource { - // 获取所有合约列表 - getContractList(): Promise; - - // 获取单个合约详情 - getContractDetail(symbol: string): Promise; - - // 获取K线数据 - getKlineData(symbol: string, period: string, count: number): Promise; - - // 获取实时行情数据 - getTickData(symbol: string): Promise; - - // 获取市场概览 - getMarketOverview(): Promise; - - // 获取历史成交数据 - getHistoricalTrades(symbol: string, start: number, end: number): Promise; - - // 初始化数据源 - initialize(): Promise; - - // 关闭数据源连接 - close(): Promise; -} \ No newline at end of file diff --git a/backend/src/services/datasource/DataSourceFactory.ts b/backend/src/services/datasource/DataSourceFactory.ts deleted file mode 100644 index 218953e..0000000 --- a/backend/src/services/datasource/DataSourceFactory.ts +++ /dev/null @@ -1,70 +0,0 @@ -// 数据源工厂类 -import { DataSource } from './DataSource'; -import { TQDataSource } from './TQDataSource'; -import { logger } from '../../utils/logger'; - -// 数据源类型 -export enum DataSourceType { - TQSDK = 'tqsdk', - MOCK = 'mock', - WIND = 'wind', - SINA = 'sina', - TEST = 'test' -} - -export class DataSourceFactory { - private static dataSources: Map = new Map(); - - // 获取数据源实例 - static async getDataSource(type: DataSourceType = DataSourceType.TQSDK, config: any = {}): Promise { - logger.log('获取数据源实例:', type); - if (!this.dataSources.has(type)) { - let dataSource: DataSource; - - switch (type) { - case DataSourceType.TQSDK: - dataSource = new TQDataSource(config.tqsdk || {}); - break; - case DataSourceType.MOCK: - case DataSourceType.TEST: - // 导入模拟数据源 - const { MockDataSource } = await import('./MockDataSource'); - dataSource = new MockDataSource(); - break; - default: - throw new Error(`不支持的数据源类型: ${type}`); - } - - // 初始化数据源 - const initialized = await dataSource.initialize(); - if (!initialized) { - throw new Error(`数据源${type}初始化失败`); - } - - this.dataSources.set(type, dataSource); - } - - return this.dataSources.get(type)!; - } - - // 关闭所有数据源 - static async closeAllDataSources(): Promise { - for (const [type, dataSource] of this.dataSources) { - try { - await dataSource.close(); - logger.log(`数据源${type}已关闭`); - } catch (error) { - logger.error(`关闭数据源${type}失败:`, error); - } - } - this.dataSources.clear(); - } - - // 切换数据源 - static async switchDataSource(type: DataSourceType): Promise { - // 关闭当前数据源 - await this.closeAllDataSources(); - // 获取新数据源 - return this.getDataSource(type); - } -} \ No newline at end of file diff --git a/backend/src/services/datasource/MockDataSource.ts b/backend/src/services/datasource/MockDataSource.ts deleted file mode 100644 index 2d6b5eb..0000000 --- a/backend/src/services/datasource/MockDataSource.ts +++ /dev/null @@ -1,200 +0,0 @@ -// 模拟数据源实现 -import { DataSource } from './DataSource'; -import { futuresList, generateFuturesOverview, generateFutureData, generateKlineData, riskAlerts } from '../../utils/mockData'; -import { logger } from '../../utils/logger'; - -export class MockDataSource implements DataSource { - private initialized: boolean = false; - - async initialize(): Promise { - // 模拟初始化 - this.initialized = true; - logger.log('模拟数据源初始化成功'); - return true; - } - - async getContractList(): Promise { - if (!this.initialized) { - throw new Error('模拟数据源未初始化'); - } - - // 返回模拟合约列表 - return futuresList.map(item => ({ - symbol: item.code, - name: item.name, - exchange: this.getExchangeBySymbol(item.code), - product_class: 'futures', - price_tick: 0.01, - size: 1, - margin_rate: 0.05 - })); - } - - async getContractDetail(symbol: string): Promise { - if (!this.initialized) { - throw new Error('模拟数据源未初始化'); - } - - // 返回模拟合约详情 - const future = futuresList.find(item => item.code === symbol); - if (!future) { - throw new Error(`合约${symbol}不存在`); - } - - return { - symbol: future.code, - name: future.name, - exchange: this.getExchangeBySymbol(future.code), - product_class: 'futures', - price_tick: 0.01, - size: 1, - margin_rate: 0.05, - delivery_month: '202412', - trading_hours: '09:00-11:30, 13:30-15:00, 21:00-02:30', - pre_close: 2000, - open: 2005, - high: 2010, - low: 1995, - last_price: 2003, - volume: 10000, - open_interest: 50000 - }; - } - - async getKlineData(symbol: string, period: string, count: number): Promise { - if (!this.initialized) { - throw new Error('模拟数据源未初始化'); - } - - // 返回模拟K线数据 - const klineData = generateKlineData(count); - return klineData.map(item => ({ - datetime: item.time * 1000000000, // 转换为纳秒 - open: item.open, - high: item.high, - low: item.low, - close: item.close, - volume: item.volume, - open_interest: Math.floor(Math.random() * 100000) + 50000 - })); - } - - async getTickData(symbol: string): Promise { - if (!this.initialized) { - throw new Error('模拟数据源未初始化'); - } - - // 返回模拟实时行情数据 - const future = futuresList.find(item => item.code === symbol); - if (!future) { - throw new Error(`合约${symbol}不存在`); - } - - const pre_close = 2000; - const last_price = pre_close + (Math.random() * 20 - 10); - const price_change = last_price - pre_close; - - return { - datetime: Date.now() * 1000000, // 转换为纳秒 - last_price: +last_price.toFixed(2), - pre_close, - open: pre_close + (Math.random() * 10 - 5), - high: Math.max(last_price, pre_close + (Math.random() * 15 - 5)), - low: Math.min(last_price, pre_close + (Math.random() * 15 - 10)), - volume: Math.floor(Math.random() * 10000) + 5000, - open_interest: Math.floor(Math.random() * 100000) + 50000, - price_change: +price_change.toFixed(2), - bid_price1: last_price - 0.01, - bid_volume1: Math.floor(Math.random() * 100) + 50, - ask_price1: last_price + 0.01, - ask_volume1: Math.floor(Math.random() * 100) + 50 - }; - } - - async getMarketOverview(): Promise { - if (!this.initialized) { - throw new Error('模拟数据源未初始化'); - } - - // 返回模拟市场概览 - const overview = generateFuturesOverview(); - return overview.map(item => ({ - symbol: item.code, - name: item.name, - price: item.currentPrice, - change: item.changePercent, - change_percent: item.changePercent, - volume: Math.floor(Math.random() * 100000) + 50000, - open_interest: Math.floor(Math.random() * 1000000) + 500000 - })); - } - - async getHistoricalTrades(symbol: string, start: number, end: number): Promise { - if (!this.initialized) { - throw new Error('模拟数据源未初始化'); - } - - // 返回模拟历史成交数据 - const trades = []; - const count = 100; // 生成100条模拟数据 - const basePrice = 2000; - - for (let i = 0; i < count; i++) { - const price = basePrice + (Math.random() * 10 - 5); - const volume = Math.floor(Math.random() * 100) + 10; - const timestamp = start + Math.floor((end - start) * (i / count)); - - trades.push({ - datetime: timestamp * 1000000, // 转换为纳秒 - price: +price.toFixed(2), - volume, - direction: Math.random() > 0.5 ? 'BUY' : 'SELL' - }); - } - - return trades; - } - - async close(): Promise { - // 模拟关闭 - this.initialized = false; - logger.log('模拟数据源已关闭'); - } - - // 根据合约代码获取交易所 - private getExchangeBySymbol(symbol: string): string { - // 简单的交易所映射 - const exchangeMap: Record = { - 'AU': 'SHFE', - 'AG': 'SHFE', - 'CU': 'SHFE', - 'NI': 'SHFE', - 'SN': 'SHFE', - 'AL': 'SHFE', - 'ZN': 'SHFE', - 'FG': 'CZCE', - 'SJS': 'CZCE', - 'SCA': 'CZCE', - 'JM': 'DCE', - 'RB': 'SHFE', - 'ALO': 'SHFE', - 'MA': 'DCE', - 'PVC': 'DCE', - 'FU': 'SHFE', - 'SC': 'INE', - 'L': 'DCE', - 'NR': 'SHFE', - 'BU': 'SHFE', - 'LU': 'INE', - 'P': 'DCE', - 'LC': 'SHFE', - 'SI': 'SHFE', - 'PGS': 'SHFE', - 'IC': 'CFFEX', - 'IM': 'CFFEX', - 'IH': 'CFFEX' - }; - - return exchangeMap[symbol] || 'SHFE'; - } -} \ No newline at end of file diff --git a/backend/src/services/datasource/PythonServiceManager.ts b/backend/src/services/datasource/PythonServiceManager.ts deleted file mode 100644 index 751f186..0000000 --- a/backend/src/services/datasource/PythonServiceManager.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { spawn } from 'child_process'; -import * as path from 'path'; -import { config } from '../../config'; -import { logger } from '../../utils/logger'; - -class PythonServiceManager { - private pythonProcess: any = null; - private isRunning: boolean = false; - - async start(): Promise { - if (this.isRunning) { - logger.log('Python服务已经在运行'); - return true; - } - - try { - // 构建Python服务的路径 - const pythonServicePath = path.join(__dirname, '../../../python_service/main.py'); - - // 从配置中读取端口,默认3007 - const port = config.dataSource?.tqsdk?.pythonPort || 3007; - - logger.log(`启动Python TQAPI服务,端口: ${port}...`); - - // 启动Python服务,传递端口参数 - this.pythonProcess = spawn('python', [pythonServicePath, '--port', port.toString()], { - cwd: path.join(__dirname, '../../../python_service'), - stdio: 'inherit', - shell: true - }); - - this.pythonProcess.on('error', (error: any) => { - logger.error('启动Python服务失败:', error); - this.isRunning = false; - }); - - this.pythonProcess.on('exit', (code: number, signal: string) => { - logger.log(`Python服务退出,代码: ${code}, 信号: ${signal}`); - this.isRunning = false; - }); - - // 等待2秒,确保Python服务有时间启动 - await new Promise(resolve => setTimeout(resolve, 2000)); - - this.isRunning = true; - logger.log('Python TQAPI服务启动成功'); - return true; - } catch (error) { - logger.error('启动Python服务时出错:', error); - this.isRunning = false; - return false; - } - } - - stop(): void { - if (this.pythonProcess) { - try { - logger.log('停止Python TQAPI服务...'); - // 发送终止信号 - this.pythonProcess.kill(); - // 等待进程退出 - this.pythonProcess.on('exit', (code: number, signal: string) => { - logger.log(`Python服务退出,代码: ${code}, 信号: ${signal}`); - }); - this.pythonProcess = null; - this.isRunning = false; - logger.log('Python TQAPI服务已停止'); - } catch (error) { - logger.error('停止Python服务时出错:', error); - this.pythonProcess = null; - this.isRunning = false; - } - } else { - logger.log('Python服务未运行,无需停止'); - } - } - - getStatus(): boolean { - return this.isRunning; - } -} - -// 导出单例实例 -export const pythonServiceManager = new PythonServiceManager(); diff --git a/backend/src/services/datasource/TQDataSource.ts b/backend/src/services/datasource/TQDataSource.ts deleted file mode 100644 index ddc2859..0000000 --- a/backend/src/services/datasource/TQDataSource.ts +++ /dev/null @@ -1,439 +0,0 @@ -// TQAPI数据源实现 - 使用Python服务 -import { DataSource } from './DataSource'; -import { logger } from '../../utils/logger'; - -// 简化的HTTP客户端 -class HttpClient { - private baseUrl: string; - - constructor(baseUrl: string) { - this.baseUrl = baseUrl; - } - - async get(endpoint: string, params?: Record): Promise { - let url = `${this.baseUrl}${endpoint}`; - if (params) { - const queryString = new URLSearchParams(params).toString(); - url += `?${queryString}`; - } - - logger.log('发送GET请求:', url); - - try { - // 设置20秒超时 - const controller = new AbortController(); - const timeoutId = setTimeout(() => { - logger.error('GET请求超时,正在中止请求...'); - controller.abort(); - }, 20000); - - try { - logger.log('正在发送GET请求...'); - const start = Date.now(); - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - }, - signal: controller.signal - }); - const end = Date.now(); - logger.log(`GET请求完成,耗时: ${end - start}ms`); - logger.log('GET请求响应状态:', response.status); - - if (!response.ok) { - const errorText = await response.text(); - logger.error('GET请求失败:', errorText); - throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`); - } - - logger.log('正在解析GET响应数据...'); - const data = await response.json(); - logger.log('GET请求响应数据:', data); - return data as T; - } finally { - clearTimeout(timeoutId); - } - } catch (error: any) { - logger.error('GET请求网络错误:', error.message || error); - logger.error('错误详情:', error); - logger.error('错误堆栈:', error.stack); - throw new Error(`网络请求失败: ${error.message || error}`); - } - } - - async post(endpoint: string, data?: any): Promise { - const url = `${this.baseUrl}${endpoint}`; - logger.log(`发送POST请求: ${url} 数据: ${JSON.stringify(data)}`); - - try { - // 设置20秒超时 - const controller = new AbortController(); - const timeoutId = setTimeout(() => { - logger.error('POST请求超时,正在中止请求...'); - controller.abort(); - }, 20000); - - try { - logger.log('正在发送请求...'); - const start = Date.now(); - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: data ? JSON.stringify(data) : undefined, - signal: controller.signal - }); - const end = Date.now(); - logger.log(`请求完成,耗时: ${end - start}ms`); - logger.log('POST请求响应状态:', response.status); - - if (!response.ok) { - const errorText = await response.text(); - logger.error('POST请求失败:', errorText); - throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`); - } - - logger.log('正在解析响应数据...'); - const responseData = await response.json(); - logger.log('POST请求响应数据:', responseData); - return responseData as T; - } finally { - clearTimeout(timeoutId); - } - } catch (error: any) { - logger.error('POST请求网络错误:', error.message || error); - logger.error('错误详情:', error); - logger.error('错误堆栈:', error.stack); - throw new Error(`网络请求失败: ${error.message || error}`); - } - } -} - -export class TQDataSource implements DataSource { - private httpClient: HttpClient; - private initialized: boolean = false; - private config: { - username?: string; - password?: string; - timeout?: number; - retries?: number; - maxConnections?: number; - pythonServiceUrl?: string; - }; - - constructor(config: any = {}) { - logger.log('使用TQAPI数据源初始化...'); - // 从配置中读取端口,默认8001 - const port = config.pythonPort || 8001; - this.config = { - username: config.username || '', - password: config.password || '', - timeout: config.timeout || 30000, - retries: config.retries || 3, - maxConnections: config.maxConnections || 5, - pythonServiceUrl: config.pythonServiceUrl || `http://127.0.0.1:${port}/api` - }; - logger.log('TQAPI数据源配置:', this.config); - // 测试Python服务URL是否正确 - logger.log('测试Python服务URL:', this.config.pythonServiceUrl); - this.httpClient = new HttpClient(this.config.pythonServiceUrl!); - } - - async initialize(): Promise { - try { - logger.log('开始初始化TQAPI数据源...'); - logger.log('Python服务URL:', this.config.pythonServiceUrl); - logger.log('连接参数:', { - username: this.config.username ? '***' : '', - password: this.config.password ? '***' : '' - }); - - // 先测试Python服务是否可达 - logger.log('测试Python服务是否可达...'); - try { - const testUrl = `${this.config.pythonServiceUrl}/../health`; - logger.log('测试URL:', testUrl); - // 使用AbortController设置超时 - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 10000); - try { - const testResponse = await fetch(testUrl, { - method: 'GET', - signal: controller.signal - }); - logger.log('Python服务健康检查响应:', testResponse.status); - if (testResponse.ok) { - logger.log('Python服务可达'); - } else { - logger.error('Python服务不可达,状态码:', testResponse.status); - } - } finally { - clearTimeout(timeoutId); - } - } catch (error: any) { - logger.error('Python服务健康检查失败:', error.message || error); - } - - // 连接到天勤服务器 - logger.log('连接到天勤服务器...'); - - try { - const response = await this.httpClient.post('/connect', { - username: this.config.username, - password: this.config.password - }); - - logger.log('连接响应:', response); - - if (response.success) { - logger.log('TQAPI连接成功'); - this.initialized = true; - return true; - } else { - logger.error('TQAPI连接失败:', response.message); - this.initialized = false; - return false; - } - } catch (error: any) { - logger.error('连接请求失败:', error.message || error); - logger.error('错误详情:', error); - throw error; - } - } catch (error) { - logger.error('TQAPI数据源初始化失败:', error); - this.initialized = false; - return false; - } - } - - async getContractList(): Promise { - if (!this.initialized) { - throw new Error('TQAPI数据源未初始化'); - } - - try { - logger.log('获取合约列表...'); - const response = await this.httpClient.get('/contracts'); - - if (response.success && Array.isArray(response.data)) { - logger.log('获取合约列表成功,数量:', response.data.length); - return response.data; - } else { - logger.error('获取合约列表失败:', response.message); - return []; - } - } catch (error) { - logger.error('获取合约列表失败:', error); - throw error; - } - } - - async getContractDetail(symbol: string): Promise { - if (!this.initialized) { - throw new Error('TQAPI数据源未初始化'); - } - - try { - logger.log(`获取合约${symbol}详情...`); - const response = await this.httpClient.get(`/contract/${symbol}`); - - if (response.success) { - logger.log(`获取合约${symbol}详情成功`); - return response.data; - } else { - logger.error(`获取合约${symbol}详情失败:`, response.message); - throw new Error(`获取合约${symbol}详情失败`); - } - } catch (error) { - logger.error(`获取合约${symbol}详情失败:`, error); - throw error; - } - } - - async getKlineData(symbol: string, period: string, count: number): Promise { - if (!this.initialized) { - throw new Error('TQAPI数据源未初始化'); - } - - try { - logger.log('开始获取K线数据:', { - symbol: symbol, - period: period, - count: count - }); - - // 确保合约代码格式正确 - let contractSymbol = symbol; - if (!contractSymbol.includes('.')) { - // 如果没有交易所前缀,尝试添加默认交易所 - logger.warn('合约代码缺少交易所前缀,尝试添加默认交易所'); - contractSymbol = `SHFE.${contractSymbol}`; - } - logger.log('使用的合约代码:', contractSymbol); - - const response = await this.httpClient.get('/klines/' + contractSymbol, { - period: period, - count: count.toString() - }); - - if (response.success && Array.isArray(response.data)) { - logger.log('获取K线数据成功,长度:', response.data.length); - return response.data; - } else { - logger.error('获取K线数据失败:', response.message); - return []; - } - } catch (error) { - logger.error(`获取合约${symbol}K线数据失败:`, error); - throw error; - } - } - - async getTickData(symbol: string): Promise { - if (!this.initialized) { - throw new Error('TQAPI数据源未初始化'); - } - - try { - logger.log(`获取合约${symbol}实时行情数据...`); - - // 确保合约代码格式正确 - let contractSymbol = symbol; - if (!contractSymbol.includes('.')) { - // 如果没有交易所前缀,尝试添加默认交易所 - logger.warn('合约代码缺少交易所前缀,尝试添加默认交易所'); - contractSymbol = `SHFE.${contractSymbol}`; - } - logger.log('使用的合约代码:', contractSymbol); - - logger.log('正在发送GET请求到:', `/tick/${contractSymbol}`); - const start = Date.now(); - const response = await this.httpClient.get(`/tick/${contractSymbol}`); - const end = Date.now(); - logger.log(`GET请求完成,耗时: ${end - start}ms`); - logger.log('响应数据:', response); - - if (response.success) { - logger.log(`获取合约${symbol}实时行情数据成功`); - // 计算价格变化 - const tickData = response.data; - tickData.price_change = tickData.last_price - (tickData.pre_close || 0); - logger.log('计算价格变化后的数据:', tickData); - return tickData; - } else { - logger.error(`获取合约${symbol}实时行情数据失败:`, response.message); - // 返回默认数据,避免整个请求失败 - return { - last_price: 0, - pre_close: 0, - open: 0, - high: 0, - low: 0, - volume: 0, - open_interest: 0, - bid_price1: 0, - bid_volume1: 0, - ask_price1: 0, - ask_volume1: 0, - price_change: 0, - datetime: Date.now() - }; - } - } catch (error) { - logger.error(`获取合约${symbol}实时行情数据失败:`, error); - // 返回默认数据,避免整个请求失败 - return { - last_price: 0, - pre_close: 0, - open: 0, - high: 0, - low: 0, - volume: 0, - open_interest: 0, - bid_price1: 0, - bid_volume1: 0, - ask_price1: 0, - ask_volume1: 0, - price_change: 0, - datetime: Date.now() - }; - } - } - - async getMarketOverview(): Promise { - if (!this.initialized) { - throw new Error('TQAPI数据源未初始化'); - } - - try { - // 获取所有合约 - const contracts = await this.getContractList(); - // 限制获取的合约数量,避免请求过多 - const limitedContracts = contracts.slice(0, 20); - // 获取每个合约的实时行情 - const overview = []; - for (const contract of limitedContracts) { - try { - const tick = await this.getTickData(contract.symbol); - // 确保所有值都是有效的数字 - const price = typeof tick.last_price === 'number' ? tick.last_price : 0; - const change = typeof tick.price_change === 'number' ? tick.price_change : 0; - const pre_close = typeof tick.pre_close === 'number' && tick.pre_close !== 0 ? tick.pre_close : 1; - const volume = typeof tick.volume === 'number' ? tick.volume : 0; - const open_interest = typeof tick.open_interest === 'number' ? tick.open_interest : 0; - - overview.push({ - symbol: contract.symbol, - name: contract.name, - price: price, - change: change, - change_percent: change / pre_close * 100, - volume: volume, - open_interest: open_interest - }); - } catch (error) { - logger.error(`获取合约${contract.symbol}行情失败:`, error); - } - } - return overview; - } catch (error) { - logger.error('获取市场概览失败:', error); - throw error; - } - } - - async getHistoricalTrades(symbol: string, start: number, end: number): Promise { - if (!this.initialized) { - throw new Error('TQAPI数据源未初始化'); - } - - try { - // 目前Python服务未实现此功能 - logger.warn('TQAPI服务暂未实现历史成交数据获取功能'); - return []; - } catch (error) { - logger.error(`获取合约${symbol}历史成交数据失败:`, error); - throw error; - } - } - - async close(): Promise { - try { - logger.log('关闭TQAPI连接...'); - const response = await this.httpClient.post('/disconnect'); - - if (response.success) { - logger.log('TQAPI连接已关闭'); - } else { - logger.error('关闭TQAPI连接失败:', response.message); - } - } catch (error) { - logger.error('关闭TQAPI连接失败:', error); - } finally { - this.initialized = false; - logger.log('TQAPI数据源已关闭'); - } - } -} \ No newline at end of file diff --git a/backend/src/services/datasource/TQDataSourcePython.md b/backend/src/services/datasource/TQDataSourcePython.md deleted file mode 100644 index 51202f0..0000000 --- a/backend/src/services/datasource/TQDataSourcePython.md +++ /dev/null @@ -1,62 +0,0 @@ -# TQDataSource Python 实现方案 - -## 概述 - -将当前的 TQSDK Node.js 接口改为使用天勤接口的 Python 封装 tqapi。通过创建一个 Python 服务来处理 tqapi 的调用,并修改现有的 Node.js 代码与之通信。 - -## 实现方案 - -### 1. Python 服务 - -使用 FastAPI 创建一个 Python 服务,提供以下 HTTP 接口: - -| 方法 | 路径 | 功能 | 请求体 | 响应体 | -|------|------|------|--------|--------| -| POST | /api/connect | 连接到天勤服务器 | `{"username": "...", "password": "..."}` | `{"success": true, "message": "连接成功"}` | -| GET | /api/contracts | 获取合约列表 | N/A | `{"success": true, "data": [{...}, {...}]}` | -| GET | /api/contract/{symbol} | 获取合约详情 | N/A | `{"success": true, "data": {...}}` | -| GET | /api/klines/{symbol} | 获取 K 线数据 | 查询参数: period, count | `{"success": true, "data": [{...}, {...}]}` | -| GET | /api/tick/{symbol} | 获取 tick 数据 | N/A | `{"success": true, "data": {...}}` | -| POST | /api/disconnect | 断开连接 | N/A | `{"success": true, "message": "断开成功"}` | - -### 2. Node.js 代码修改 - -修改现有的 TQDataSource 实现,改为调用 Python 服务的 HTTP 接口: - -- 初始化时,调用 /api/connect 接口 -- 获取合约列表时,调用 /api/contracts 接口 -- 获取合约详情时,调用 /api/contract/{symbol} 接口 -- 获取 K 线数据时,调用 /api/klines/{symbol} 接口 -- 获取 tick 数据时,调用 /api/tick/{symbol} 接口 -- 关闭时,调用 /api/disconnect 接口 - -## 技术栈 - -- Python 3.8+ -- FastAPI -- TqApi (天勤量化) -- Node.js 16+ -- axios (HTTP 客户端) - -## 优势 - -1. 使用官方推荐的 Python 封装,接口更稳定 -2. 充分利用 Python 生态系统的优势 -3. 与 Node.js 代码解耦,便于维护 -4. 可以独立部署和扩展 Python 服务 - -## 实现步骤 - -1. 创建 Python 服务项目 -2. 实现 Python 服务的核心功能 -3. 测试 Python 服务的接口 -4. 修改 Node.js 代码,与 Python 服务通信 -5. 测试整体功能 -6. 优化性能和错误处理 - -## 注意事项 - -1. 需要确保 Python 服务和 Node.js 服务在同一网络环境中 -2. 需要处理好错误和异常情况 -3. 需要考虑性能优化,特别是在获取大量数据时 -4. 需要确保安全性,特别是在处理敏感信息时 \ No newline at end of file diff --git a/backend/src/services/marketService.ts b/backend/src/services/marketService.ts index 5f8c388..19f0f62 100644 --- a/backend/src/services/marketService.ts +++ b/backend/src/services/marketService.ts @@ -1,5 +1,4 @@ // 市场数据服务 -import { DataSourceFactory, DataSourceType } from './datasource/DataSourceFactory'; import { futuresList, generateFuturesOverview, generateFutureData, generateKlineData, riskAlerts } from '../utils/mockData'; import { config } from '../config'; import { serviceImplementationClient } from './ServiceImplementationClient'; @@ -137,104 +136,6 @@ export const fetchMarketOverview = async () => { // service_implementation API 失败,尝试使用其他数据源 } - // 获取数据源配置 - const dataSourceConfig = getDataSourceConfig(); - logger.log('获取数据源配置:', dataSourceConfig); - // 检查是否有可用的数据源 - const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled; - if (!hasAvailableDataSource) { - throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); - } - - // 尝试使用TQSDK数据源 - if (dataSourceConfig.tqsdk?.enabled) { - try { - const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig); - - // 使用用户指定的合约列表 - const overview = []; - for (const future of futuresList) { - try { - // 构建合约符号(使用小写代码,因为TQAPI期望小写) - const symbol = `${future.exchange}.${future.code.toLowerCase()}${new Date().getFullYear().toString().slice(-2)}05`; - // 获取合约详情和实时行情 - const tick = await dataSource.getTickData(symbol); - - overview.push({ - code: future.code, - name: future.name, - currentPrice: tick.last_price, - changePercent: tick.price_change / tick.pre_close * 100, - winRate: Math.floor(Math.random() * 50) + 30, // 模拟胜率 - atr: +(Math.random() * 5 + 0.5).toFixed(2), // 模拟ATR - adx: Math.floor(Math.random() * 60) + 10, // 模拟ADX - adxStatus: (adx: number) => { - if (adx < 20) return '无趋势/震荡'; - if (adx < 40) return '弱趋势'; - return '强趋势'; - }, - trends: { - '5MIN': { - direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], - status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], - rsi: Math.floor(Math.random() * 80) + 10 - }, - '30MIN': { - direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], - status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], - rsi: Math.floor(Math.random() * 80) + 10 - }, - '1HOUR': { - direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], - status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], - rsi: Math.floor(Math.random() * 80) + 10 - }, - '1DAY': { - direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], - status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], - rsi: Math.floor(Math.random() * 80) + 10 - } - }, - tradingAdvice: { - entry: tick.last_price, - stopLoss: tick.last_price * (1 - 0.02 * (Math.random() + 0.5)), - target: tick.last_price * (1 + 0.03 * (Math.random() + 0.5)), - resistance: tick.last_price * (1 + 0.05 * (Math.random() + 0.5)), - support: tick.last_price * (1 - 0.05 * (Math.random() + 0.5)) - }, - overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)], - aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近` - }); - } catch (error) { - logger.error(`获取合约${future.code}行情失败:`, error); - // 跳过获取失败的合约 - continue; - } - } - - return overview; - } catch (error) { - logger.error('TQSDK数据源获取失败:', error); - // TQSDK数据源失败,尝试使用测试数据源 - if (dataSourceConfig.test?.enabled) { - logger.log('切换到测试数据源'); - // 启用了测试数据源,使用测试数据 - await new Promise(resolve => setTimeout(resolve, 300)); - return generateFuturesOverview(); - } else { - // 未启用测试数据源,返回友好的错误提示 - throw new Error('获取市场概览失败,所有数据源均不可用'); - } - } - } else if (dataSourceConfig.test?.enabled) { - // 直接使用测试数据源 - logger.log('使用测试数据源'); - await new Promise(resolve => setTimeout(resolve, 300)); - return generateFuturesOverview(); - } else { - // 无可用数据源 - throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); - } } catch (error) { logger.error('获取市场概览失败:', error); // 直接返回友好的错误提示 @@ -354,103 +255,6 @@ export const fetchMarketDetail = async (symbol: string) => { // service_implementation API 失败,尝试使用其他数据源 } - // 获取数据源配置 - const dataSourceConfig = getDataSourceConfig(); - - // 检查是否有可用的数据源 - const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled; - if (!hasAvailableDataSource) { - throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); - } - - // 尝试使用TQSDK数据源 - if (dataSourceConfig.tqsdk?.enabled) { - try { - const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig); - - // 构建合约符号(使用小写代码,因为TQAPI期望小写) - const contractSymbol = `${future.exchange}.${future.code.toLowerCase()}${new Date().getFullYear().toString().slice(-2)}05`; - - // 获取合约详情和实时行情 - const tick = await dataSource.getTickData(contractSymbol); - - // 转换为前端需要的格式 - return { - code: future.code, - name: future.name, - fullName: `${future.name}-${future.code}605`, - currentPrice: tick.last_price, - changePercent: tick.price_change / tick.pre_close * 100, - winRate: Math.floor(Math.random() * 50) + 30, - atr: +(Math.random() * 5 + 0.5).toFixed(2), - adx: Math.floor(Math.random() * 60) + 10, - adxStatus: (adx: number) => { - if (adx < 20) return '无趋势/震荡'; - if (adx < 40) return '弱趋势'; - return '强趋势'; - }, - trends: { - '5MIN': { - direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], - status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], - rsi: Math.floor(Math.random() * 80) + 10 - }, - '30MIN': { - direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], - status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], - rsi: Math.floor(Math.random() * 80) + 10 - }, - '1HOUR': { - direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], - status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], - rsi: Math.floor(Math.random() * 80) + 10 - }, - '1DAY': { - direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], - status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], - rsi: Math.floor(Math.random() * 80) + 10 - } - }, - indicators: { - macd: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)], - rsi: `${Math.floor(Math.random() * 80) + 10}(中性)`, - bollinger: ['触及上轨', '触及下轨', '中轨附近'][Math.floor(Math.random() * 3)], - kdj: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)] - }, - tradingAdvice: { - entry: tick.last_price, - stopLoss: tick.last_price * (1 - 0.02 * (Math.random() + 0.5)), - target: tick.last_price * (1 + 0.03 * (Math.random() + 0.5)), - resistance: tick.last_price * (1 + 0.05 * (Math.random() + 0.5)), - support: tick.last_price * (1 - 0.05 * (Math.random() + 0.5)) - }, - riskLevel: ['低', '中等', '高'][Math.floor(Math.random() * 3)], - volatility: ['低', '中等', '高'][Math.floor(Math.random() * 3)], - overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)], - aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近` - }; - } catch (error) { - logger.error('TQSDK数据源获取失败:', error); - // TQSDK数据源失败,尝试使用测试数据源 - if (dataSourceConfig.test?.enabled) { - logger.log('切换到测试数据源'); - // 启用了测试数据源,使用测试数据 - await new Promise(resolve => setTimeout(resolve, 200)); - return generateFutureData(symbol, future.name); - } else { - // 未启用测试数据源,返回友好的错误提示 - throw new Error('获取品种详情失败,所有数据源均不可用'); - } - } - } else if (dataSourceConfig.test?.enabled) { - // 直接使用测试数据源 - logger.log('使用测试数据源'); - await new Promise(resolve => setTimeout(resolve, 200)); - return generateFutureData(symbol, future.name); - } else { - // 无可用数据源 - throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); - } } catch (error) { logger.error(`获取品种${symbol}详情失败:`, error); // 直接返回友好的错误提示 @@ -535,58 +339,6 @@ export const fetchKlineData = async (symbol: string, period: string) => { logger.error('service_implementation API 获取K线数据失败:', error); // service_implementation API 失败,尝试使用其他数据源 } - - // 获取数据源配置 - const dataSourceConfig = getDataSourceConfig(); - - // 检查是否有可用的数据源 - const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled; - if (!hasAvailableDataSource) { - throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); - } - - // 尝试使用TQSDK数据源 - if (dataSourceConfig.tqsdk?.enabled) { - try { - const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig); - - // 构建合约符号(使用小写代码,因为TQAPI期望小写) - const contractSymbol = `${future.exchange}.${future.code.toLowerCase()}${new Date().getFullYear().toString().slice(-2)}05`; - - // 获取K线数据 - const klineData = await dataSource.getKlineData(contractSymbol, period, 30); - - // 转换为前端需要的格式 - return klineData.map(item => ({ - timestamp: item.datetime / 1000000000, // 转换为秒 - open: item.open, - high: item.high, - low: item.low, - close: item.close, - volume: item.volume - })); - } catch (error) { - logger.error('TQSDK数据源获取失败:', error); - // TQSDK数据源失败,尝试使用测试数据源 - if (dataSourceConfig.test?.enabled) { - logger.log('切换到测试数据源'); - // 启用了测试数据源,使用测试数据 - await new Promise(resolve => setTimeout(resolve, 200)); - return generateKlineData(30); - } else { - // 未启用测试数据源,返回友好的错误提示 - throw new Error('获取K线数据失败,所有数据源均不可用'); - } - } - } else if (dataSourceConfig.test?.enabled) { - // 直接使用测试数据源 - logger.log('使用测试数据源'); - await new Promise(resolve => setTimeout(resolve, 200)); - return generateKlineData(30); - } else { - // 无可用数据源 - throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); - } } catch (error) { logger.error(`获取合约${symbol}K线数据失败:`, error); // 直接返回友好的错误提示