|
|
|
|
@ -39,14 +39,14 @@ export const fetchMarketOverview = async () => {
|
|
|
|
|
|
|
|
|
|
// 转换为数组
|
|
|
|
|
const contractList = Array.from(uniqueContracts.values());
|
|
|
|
|
logger.log(`获取到 ${contractList.length} 个独特品种`);
|
|
|
|
|
logger.log(`获取到 ${contractList.length} 个独特品种,分别是: ${contractList.map(c => c.code).join(', ')}`);
|
|
|
|
|
|
|
|
|
|
// 使用获取到的合约列表
|
|
|
|
|
const overview = [];
|
|
|
|
|
for (const future of contractList) {
|
|
|
|
|
try {
|
|
|
|
|
// 构建合约符号(使用大写代码,因为 service_implementation API 期望大写)
|
|
|
|
|
const symbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}05`;
|
|
|
|
|
const symbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}03`;
|
|
|
|
|
|
|
|
|
|
// 获取合约详情
|
|
|
|
|
logger.log(`获取合约${symbol}详情...`);
|
|
|
|
|
@ -131,104 +131,104 @@ export const fetchMarketOverview = async () => {
|
|
|
|
|
// service_implementation API 失败,尝试使用其他数据源
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取数据源配置
|
|
|
|
|
const dataSourceConfig = getDataSourceConfig();
|
|
|
|
|
logger.log('获取数据源配置:', dataSourceConfig);
|
|
|
|
|
// 检查是否有可用的数据源
|
|
|
|
|
const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled;
|
|
|
|
|
if (!hasAvailableDataSource) {
|
|
|
|
|
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 尝试使用TQSDK数据源
|
|
|
|
|
if (dataSourceConfig.tqsdk?.enabled) {
|
|
|
|
|
try {
|
|
|
|
|
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
|
|
|
|
|
|
|
|
|
|
// 使用用户指定的合约列表
|
|
|
|
|
const overview = [];
|
|
|
|
|
for (const future of futuresList) {
|
|
|
|
|
try {
|
|
|
|
|
// 构建合约符号(使用小写代码,因为TQAPI期望小写)
|
|
|
|
|
const symbol = `${future.exchange}.${future.code.toLowerCase()}${new Date().getFullYear().toString().slice(-2)}05`;
|
|
|
|
|
// 获取合约详情和实时行情
|
|
|
|
|
const tick = await dataSource.getTickData(symbol);
|
|
|
|
|
|
|
|
|
|
overview.push({
|
|
|
|
|
code: future.code,
|
|
|
|
|
name: future.name,
|
|
|
|
|
currentPrice: tick.last_price,
|
|
|
|
|
changePercent: tick.price_change / tick.pre_close * 100,
|
|
|
|
|
winRate: Math.floor(Math.random() * 50) + 30, // 模拟胜率
|
|
|
|
|
atr: +(Math.random() * 5 + 0.5).toFixed(2), // 模拟ATR
|
|
|
|
|
adx: Math.floor(Math.random() * 60) + 10, // 模拟ADX
|
|
|
|
|
adxStatus: (adx: number) => {
|
|
|
|
|
if (adx < 20) return '无趋势/震荡';
|
|
|
|
|
if (adx < 40) return '弱趋势';
|
|
|
|
|
return '强趋势';
|
|
|
|
|
},
|
|
|
|
|
trends: {
|
|
|
|
|
'5MIN': {
|
|
|
|
|
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
|
|
|
|
|
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
|
|
|
|
|
rsi: Math.floor(Math.random() * 80) + 10
|
|
|
|
|
},
|
|
|
|
|
'30MIN': {
|
|
|
|
|
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
|
|
|
|
|
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
|
|
|
|
|
rsi: Math.floor(Math.random() * 80) + 10
|
|
|
|
|
},
|
|
|
|
|
'1HOUR': {
|
|
|
|
|
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
|
|
|
|
|
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
|
|
|
|
|
rsi: Math.floor(Math.random() * 80) + 10
|
|
|
|
|
},
|
|
|
|
|
'1DAY': {
|
|
|
|
|
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
|
|
|
|
|
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
|
|
|
|
|
rsi: Math.floor(Math.random() * 80) + 10
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
tradingAdvice: {
|
|
|
|
|
entry: tick.last_price,
|
|
|
|
|
stopLoss: tick.last_price * (1 - 0.02 * (Math.random() + 0.5)),
|
|
|
|
|
target: tick.last_price * (1 + 0.03 * (Math.random() + 0.5)),
|
|
|
|
|
resistance: tick.last_price * (1 + 0.05 * (Math.random() + 0.5)),
|
|
|
|
|
support: tick.last_price * (1 - 0.05 * (Math.random() + 0.5))
|
|
|
|
|
},
|
|
|
|
|
overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)],
|
|
|
|
|
aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近`
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error(`获取合约${future.code}行情失败:`, error);
|
|
|
|
|
// 跳过获取失败的合约
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return overview;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('TQSDK数据源获取失败:', error);
|
|
|
|
|
// TQSDK数据源失败,尝试使用测试数据源
|
|
|
|
|
if (dataSourceConfig.test?.enabled) {
|
|
|
|
|
logger.log('切换到测试数据源');
|
|
|
|
|
// 启用了测试数据源,使用测试数据
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
return generateFuturesOverview();
|
|
|
|
|
} else {
|
|
|
|
|
// 未启用测试数据源,返回友好的错误提示
|
|
|
|
|
throw new Error('获取市场概览失败,所有数据源均不可用');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (dataSourceConfig.test?.enabled) {
|
|
|
|
|
// 直接使用测试数据源
|
|
|
|
|
logger.log('使用测试数据源');
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
return generateFuturesOverview();
|
|
|
|
|
} else {
|
|
|
|
|
// 无可用数据源
|
|
|
|
|
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
|
|
|
|
|
}
|
|
|
|
|
// // 获取数据源配置
|
|
|
|
|
// const dataSourceConfig = getDataSourceConfig();
|
|
|
|
|
// logger.log('获取数据源配置:', dataSourceConfig);
|
|
|
|
|
// // 检查是否有可用的数据源
|
|
|
|
|
// const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled;
|
|
|
|
|
// if (!hasAvailableDataSource) {
|
|
|
|
|
// throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // 尝试使用TQSDK数据源
|
|
|
|
|
// if (dataSourceConfig.tqsdk?.enabled) {
|
|
|
|
|
// try {
|
|
|
|
|
// const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
|
|
|
|
|
|
|
|
|
|
// // 使用用户指定的合约列表
|
|
|
|
|
// const overview = [];
|
|
|
|
|
// for (const future of futuresList) {
|
|
|
|
|
// try {
|
|
|
|
|
// // 构建合约符号(使用小写代码,因为TQAPI期望小写)
|
|
|
|
|
// const symbol = `${future.exchange}.${future.code.toLowerCase()}${new Date().getFullYear().toString().slice(-2)}05`;
|
|
|
|
|
// // 获取合约详情和实时行情
|
|
|
|
|
// const tick = await dataSource.getTickData(symbol);
|
|
|
|
|
|
|
|
|
|
// overview.push({
|
|
|
|
|
// code: future.code,
|
|
|
|
|
// name: future.name,
|
|
|
|
|
// currentPrice: tick.last_price,
|
|
|
|
|
// changePercent: tick.price_change / tick.pre_close * 100,
|
|
|
|
|
// winRate: Math.floor(Math.random() * 50) + 30, // 模拟胜率
|
|
|
|
|
// atr: +(Math.random() * 5 + 0.5).toFixed(2), // 模拟ATR
|
|
|
|
|
// adx: Math.floor(Math.random() * 60) + 10, // 模拟ADX
|
|
|
|
|
// adxStatus: (adx: number) => {
|
|
|
|
|
// if (adx < 20) return '无趋势/震荡';
|
|
|
|
|
// if (adx < 40) return '弱趋势';
|
|
|
|
|
// return '强趋势';
|
|
|
|
|
// },
|
|
|
|
|
// trends: {
|
|
|
|
|
// '5MIN': {
|
|
|
|
|
// direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
|
|
|
|
|
// status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
|
|
|
|
|
// rsi: Math.floor(Math.random() * 80) + 10
|
|
|
|
|
// },
|
|
|
|
|
// '30MIN': {
|
|
|
|
|
// direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
|
|
|
|
|
// status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
|
|
|
|
|
// rsi: Math.floor(Math.random() * 80) + 10
|
|
|
|
|
// },
|
|
|
|
|
// '1HOUR': {
|
|
|
|
|
// direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
|
|
|
|
|
// status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
|
|
|
|
|
// rsi: Math.floor(Math.random() * 80) + 10
|
|
|
|
|
// },
|
|
|
|
|
// '1DAY': {
|
|
|
|
|
// direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
|
|
|
|
|
// status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
|
|
|
|
|
// rsi: Math.floor(Math.random() * 80) + 10
|
|
|
|
|
// }
|
|
|
|
|
// },
|
|
|
|
|
// tradingAdvice: {
|
|
|
|
|
// entry: tick.last_price,
|
|
|
|
|
// stopLoss: tick.last_price * (1 - 0.02 * (Math.random() + 0.5)),
|
|
|
|
|
// target: tick.last_price * (1 + 0.03 * (Math.random() + 0.5)),
|
|
|
|
|
// resistance: tick.last_price * (1 + 0.05 * (Math.random() + 0.5)),
|
|
|
|
|
// support: tick.last_price * (1 - 0.05 * (Math.random() + 0.5))
|
|
|
|
|
// },
|
|
|
|
|
// overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)],
|
|
|
|
|
// aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近`
|
|
|
|
|
// });
|
|
|
|
|
// } catch (error) {
|
|
|
|
|
// logger.error(`获取合约${future.code}行情失败:`, error);
|
|
|
|
|
// // 跳过获取失败的合约
|
|
|
|
|
// continue;
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// return overview;
|
|
|
|
|
// } catch (error) {
|
|
|
|
|
// logger.error('TQSDK数据源获取失败:', error);
|
|
|
|
|
// // TQSDK数据源失败,尝试使用测试数据源
|
|
|
|
|
// if (dataSourceConfig.test?.enabled) {
|
|
|
|
|
// logger.log('切换到测试数据源');
|
|
|
|
|
// // 启用了测试数据源,使用测试数据
|
|
|
|
|
// await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
// return generateFuturesOverview();
|
|
|
|
|
// } else {
|
|
|
|
|
// // 未启用测试数据源,返回友好的错误提示
|
|
|
|
|
// throw new Error('获取市场概览失败,所有数据源均不可用');
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// } else if (dataSourceConfig.test?.enabled) {
|
|
|
|
|
// // 直接使用测试数据源
|
|
|
|
|
// logger.log('使用测试数据源');
|
|
|
|
|
// await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
// return generateFuturesOverview();
|
|
|
|
|
// } else {
|
|
|
|
|
// // 无可用数据源
|
|
|
|
|
// throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
|
|
|
|
|
// }
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('获取市场概览失败:', error);
|
|
|
|
|
// 直接返回友好的错误提示
|
|
|
|
|
@ -252,7 +252,7 @@ export const fetchMarketDetail = async (symbol: string) => {
|
|
|
|
|
logger.log('尝试使用 service_implementation API 获取品种详情...');
|
|
|
|
|
|
|
|
|
|
// 构建合约符号(使用大写代码,因为 service_implementation API 期望大写)
|
|
|
|
|
const contractSymbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}05`;
|
|
|
|
|
const contractSymbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}03`;
|
|
|
|
|
|
|
|
|
|
// 获取合约详情
|
|
|
|
|
logger.log(`获取合约${contractSymbol}详情...`);
|
|
|
|
|
@ -461,7 +461,7 @@ export const fetchKlineData = async (symbol: string, period: string) => {
|
|
|
|
|
logger.log('尝试使用 service_implementation API 获取K线数据...');
|
|
|
|
|
|
|
|
|
|
// 构建合约符号(使用大写代码,因为 service_implementation API 期望大写)
|
|
|
|
|
const contractSymbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}05`;
|
|
|
|
|
const contractSymbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}3`;
|
|
|
|
|
|
|
|
|
|
// 转换周期格式
|
|
|
|
|
let duration = period;
|
|
|
|
|
@ -517,57 +517,57 @@ export const fetchKlineData = async (symbol: string, period: string) => {
|
|
|
|
|
// service_implementation API 失败,尝试使用其他数据源
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取数据源配置
|
|
|
|
|
const dataSourceConfig = getDataSourceConfig();
|
|
|
|
|
|
|
|
|
|
// 检查是否有可用的数据源
|
|
|
|
|
const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled;
|
|
|
|
|
if (!hasAvailableDataSource) {
|
|
|
|
|
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 尝试使用TQSDK数据源
|
|
|
|
|
if (dataSourceConfig.tqsdk?.enabled) {
|
|
|
|
|
try {
|
|
|
|
|
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
|
|
|
|
|
|
|
|
|
|
// 构建合约符号(使用小写代码,因为TQAPI期望小写)
|
|
|
|
|
const contractSymbol = `${future.exchange}.${future.code.toLowerCase()}${new Date().getFullYear().toString().slice(-2)}05`;
|
|
|
|
|
|
|
|
|
|
// 获取K线数据
|
|
|
|
|
const klineData = await dataSource.getKlineData(contractSymbol, period, 30);
|
|
|
|
|
|
|
|
|
|
// 转换为前端需要的格式
|
|
|
|
|
return klineData.map(item => ({
|
|
|
|
|
timestamp: item.datetime / 1000000000, // 转换为秒
|
|
|
|
|
open: item.open,
|
|
|
|
|
high: item.high,
|
|
|
|
|
low: item.low,
|
|
|
|
|
close: item.close,
|
|
|
|
|
volume: item.volume
|
|
|
|
|
}));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('TQSDK数据源获取失败:', error);
|
|
|
|
|
// TQSDK数据源失败,尝试使用测试数据源
|
|
|
|
|
if (dataSourceConfig.test?.enabled) {
|
|
|
|
|
logger.log('切换到测试数据源');
|
|
|
|
|
// 启用了测试数据源,使用测试数据
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
|
|
|
return generateKlineData(30);
|
|
|
|
|
} else {
|
|
|
|
|
// 未启用测试数据源,返回友好的错误提示
|
|
|
|
|
throw new Error('获取K线数据失败,所有数据源均不可用');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (dataSourceConfig.test?.enabled) {
|
|
|
|
|
// 直接使用测试数据源
|
|
|
|
|
logger.log('使用测试数据源');
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
|
|
|
return generateKlineData(30);
|
|
|
|
|
} else {
|
|
|
|
|
// 无可用数据源
|
|
|
|
|
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
|
|
|
|
|
}
|
|
|
|
|
// // 获取数据源配置
|
|
|
|
|
// const dataSourceConfig = getDataSourceConfig();
|
|
|
|
|
|
|
|
|
|
// // 检查是否有可用的数据源
|
|
|
|
|
// const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled;
|
|
|
|
|
// if (!hasAvailableDataSource) {
|
|
|
|
|
// throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // 尝试使用TQSDK数据源
|
|
|
|
|
// if (dataSourceConfig.tqsdk?.enabled) {
|
|
|
|
|
// try {
|
|
|
|
|
// const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
|
|
|
|
|
|
|
|
|
|
// // 构建合约符号(使用小写代码,因为TQAPI期望小写)
|
|
|
|
|
// const contractSymbol = `${future.exchange}.${future.code.toLowerCase()}${new Date().getFullYear().toString().slice(-2)}05`;
|
|
|
|
|
|
|
|
|
|
// // 获取K线数据
|
|
|
|
|
// const klineData = await dataSource.getKlineData(contractSymbol, period, 30);
|
|
|
|
|
|
|
|
|
|
// // 转换为前端需要的格式
|
|
|
|
|
// return klineData.map(item => ({
|
|
|
|
|
// timestamp: item.datetime / 1000000000, // 转换为秒
|
|
|
|
|
// open: item.open,
|
|
|
|
|
// high: item.high,
|
|
|
|
|
// low: item.low,
|
|
|
|
|
// close: item.close,
|
|
|
|
|
// volume: item.volume
|
|
|
|
|
// }));
|
|
|
|
|
// } catch (error) {
|
|
|
|
|
// logger.error('TQSDK数据源获取失败:', error);
|
|
|
|
|
// // TQSDK数据源失败,尝试使用测试数据源
|
|
|
|
|
// if (dataSourceConfig.test?.enabled) {
|
|
|
|
|
// logger.log('切换到测试数据源');
|
|
|
|
|
// // 启用了测试数据源,使用测试数据
|
|
|
|
|
// await new Promise(resolve => setTimeout(resolve, 200));
|
|
|
|
|
// return generateKlineData(30);
|
|
|
|
|
// } else {
|
|
|
|
|
// // 未启用测试数据源,返回友好的错误提示
|
|
|
|
|
// throw new Error('获取K线数据失败,所有数据源均不可用');
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// } else if (dataSourceConfig.test?.enabled) {
|
|
|
|
|
// // 直接使用测试数据源
|
|
|
|
|
// logger.log('使用测试数据源');
|
|
|
|
|
// await new Promise(resolve => setTimeout(resolve, 200));
|
|
|
|
|
// return generateKlineData(30);
|
|
|
|
|
// } else {
|
|
|
|
|
// // 无可用数据源
|
|
|
|
|
// throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
|
|
|
|
|
// }
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error(`获取合约${symbol}K线数据失败:`, error);
|
|
|
|
|
// 直接返回友好的错误提示
|
|
|
|
|
@ -610,7 +610,7 @@ export const fetchMarketHotspots = async () => {
|
|
|
|
|
for (const future of contractList) {
|
|
|
|
|
try {
|
|
|
|
|
// 构建合约符号(使用大写代码,因为 service_implementation API 期望大写)
|
|
|
|
|
const symbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}05`;
|
|
|
|
|
const symbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}03`;
|
|
|
|
|
|
|
|
|
|
// 获取合约详情
|
|
|
|
|
logger.log(`获取合约${symbol}详情...`);
|
|
|
|
|
|