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