parent
fc402d80d9
commit
05604c9d71
@ -0,0 +1,101 @@
|
||||
{
|
||||
"database": {
|
||||
"mongoDB": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 10000,
|
||||
"database": "aaa",
|
||||
"username": "aaa",
|
||||
"password": "aaaa",
|
||||
"authSource": "aaa",
|
||||
"ssl": false,
|
||||
"enabled": true
|
||||
},
|
||||
"postgreSQL": {
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"database": "alpha-futures",
|
||||
"username": "postgres",
|
||||
"password": "password",
|
||||
"ssl": false,
|
||||
"enabled": true
|
||||
},
|
||||
"redis": {
|
||||
"host": "localhost",
|
||||
"port": 6379,
|
||||
"password": "",
|
||||
"db": 0,
|
||||
"enabled": true
|
||||
},
|
||||
"influxDB": {
|
||||
"host": "localhost",
|
||||
"port": 8086,
|
||||
"database": "alpha-futures",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"ssl": false,
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
"port": 3006,
|
||||
"host": "0.0.0.0",
|
||||
"environment": "development",
|
||||
"debug": true,
|
||||
"timeout": 30000,
|
||||
"maxBodySize": "10mb"
|
||||
},
|
||||
"security": {
|
||||
"jwtSecret": "your-secret-key",
|
||||
"jwtExpiresIn": "7d",
|
||||
"rateLimit": {
|
||||
"windowMs": 60000,
|
||||
"max": 120
|
||||
},
|
||||
"cors": {
|
||||
"origin": "*",
|
||||
"methods": [
|
||||
"GET",
|
||||
"POST",
|
||||
"PUT",
|
||||
"DELETE",
|
||||
"OPTIONS"
|
||||
],
|
||||
"allowedHeaders": [
|
||||
"Content-Type",
|
||||
"Authorization"
|
||||
]
|
||||
}
|
||||
},
|
||||
"dataSource": {
|
||||
"test": {
|
||||
"enabled": true,
|
||||
"timeout": 10000,
|
||||
"retries": 3,
|
||||
"refreshInterval": 60000
|
||||
},
|
||||
"tqsdk": {
|
||||
"enabled": true,
|
||||
"username": "windsdreamer",
|
||||
"password": "1qazse42W3",
|
||||
"timeout": 10000,
|
||||
"retries": 10,
|
||||
"maxConnections": 20
|
||||
},
|
||||
"wind": {
|
||||
"enabled": false,
|
||||
"apiKey": "",
|
||||
"apiSecret": "",
|
||||
"url": "https://api.wind.com.cn",
|
||||
"timeout": 30000,
|
||||
"retries": 3
|
||||
},
|
||||
"sina": {
|
||||
"enabled": false,
|
||||
"url": "https://finance.sina.com.cn",
|
||||
"timeout": 10000,
|
||||
"retries": 3,
|
||||
"refreshInterval": 60000
|
||||
},
|
||||
"defaultDataSource": "tqsdk"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
// 数据源抽象接口
|
||||
export interface DataSource {
|
||||
// 获取所有合约列表
|
||||
getContractList(): Promise<any[]>;
|
||||
|
||||
// 获取单个合约详情
|
||||
getContractDetail(symbol: string): Promise<any>;
|
||||
|
||||
// 获取K线数据
|
||||
getKlineData(symbol: string, period: string, count: number): Promise<any[]>;
|
||||
|
||||
// 获取实时行情数据
|
||||
getTickData(symbol: string): Promise<any>;
|
||||
|
||||
// 获取市场概览
|
||||
getMarketOverview(): Promise<any[]>;
|
||||
|
||||
// 获取历史成交数据
|
||||
getHistoricalTrades(symbol: string, start: number, end: number): Promise<any[]>;
|
||||
|
||||
// 初始化数据源
|
||||
initialize(): Promise<boolean>;
|
||||
|
||||
// 关闭数据源连接
|
||||
close(): Promise<void>;
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
// 数据源工厂类
|
||||
import { DataSource } from './DataSource';
|
||||
import { TQDataSource } from './TQDataSource';
|
||||
|
||||
// 数据源类型
|
||||
export enum DataSourceType {
|
||||
TQSDK = 'tqsdk',
|
||||
MOCK = 'mock',
|
||||
WIND = 'wind',
|
||||
SINA = 'sina',
|
||||
TEST = 'test'
|
||||
}
|
||||
|
||||
export class DataSourceFactory {
|
||||
private static dataSources: Map<DataSourceType, DataSource> = new Map();
|
||||
|
||||
// 获取数据源实例
|
||||
static async getDataSource(type: DataSourceType = DataSourceType.TQSDK, config: any = {}): Promise<DataSource> {
|
||||
if (!this.dataSources.has(type)) {
|
||||
let dataSource: DataSource;
|
||||
|
||||
switch (type) {
|
||||
case DataSourceType.TQSDK:
|
||||
dataSource = new TQDataSource(config.tqsdk || {});
|
||||
break;
|
||||
case DataSourceType.MOCK:
|
||||
case DataSourceType.TEST:
|
||||
// 导入模拟数据源
|
||||
const { MockDataSource } = await import('./MockDataSource');
|
||||
dataSource = new MockDataSource();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`不支持的数据源类型: ${type}`);
|
||||
}
|
||||
|
||||
// 初始化数据源
|
||||
const initialized = await dataSource.initialize();
|
||||
if (!initialized) {
|
||||
throw new Error(`数据源${type}初始化失败`);
|
||||
}
|
||||
|
||||
this.dataSources.set(type, dataSource);
|
||||
}
|
||||
|
||||
return this.dataSources.get(type)!;
|
||||
}
|
||||
|
||||
// 关闭所有数据源
|
||||
static async closeAllDataSources(): Promise<void> {
|
||||
for (const [type, dataSource] of this.dataSources) {
|
||||
try {
|
||||
await dataSource.close();
|
||||
console.log(`数据源${type}已关闭`);
|
||||
} catch (error) {
|
||||
console.error(`关闭数据源${type}失败:`, error);
|
||||
}
|
||||
}
|
||||
this.dataSources.clear();
|
||||
}
|
||||
|
||||
// 切换数据源
|
||||
static async switchDataSource(type: DataSourceType): Promise<DataSource> {
|
||||
// 关闭当前数据源
|
||||
await this.closeAllDataSources();
|
||||
// 获取新数据源
|
||||
return this.getDataSource(type);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,199 @@
|
||||
// 模拟数据源实现
|
||||
import { DataSource } from './DataSource';
|
||||
import { futuresList, generateFuturesOverview, generateFutureData, generateKlineData, riskAlerts } from '../../utils/mockData';
|
||||
|
||||
export class MockDataSource implements DataSource {
|
||||
private initialized: boolean = false;
|
||||
|
||||
async initialize(): Promise<boolean> {
|
||||
// 模拟初始化
|
||||
this.initialized = true;
|
||||
console.log('模拟数据源初始化成功');
|
||||
return true;
|
||||
}
|
||||
|
||||
async getContractList(): Promise<any[]> {
|
||||
if (!this.initialized) {
|
||||
throw new Error('模拟数据源未初始化');
|
||||
}
|
||||
|
||||
// 返回模拟合约列表
|
||||
return futuresList.map(item => ({
|
||||
symbol: item.code,
|
||||
name: item.name,
|
||||
exchange: this.getExchangeBySymbol(item.code),
|
||||
product_class: 'futures',
|
||||
price_tick: 0.01,
|
||||
size: 1,
|
||||
margin_rate: 0.05
|
||||
}));
|
||||
}
|
||||
|
||||
async getContractDetail(symbol: string): Promise<any> {
|
||||
if (!this.initialized) {
|
||||
throw new Error('模拟数据源未初始化');
|
||||
}
|
||||
|
||||
// 返回模拟合约详情
|
||||
const future = futuresList.find(item => item.code === symbol);
|
||||
if (!future) {
|
||||
throw new Error(`合约${symbol}不存在`);
|
||||
}
|
||||
|
||||
return {
|
||||
symbol: future.code,
|
||||
name: future.name,
|
||||
exchange: this.getExchangeBySymbol(future.code),
|
||||
product_class: 'futures',
|
||||
price_tick: 0.01,
|
||||
size: 1,
|
||||
margin_rate: 0.05,
|
||||
delivery_month: '202412',
|
||||
trading_hours: '09:00-11:30, 13:30-15:00, 21:00-02:30',
|
||||
pre_close: 2000,
|
||||
open: 2005,
|
||||
high: 2010,
|
||||
low: 1995,
|
||||
last_price: 2003,
|
||||
volume: 10000,
|
||||
open_interest: 50000
|
||||
};
|
||||
}
|
||||
|
||||
async getKlineData(symbol: string, period: string, count: number): Promise<any[]> {
|
||||
if (!this.initialized) {
|
||||
throw new Error('模拟数据源未初始化');
|
||||
}
|
||||
|
||||
// 返回模拟K线数据
|
||||
const klineData = generateKlineData(count);
|
||||
return klineData.map(item => ({
|
||||
datetime: item.time * 1000000000, // 转换为纳秒
|
||||
open: item.open,
|
||||
high: item.high,
|
||||
low: item.low,
|
||||
close: item.close,
|
||||
volume: item.volume,
|
||||
open_interest: Math.floor(Math.random() * 100000) + 50000
|
||||
}));
|
||||
}
|
||||
|
||||
async getTickData(symbol: string): Promise<any> {
|
||||
if (!this.initialized) {
|
||||
throw new Error('模拟数据源未初始化');
|
||||
}
|
||||
|
||||
// 返回模拟实时行情数据
|
||||
const future = futuresList.find(item => item.code === symbol);
|
||||
if (!future) {
|
||||
throw new Error(`合约${symbol}不存在`);
|
||||
}
|
||||
|
||||
const pre_close = 2000;
|
||||
const last_price = pre_close + (Math.random() * 20 - 10);
|
||||
const price_change = last_price - pre_close;
|
||||
|
||||
return {
|
||||
datetime: Date.now() * 1000000, // 转换为纳秒
|
||||
last_price: +last_price.toFixed(2),
|
||||
pre_close,
|
||||
open: pre_close + (Math.random() * 10 - 5),
|
||||
high: Math.max(last_price, pre_close + (Math.random() * 15 - 5)),
|
||||
low: Math.min(last_price, pre_close + (Math.random() * 15 - 10)),
|
||||
volume: Math.floor(Math.random() * 10000) + 5000,
|
||||
open_interest: Math.floor(Math.random() * 100000) + 50000,
|
||||
price_change: +price_change.toFixed(2),
|
||||
bid_price1: last_price - 0.01,
|
||||
bid_volume1: Math.floor(Math.random() * 100) + 50,
|
||||
ask_price1: last_price + 0.01,
|
||||
ask_volume1: Math.floor(Math.random() * 100) + 50
|
||||
};
|
||||
}
|
||||
|
||||
async getMarketOverview(): Promise<any[]> {
|
||||
if (!this.initialized) {
|
||||
throw new Error('模拟数据源未初始化');
|
||||
}
|
||||
|
||||
// 返回模拟市场概览
|
||||
const overview = generateFuturesOverview();
|
||||
return overview.map(item => ({
|
||||
symbol: item.code,
|
||||
name: item.name,
|
||||
price: item.currentPrice,
|
||||
change: item.changePercent,
|
||||
change_percent: item.changePercent,
|
||||
volume: Math.floor(Math.random() * 100000) + 50000,
|
||||
open_interest: Math.floor(Math.random() * 1000000) + 500000
|
||||
}));
|
||||
}
|
||||
|
||||
async getHistoricalTrades(symbol: string, start: number, end: number): Promise<any[]> {
|
||||
if (!this.initialized) {
|
||||
throw new Error('模拟数据源未初始化');
|
||||
}
|
||||
|
||||
// 返回模拟历史成交数据
|
||||
const trades = [];
|
||||
const count = 100; // 生成100条模拟数据
|
||||
const basePrice = 2000;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const price = basePrice + (Math.random() * 10 - 5);
|
||||
const volume = Math.floor(Math.random() * 100) + 10;
|
||||
const timestamp = start + Math.floor((end - start) * (i / count));
|
||||
|
||||
trades.push({
|
||||
datetime: timestamp * 1000000, // 转换为纳秒
|
||||
price: +price.toFixed(2),
|
||||
volume,
|
||||
direction: Math.random() > 0.5 ? 'BUY' : 'SELL'
|
||||
});
|
||||
}
|
||||
|
||||
return trades;
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
// 模拟关闭
|
||||
this.initialized = false;
|
||||
console.log('模拟数据源已关闭');
|
||||
}
|
||||
|
||||
// 根据合约代码获取交易所
|
||||
private getExchangeBySymbol(symbol: string): string {
|
||||
// 简单的交易所映射
|
||||
const exchangeMap: Record<string, string> = {
|
||||
'AU': 'SHFE',
|
||||
'AG': 'SHFE',
|
||||
'CU': 'SHFE',
|
||||
'NI': 'SHFE',
|
||||
'SN': 'SHFE',
|
||||
'AL': 'SHFE',
|
||||
'ZN': 'SHFE',
|
||||
'FG': 'CZCE',
|
||||
'SJS': 'CZCE',
|
||||
'SCA': 'CZCE',
|
||||
'JM': 'DCE',
|
||||
'RB': 'SHFE',
|
||||
'ALO': 'SHFE',
|
||||
'MA': 'DCE',
|
||||
'PVC': 'DCE',
|
||||
'FU': 'SHFE',
|
||||
'SC': 'INE',
|
||||
'L': 'DCE',
|
||||
'NR': 'SHFE',
|
||||
'BU': 'SHFE',
|
||||
'LU': 'INE',
|
||||
'P': 'DCE',
|
||||
'LC': 'SHFE',
|
||||
'SI': 'SHFE',
|
||||
'PGS': 'SHFE',
|
||||
'IC': 'CFFEX',
|
||||
'IM': 'CFFEX',
|
||||
'IH': 'CFFEX'
|
||||
};
|
||||
|
||||
return exchangeMap[symbol] || 'SHFE';
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue