// TQAPI数据源实现 - 使用Python服务 import { DataSource } from './DataSource'; // 简化的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}`; } console.log('发送GET请求:', url); try { // 设置20秒超时 const controller = new AbortController(); const timeoutId = setTimeout(() => { console.error('GET请求超时,正在中止请求...'); controller.abort(); }, 20000); try { console.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(); console.log(`GET请求完成,耗时: ${end - start}ms`); console.log('GET请求响应状态:', response.status); if (!response.ok) { const errorText = await response.text(); console.error('GET请求失败:', errorText); throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`); } console.log('正在解析GET响应数据...'); const data = await response.json(); console.log('GET请求响应数据:', data); return data as T; } finally { clearTimeout(timeoutId); } } catch (error: any) { console.error('GET请求网络错误:', error.message || error); console.error('错误详情:', error); console.error('错误堆栈:', error.stack); throw new Error(`网络请求失败: ${error.message || error}`); } } async post(endpoint: string, data?: any): Promise { const url = `${this.baseUrl}${endpoint}`; console.log('发送POST请求:', url, '数据:', data); try { // 设置20秒超时 const controller = new AbortController(); const timeoutId = setTimeout(() => { console.error('POST请求超时,正在中止请求...'); controller.abort(); }, 20000); try { console.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(); console.log(`请求完成,耗时: ${end - start}ms`); console.log('POST请求响应状态:', response.status); if (!response.ok) { const errorText = await response.text(); console.error('POST请求失败:', errorText); throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`); } console.log('正在解析响应数据...'); const responseData = await response.json(); console.log('POST请求响应数据:', responseData); return responseData as T; } finally { clearTimeout(timeoutId); } } catch (error: any) { console.error('POST请求网络错误:', error.message || error); console.error('错误详情:', error); console.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 = {}) { console.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` }; console.log('TQAPI数据源配置:', this.config); // 测试Python服务URL是否正确 console.log('测试Python服务URL:', this.config.pythonServiceUrl); this.httpClient = new HttpClient(this.config.pythonServiceUrl!); } async initialize(): Promise { try { console.log('开始初始化TQAPI数据源...'); console.log('Python服务URL:', this.config.pythonServiceUrl); console.log('连接参数:', { username: this.config.username ? '***' : '', password: this.config.password ? '***' : '' }); // 先测试Python服务是否可达 console.log('测试Python服务是否可达...'); try { const testUrl = `${this.config.pythonServiceUrl}/../health`; console.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 }); console.log('Python服务健康检查响应:', testResponse.status); if (testResponse.ok) { console.log('Python服务可达'); } else { console.error('Python服务不可达,状态码:', testResponse.status); } } finally { clearTimeout(timeoutId); } } catch (error: any) { console.error('Python服务健康检查失败:', error.message || error); } // 连接到天勤服务器 console.log('连接到天勤服务器...'); try { const response = await this.httpClient.post('/connect', { username: this.config.username, password: this.config.password }); console.log('连接响应:', response); if (response.success) { console.log('TQAPI连接成功'); this.initialized = true; return true; } else { console.error('TQAPI连接失败:', response.message); this.initialized = false; return false; } } catch (error: any) { console.error('连接请求失败:', error.message || error); console.error('错误详情:', error); throw error; } } catch (error) { console.error('TQAPI数据源初始化失败:', error); this.initialized = false; return false; } } async getContractList(): Promise { if (!this.initialized) { throw new Error('TQAPI数据源未初始化'); } try { console.log('获取合约列表...'); const response = await this.httpClient.get('/contracts'); if (response.success && Array.isArray(response.data)) { console.log('获取合约列表成功,数量:', response.data.length); return response.data; } else { console.error('获取合约列表失败:', response.message); return []; } } catch (error) { console.error('获取合约列表失败:', error); throw error; } } async getContractDetail(symbol: string): Promise { if (!this.initialized) { throw new Error('TQAPI数据源未初始化'); } try { console.log(`获取合约${symbol}详情...`); const response = await this.httpClient.get(`/contract/${symbol}`); if (response.success) { console.log(`获取合约${symbol}详情成功`); return response.data; } else { console.error(`获取合约${symbol}详情失败:`, response.message); throw new Error(`获取合约${symbol}详情失败`); } } catch (error) { console.error(`获取合约${symbol}详情失败:`, error); throw error; } } async getKlineData(symbol: string, period: string, count: number): Promise { if (!this.initialized) { throw new Error('TQAPI数据源未初始化'); } try { console.log('开始获取K线数据:', { symbol: symbol, period: period, count: count }); // 确保合约代码格式正确 let contractSymbol = symbol; if (!contractSymbol.includes('.')) { // 如果没有交易所前缀,尝试添加默认交易所 console.warn('合约代码缺少交易所前缀,尝试添加默认交易所'); contractSymbol = `SHFE.${contractSymbol}`; } console.log('使用的合约代码:', contractSymbol); const response = await this.httpClient.get('/klines/' + contractSymbol, { period: period, count: count.toString() }); if (response.success && Array.isArray(response.data)) { console.log('获取K线数据成功,长度:', response.data.length); return response.data; } else { console.error('获取K线数据失败:', response.message); return []; } } catch (error) { console.error(`获取合约${symbol}K线数据失败:`, error); throw error; } } async getTickData(symbol: string): Promise { if (!this.initialized) { throw new Error('TQAPI数据源未初始化'); } try { console.log(`获取合约${symbol}实时行情数据...`); // 确保合约代码格式正确 let contractSymbol = symbol; if (!contractSymbol.includes('.')) { // 如果没有交易所前缀,尝试添加默认交易所 console.warn('合约代码缺少交易所前缀,尝试添加默认交易所'); contractSymbol = `SHFE.${contractSymbol}`; } console.log('使用的合约代码:', contractSymbol); console.log('正在发送GET请求到:', `/tick/${contractSymbol}`); const start = Date.now(); const response = await this.httpClient.get(`/tick/${contractSymbol}`); const end = Date.now(); console.log(`GET请求完成,耗时: ${end - start}ms`); console.log('响应数据:', response); if (response.success) { console.log(`获取合约${symbol}实时行情数据成功`); // 计算价格变化 const tickData = response.data; tickData.price_change = tickData.last_price - (tickData.pre_close || 0); console.log('计算价格变化后的数据:', tickData); return tickData; } else { console.error(`获取合约${symbol}实时行情数据失败:`, response.message); throw new Error(`获取合约${symbol}实时行情数据失败`); } } catch (error) { console.error(`获取合约${symbol}实时行情数据失败:`, error); throw error; } } 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) { console.error(`获取合约${contract.symbol}行情失败:`, error); } } return overview; } catch (error) { console.error('获取市场概览失败:', error); throw error; } } async getHistoricalTrades(symbol: string, start: number, end: number): Promise { if (!this.initialized) { throw new Error('TQAPI数据源未初始化'); } try { // 目前Python服务未实现此功能 console.warn('TQAPI服务暂未实现历史成交数据获取功能'); return []; } catch (error) { console.error(`获取合约${symbol}历史成交数据失败:`, error); throw error; } } async close(): Promise { try { console.log('关闭TQAPI连接...'); const response = await this.httpClient.post('/disconnect'); if (response.success) { console.log('TQAPI连接已关闭'); } else { console.error('关闭TQAPI连接失败:', response.message); } } catch (error) { console.error('关闭TQAPI连接失败:', error); } finally { this.initialized = false; console.log('TQAPI数据源已关闭'); } } }