fix: tqsdk初始化成功

master
Lxy 3 months ago
parent de5ad20375
commit aa84e15132

@ -19,7 +19,8 @@
"pg": "^8.11.3", "pg": "^8.11.3",
"redis": "^4.6.12", "redis": "^4.6.12",
"socket.io": "^4.7.4", "socket.io": "^4.7.4",
"tqsdk": "^1.3.1" "tqsdk": "^1.3.1",
"ws": "^8.19.0"
}, },
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
@ -28,6 +29,7 @@
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/node": "^20.10.4", "@types/node": "^20.10.4",
"@types/pg": "^8.10.9", "@types/pg": "^8.10.9",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0", "@typescript-eslint/parser": "^6.15.0",
"chai": "^4.3.10", "chai": "^4.3.10",
@ -600,6 +602,16 @@
"@types/webidl-conversions": "*" "@types/webidl-conversions": "*"
} }
}, },
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
@ -1661,6 +1673,27 @@
"node": ">=10.0.0" "node": ">=10.0.0"
} }
}, },
"node_modules/engine.io/node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/es-define-property": { "node_modules/es-define-property": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@ -4174,6 +4207,27 @@
"ws": "~8.18.3" "ws": "~8.18.3"
} }
}, },
"node_modules/socket.io-adapter/node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/socket.io-parser": { "node_modules/socket.io-parser": {
"version": "4.2.5", "version": "4.2.5",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz",
@ -4822,9 +4876,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/ws": { "node_modules/ws": {
"version": "8.18.3", "version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"

@ -22,7 +22,8 @@
"pg": "^8.11.3", "pg": "^8.11.3",
"redis": "^4.6.12", "redis": "^4.6.12",
"socket.io": "^4.7.4", "socket.io": "^4.7.4",
"tqsdk": "^1.3.1" "tqsdk": "^1.3.1",
"ws": "^8.19.0"
}, },
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
@ -31,6 +32,7 @@
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/node": "^20.10.4", "@types/node": "^20.10.4",
"@types/pg": "^8.10.9", "@types/pg": "^8.10.9",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0", "@typescript-eslint/parser": "^6.15.0",
"chai": "^4.3.10", "chai": "^4.3.10",

@ -1,7 +1,7 @@
// TQSDK数据源实现 // TQSDK数据源实现
import { DataSource } from './DataSource'; import { DataSource } from './DataSource';
import TqSdk from 'tqsdk'; import TQSDK from 'tqsdk';
import TqAccount from 'tqsdk'; import WebSocket from 'ws';
export class TQDataSource implements DataSource { export class TQDataSource implements DataSource {
private tq: any = null; private tq: any = null;
@ -15,6 +15,7 @@ export class TQDataSource implements DataSource {
}; };
constructor(config: any = {}) { constructor(config: any = {}) {
console.log('使用TQSDK数据源初始化...');
this.config = { this.config = {
username: config.username || '', username: config.username || '',
password: config.password || '', password: config.password || '',
@ -22,26 +23,45 @@ export class TQDataSource implements DataSource {
retries: config.retries || 3, retries: config.retries || 3,
maxConnections: config.maxConnections || 5 maxConnections: config.maxConnections || 5
}; };
console.log('TQSDK数据源配置:', this.config);
} }
async initialize(): Promise<boolean> { async initialize(): Promise<boolean> {
try { try {
console.log('开始初始化TQSDK数据源...');
// 创建TQSDK实例使用配置的参数 // 创建TQSDK实例使用配置的参数
const tqConfig: any = {}; console.log('创建TQSDK实例...');
// 如果有用户名和密码,使用用户名密码登录 // 初始化TQSDK传入WebSocket对象
if (this.config.username && this.config.password) { this.tq = new TQSDK({ autoInit: true }, { WebSocket });
tqConfig.account = this.config.username;
tqConfig.password = this.config.password; console.log('TQSDK实例创建成功等待就绪...');
}
// 等待初始化完成,设置超时
const timeout = this.config.timeout || 10000;
console.log('等待TQSDK连接超时时间:', timeout, 'ms');
this.tq = new TqSdk(tqConfig); await new Promise((resolve, reject) => {
// 设置超时
const timeoutId = setTimeout(() => {
console.error('TQSDK连接超时');
reject(new Error('TQSDK连接超时'));
}, timeout);
// 等待初始化完成 // 监听就绪事件
await new Promise((resolve) => { this.tq?.on('ready', () => {
this.tq?.on('connected', () => { console.log('TQSDK就绪');
clearTimeout(timeoutId);
resolve(true); resolve(true);
}); });
// 监听错误事件
this.tq?.on('error', (error: any) => {
console.error('TQSDK错误:', error);
clearTimeout(timeoutId);
reject(error);
});
}); });
this.initialized = true; this.initialized = true;
@ -49,6 +69,19 @@ export class TQDataSource implements DataSource {
return true; return true;
} catch (error) { } catch (error) {
console.error('TQSDK数据源初始化失败:', error); console.error('TQSDK数据源初始化失败:', error);
// 清理资源
if (this.tq) {
try {
if (typeof this.tq.close === 'function') {
this.tq.close();
} else {
console.log('TqApi实例没有close方法');
}
} catch (closeError) {
console.error('关闭TQSDK连接失败:', closeError);
}
this.tq = null;
}
this.initialized = false; this.initialized = false;
return false; return false;
} }
@ -60,15 +93,31 @@ export class TQDataSource implements DataSource {
} }
try { try {
// 获取所有合约 // 使用TQSDK的getQuotesByInput方法获取合约列表
const contracts = await this.tq.get_contracts(); // 这里使用空字符串搜索,获取所有合约
// 过滤期货合约,排除期权等其他类型 const contracts = this.tq.getQuotesByInput('', {
const futuresContracts = contracts.filter((contract: any) => { future: true,
return contract.exchange.includes('SHFE') || future_index: true,
contract.exchange.includes('DCE') || future_cont: true,
contract.exchange.includes('CZCE') || option: false,
contract.exchange.includes('INE'); combine: false
}); });
// 转换合约格式
const futuresContracts = contracts.map((symbol: string) => {
try {
const quote = this.tq.getQuote(symbol);
return {
symbol: symbol,
name: quote.instrument_name,
exchange: symbol.split('.')[0]
};
} catch (error) {
console.error(`获取合约${symbol}信息失败:`, error);
return null;
}
}).filter((contract: any) => contract !== null);
return futuresContracts; return futuresContracts;
} catch (error) { } catch (error) {
console.error('获取合约列表失败:', error); console.error('获取合约列表失败:', error);
@ -83,8 +132,17 @@ export class TQDataSource implements DataSource {
try { try {
// 获取合约详情 // 获取合约详情
const contract = await this.tq.get_contract(symbol); const quote = this.tq.getQuote(symbol);
return contract; return {
symbol: symbol,
name: quote.instrument_name,
exchange: symbol.split('.')[0],
product_id: quote.product_id,
price_tick: quote.price_tick,
volume_multiple: quote.volume_multiple,
margin_rate: quote.margin_rate,
expire_datetime: quote.expire_datetime
};
} catch (error) { } catch (error) {
console.error(`获取合约${symbol}详情失败:`, error); console.error(`获取合约${symbol}详情失败:`, error);
throw error; throw error;
@ -98,10 +156,10 @@ export class TQDataSource implements DataSource {
try { try {
// 转换周期格式 // 转换周期格式
const tqPeriod = this.convertPeriod(period); const duration = this.convertPeriodToDuration(period);
// 获取K线数据 // 获取K线数据
const kline = await this.tq.get_kline_serial(symbol, tqPeriod, count); const klines = this.tq.getKlines(symbol, duration);
return kline; return klines.data || [];
} catch (error) { } catch (error) {
console.error(`获取合约${symbol}K线数据失败:`, error); console.error(`获取合约${symbol}K线数据失败:`, error);
throw error; throw error;
@ -115,8 +173,22 @@ export class TQDataSource implements DataSource {
try { try {
// 获取实时行情数据 // 获取实时行情数据
const tick = await this.tq.get_tick_serial(symbol, 1); const quote = this.tq.getQuote(symbol);
return tick[0]; return {
last_price: quote.last_price,
price_change: quote.last_price - quote.pre_settlement,
pre_close: quote.pre_close,
open: quote.open,
high: quote.high,
low: quote.low,
volume: quote.volume,
open_interest: quote.open_interest,
bid_price1: quote.bid_price1,
bid_volume1: quote.bid_volume1,
ask_price1: quote.ask_price1,
ask_volume1: quote.ask_volume1,
datetime: quote.datetime
};
} catch (error) { } catch (error) {
console.error(`获取合约${symbol}实时行情数据失败:`, error); console.error(`获取合约${symbol}实时行情数据失败:`, error);
throw error; throw error;
@ -164,9 +236,10 @@ export class TQDataSource implements DataSource {
} }
try { try {
// 获取历史成交数据 // TQSDK Node.js版本可能不支持直接获取历史成交数据
const trades = await this.tq.get_trade_serial(symbol, start, end); // 这里返回空数组实际使用时需要根据TQSDK文档调整
return trades; console.warn('TQSDK Node.js版本可能不支持直接获取历史成交数据');
return [];
} catch (error) { } catch (error) {
console.error(`获取合约${symbol}历史成交数据失败:`, error); console.error(`获取合约${symbol}历史成交数据失败:`, error);
throw error; throw error;
@ -175,8 +248,16 @@ export class TQDataSource implements DataSource {
async close(): Promise<void> { async close(): Promise<void> {
if (this.tq) { if (this.tq) {
if (typeof this.tq.close === 'function') { try {
this.tq.close(); // TQSDK Node.js版本可能没有close方法
// 这里尝试关闭websocket连接
if (this.tq.quotesWs && typeof this.tq.quotesWs.close === 'function') {
this.tq.quotesWs.close();
console.log('TQSDK行情WebSocket连接已关闭');
}
console.log('TQSDK连接已关闭');
} catch (error) {
console.error('关闭TQSDK连接失败:', error);
} }
this.tq = null; this.tq = null;
this.initialized = false; this.initialized = false;
@ -184,27 +265,27 @@ export class TQDataSource implements DataSource {
} }
} }
// 转换周期格式 // 转换周期格式为TQSDK所需的纳秒格式
private convertPeriod(period: string): string { private convertPeriodToDuration(period: string): number {
switch (period) { switch (period) {
case '1M': case '1M':
return '1min'; return 60 * 1e9; // 1分钟
case '5M': case '5M':
return '5min'; return 5 * 60 * 1e9; // 5分钟
case '15M': case '15M':
return '15min'; return 15 * 60 * 1e9; // 15分钟
case '30M': case '30M':
return '30min'; return 30 * 60 * 1e9; // 30分钟
case '1H': case '1H':
return '60min'; return 60 * 60 * 1e9; // 1小时
case '4H': case '4H':
return '240min'; return 4 * 60 * 60 * 1e9; // 4小时
case '1D': case '1D':
return '1d'; return 24 * 60 * 60 * 1e9; // 1天
case '1W': case '1W':
return '1w'; return 7 * 24 * 60 * 60 * 1e9; // 1周
default: default:
return '1min'; return 60 * 1e9; // 默认1分钟
} }
} }
} }
Loading…
Cancel
Save