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

// 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数据源已关闭');
}
}
}