You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

438 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// TQAPI数据源实现 - 使用Python服务
import { DataSource } from './DataSource';
// 简化的HTTP客户端
class HttpClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
async get<T>(endpoint: string, params?: Record<string, string>): Promise<T> {
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<T>(endpoint: string, data?: any): Promise<T> {
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<boolean> {
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<any>('/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<any[]> {
if (!this.initialized) {
throw new Error('TQAPI数据源未初始化');
}
try {
console.log('获取合约列表...');
const response = await this.httpClient.get<any>('/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<any> {
if (!this.initialized) {
throw new Error('TQAPI数据源未初始化');
}
try {
console.log(`获取合约${symbol}详情...`);
const response = await this.httpClient.get<any>(`/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<any[]> {
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<any>('/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<any> {
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<any>(`/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);
// 返回默认数据,避免整个请求失败
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) {
console.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<any[]> {
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<any[]> {
if (!this.initialized) {
throw new Error('TQAPI数据源未初始化');
}
try {
// 目前Python服务未实现此功能
console.warn('TQAPI服务暂未实现历史成交数据获取功能');
return [];
} catch (error) {
console.error(`获取合约${symbol}历史成交数据失败:`, error);
throw error;
}
}
async close(): Promise<void> {
try {
console.log('关闭TQAPI连接...');
const response = await this.httpClient.post<any>('/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数据源已关闭');
}
}
}