skills/quant-research/index.ts · 量化投研核心逻辑
import { Skill } from '@openclaw/core';
import { WindClient } from './clients/wind';
import { TushareClient } from './clients/tushare';
import { FactorEngine } from './engine/factor';
import { BacktestEngine } from './engine/backtest';
interface ResearchRequest {
type: 'report' | 'factor' | 'backtest' | 'stock_pick';
symbols?: string[];
startDate?: string;
endDate?: string;
parameters?: Record<string, any>;
}
export const quantResearchSkill = new Skill({
name: 'quant-research',
description: '量化投研服务:研报生成、因子分析、策略回测、选股择时',
config: {
dataProviders: ['wind', 'tushare', 'announcement'],
factorLibrary: './factors',
backtestConfig: {
initialCapital: 1000000,
commission: 0.0003,
slippage: 0.001,
benchmark: '000300.SH'
}
},
onStart: async (context) => {
context.logger.info('🚀 启动量化投研服务...');
const wind = new WindClient(process.env.WIND_API_KEY);
const tushare = new TushareClient(process.env.TUSHARE_TOKEN);
const factorEngine = new FactorEngine();
const backtestEngine = new BacktestEngine(context.config.backtestConfig);
context.on('research_request', async (request: ResearchRequest) => {
try {
switch (request.type) {
case 'report':
return await generateReport(request, wind, tushare);
case 'factor':
return await analyzeFactors(request, factorEngine);
case 'backtest':
return await runBacktest(request, backtestEngine);
case 'stock_pick':
return await pickStocks(request, factorEngine);
default:
throw new Error(`未知任务类型:${request.type}`);
}
} catch (error) {
context.logger.error('❌ 投研任务失败:', error);
await context.notify('wechat', {
message: `⚠️ 投研任务异常:${error.message}`,
level: 'high'
});
}
});
},
onStop: async (context) => {
context.logger.info('🛑 停止量化投研服务');
}
});
async function generateReport(
request: ResearchRequest,
wind: WindClient,
tushare: TushareClient
) {
const reports = [];
for (const symbol of request.symbols || []) {
const financials = await wind.getFinancials(symbol);
const prices = await tushare.getDailyPrices(symbol, request.startDate, request.endDate);
const announcements = await wind.getAnnouncements(symbol, request.startDate, request.endDate);
const summary = await context.llm.generate(`
请根据以下数据分析${symbol}的投资价值:
财务数据:${JSON.stringify(financials)}
行情数据:${prices.length} 个交易日,涨跌幅 ${(prices[prices.length-1].close / prices[0].close - 1).toFixed(2)}%
重要公告:${announcements.map(a => a.title).join('; ')}
请从以下维度分析:
1. 财务状况(营收/净利润/ROE/负债率)
2. 估值水平(PE/PB/PS,与历史/行业对比)
3. 技术面(趋势/支撑阻力/成交量)
4. 催化剂与风险因素
5. 投资建议(买入/增持/中性/减持)
`);
reports.push({
symbol,
generatedAt: new Date().toISOString(),
summary,
data: { financials, prices, announcements }
});
}
return reports;
}
async function analyzeFactors(
request: ResearchRequest,
factorEngine: FactorEngine
) {
const { factors, startDate, endDate } = request.parameters;
const factorValues = await factorEngine.calculate(factors, startDate, endDate);
const icResults = await factorEngine.testIC(factorValues);
const quantileReturns = await factorEngine.quantileBacktest(factorValues);
const decayAnalysis = await factorEngine.analyzeDecay(factorValues);
return {
factors,
period: { startDate, endDate },
icStats: icResults,
quantileReturns,
decayAnalysis,
recommendation: generateFactorRecommendation(icResults)
};
}
async function runBacktest(
request: ResearchRequest,
backtestEngine: BacktestEngine
) {
const { strategy, parameters, startDate, endDate } = request.parameters;
const result = await backtestEngine.run(strategy, {
startDate,
endDate,
...parameters
});
const metrics = {
totalReturn: calculateTotalReturn(result.nav),
annualizedReturn: calculateAnnualizedReturn(result.nav),
sharpeRatio: calculateSharpe(result.returns),
maxDrawdown: calculateMaxDrawdown(result.nav),
winRate: calculateWinRate(result.trades),
profitFactor: calculateProfitFactor(result.trades)
};
const attribution = await backtestEngine.attribute(result);
return {
strategy,
period: { startDate, endDate },
nav: result.nav,
trades: result.trades,
metrics,
attribution,
charts: await generateCharts(result)
};
}
config/quant-research.json · 参数配置
{
"dataSources": {
"wind": {
"enabled": true,
"apiKey": "${WIND_API_KEY}",
"endpoints": {
"financials": "/api/financials",
"prices": "/api/prices",
"announcements": "/api/announcements"
}
},
"tushare": {
"enabled": true,
"token": "${TUSHARE_TOKEN}",
"pro": true
}
},
"factors": {
"value": ["PE", "PB", "PS", "PCF"],
"growth": ["revenue_growth", "profit_growth", "roe_change"],
"momentum": ["return_1m", "return_3m", "return_6m", "rsi"],
"quality": ["roe", "roa", "gross_margin", "asset_turnover"],
"sentiment": ["analyst_upgrade", "news_sentiment", "social_media"]
},
"backtest": {
"initialCapital": 1000000,
"commission": 0.0003,
"slippage": 0.001,
"benchmark": "000300.SH",
"riskFreeRate": 0.03,
"adjustment": "post"
},
"reporting": {
"formats": ["pdf", "html", "excel"],
"templates": "./templates",
"delivery": ["email", "wechat", "dingtalk"]
}
}