|
|
|
|
|
// TQSDK数据源实现
|
|
|
|
|
|
import { DataSource } from './DataSource';
|
|
|
|
|
|
import { TqSdk, TqAccount } from 'tqsdk';
|
|
|
|
|
|
|
|
|
|
|
|
export class TQDataSource implements DataSource {
|
|
|
|
|
|
private tq: TqSdk | null = null;
|
|
|
|
|
|
private initialized: boolean = false;
|
|
|
|
|
|
private config: {
|
|
|
|
|
|
username?: string;
|
|
|
|
|
|
password?: string;
|
|
|
|
|
|
timeout?: number;
|
|
|
|
|
|
retries?: number;
|
|
|
|
|
|
maxConnections?: number;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
constructor(config: any = {}) {
|
|
|
|
|
|
this.config = {
|
|
|
|
|
|
username: config.username || '',
|
|
|
|
|
|
password: config.password || '',
|
|
|
|
|
|
timeout: config.timeout || 30000,
|
|
|
|
|
|
retries: config.retries || 3,
|
|
|
|
|
|
maxConnections: config.maxConnections || 5
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async initialize(): Promise<boolean> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 创建TQSDK实例,使用配置的参数
|
|
|
|
|
|
const tqConfig: any = {};
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有用户名和密码,使用用户名密码登录
|
|
|
|
|
|
if (this.config.username && this.config.password) {
|
|
|
|
|
|
tqConfig.account = this.config.username;
|
|
|
|
|
|
tqConfig.password = this.config.password;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.tq = new TqSdk(tqConfig);
|
|
|
|
|
|
|
|
|
|
|
|
// 等待初始化完成
|
|
|
|
|
|
await new Promise((resolve) => {
|
|
|
|
|
|
this.tq?.on('connected', () => {
|
|
|
|
|
|
resolve(true);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.initialized = true;
|
|
|
|
|
|
console.log('TQSDK数据源初始化成功');
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('TQSDK数据源初始化失败:', error);
|
|
|
|
|
|
this.initialized = false;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getContractList(): Promise<any[]> {
|
|
|
|
|
|
if (!this.initialized || !this.tq) {
|
|
|
|
|
|
throw new Error('TQSDK数据源未初始化');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取所有合约
|
|
|
|
|
|
const contracts = await this.tq.get_contracts();
|
|
|
|
|
|
// 过滤期货合约,排除期权等其他类型
|
|
|
|
|
|
const futuresContracts = contracts.filter((contract: any) => {
|
|
|
|
|
|
return contract.exchange.includes('SHFE') ||
|
|
|
|
|
|
contract.exchange.includes('DCE') ||
|
|
|
|
|
|
contract.exchange.includes('CZCE') ||
|
|
|
|
|
|
contract.exchange.includes('INE');
|
|
|
|
|
|
});
|
|
|
|
|
|
return futuresContracts;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取合约列表失败:', error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getContractDetail(symbol: string): Promise<any> {
|
|
|
|
|
|
if (!this.initialized || !this.tq) {
|
|
|
|
|
|
throw new Error('TQSDK数据源未初始化');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取合约详情
|
|
|
|
|
|
const contract = await this.tq.get_contract(symbol);
|
|
|
|
|
|
return contract;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`获取合约${symbol}详情失败:`, error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getKlineData(symbol: string, period: string, count: number): Promise<any[]> {
|
|
|
|
|
|
if (!this.initialized || !this.tq) {
|
|
|
|
|
|
throw new Error('TQSDK数据源未初始化');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 转换周期格式
|
|
|
|
|
|
const tqPeriod = this.convertPeriod(period);
|
|
|
|
|
|
// 获取K线数据
|
|
|
|
|
|
const kline = await this.tq.get_kline_serial(symbol, tqPeriod, count);
|
|
|
|
|
|
return kline;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`获取合约${symbol}K线数据失败:`, error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getTickData(symbol: string): Promise<any> {
|
|
|
|
|
|
if (!this.initialized || !this.tq) {
|
|
|
|
|
|
throw new Error('TQSDK数据源未初始化');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取实时行情数据
|
|
|
|
|
|
const tick = await this.tq.get_tick_serial(symbol, 1);
|
|
|
|
|
|
return tick[0];
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`获取合约${symbol}实时行情数据失败:`, error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getMarketOverview(): Promise<any[]> {
|
|
|
|
|
|
if (!this.initialized || !this.tq) {
|
|
|
|
|
|
throw new Error('TQSDK数据源未初始化');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
overview.push({
|
|
|
|
|
|
symbol: contract.symbol,
|
|
|
|
|
|
name: contract.name,
|
|
|
|
|
|
price: tick.last_price,
|
|
|
|
|
|
change: tick.price_change,
|
|
|
|
|
|
change_percent: tick.price_change / tick.pre_close * 100,
|
|
|
|
|
|
volume: tick.volume,
|
|
|
|
|
|
open_interest: tick.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 || !this.tq) {
|
|
|
|
|
|
throw new Error('TQSDK数据源未初始化');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取历史成交数据
|
|
|
|
|
|
const trades = await this.tq.get_trade_serial(symbol, start, end);
|
|
|
|
|
|
return trades;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`获取合约${symbol}历史成交数据失败:`, error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async close(): Promise<void> {
|
|
|
|
|
|
if (this.tq) {
|
|
|
|
|
|
this.tq.close();
|
|
|
|
|
|
this.tq = null;
|
|
|
|
|
|
this.initialized = false;
|
|
|
|
|
|
console.log('TQSDK数据源已关闭');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换周期格式
|
|
|
|
|
|
private convertPeriod(period: string): string {
|
|
|
|
|
|
switch (period) {
|
|
|
|
|
|
case '1M':
|
|
|
|
|
|
return '1min';
|
|
|
|
|
|
case '5M':
|
|
|
|
|
|
return '5min';
|
|
|
|
|
|
case '15M':
|
|
|
|
|
|
return '15min';
|
|
|
|
|
|
case '30M':
|
|
|
|
|
|
return '30min';
|
|
|
|
|
|
case '1H':
|
|
|
|
|
|
return '60min';
|
|
|
|
|
|
case '4H':
|
|
|
|
|
|
return '240min';
|
|
|
|
|
|
case '1D':
|
|
|
|
|
|
return '1d';
|
|
|
|
|
|
case '1W':
|
|
|
|
|
|
return '1w';
|
|
|
|
|
|
default:
|
|
|
|
|
|
return '1min';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|