/**
* 聚宽到Ptrade策略转换工具
*/
import * as fs from 'fs';
import * as path from 'path';
interface ConversionResult {
convertedCode: string;
changes: Array<{
line: number;
original: string;
converted: string;
reason: string;
}>;
warnings: string[];
success: boolean;
}
export const convertStrategy = {
name: "convert_joinquant_to_ptrade",
description: "将聚宽(JoinQuant)策略代码转换为Ptrade格式。可以直接输入代码字符串,或提供文件路径(支持.py/.txt等格式)。自动识别并转换所有API调用,生成Ptrade可用的.py文件和详细转换报告。",
parameters: {
type: "object",
properties: {
code: {
type: "string",
description: "聚宽策略的完整Python代码(可选,与filepath二选一)"
},
filepath: {
type: "string",
description: "策略文件的路径(支持.py、.txt等格式,可选,与code二选一)"
},
output_dir: {
type: "string",
description: "输出目录路径(可选,默认为当前目录或原文件所在目录)"
}
},
required: []
},
async run(args: { code?: string; filepath?: string; output_dir?: string }) {
try {
let codeContent: string;
let sourceInfo: string;
let originalFilePath: string | undefined;
let originalFileName: string;
// 优先使用filepath,其次使用code
if (args.filepath && args.filepath.trim().length > 0) {
// 从文件读取代码
const filepath = args.filepath.trim();
originalFilePath = filepath;
// 验证文件是否存在
if (!fs.existsSync(filepath)) {
throw new Error(`文件不存在: ${filepath}`);
}
// 验证文件格式
const ext = path.extname(filepath).toLowerCase();
const supportedExts = ['.py', '.txt', '.text', '.code', '.strategy'];
if (!supportedExts.includes(ext)) {
throw new Error(`不支持的文件格式: ${ext}。支持的格式: ${supportedExts.join(', ')}`);
}
// 读取文件内容
try {
codeContent = fs.readFileSync(filepath, 'utf-8');
originalFileName = path.basename(filepath, path.extname(filepath));
sourceInfo = `文件: ${path.basename(filepath)}`;
} catch (error: any) {
throw new Error(`读取文件失败: ${error.message}`);
}
} else if (args.code && args.code.trim().length > 0) {
// 直接使用提供的代码
codeContent = args.code;
originalFileName = 'strategy';
sourceInfo = "直接输入的代码";
} else {
throw new Error("请提供策略代码(code参数)或文件路径(filepath参数)");
}
// 验证代码不为空
if (!codeContent || codeContent.trim().length === 0) {
throw new Error("策略代码不能为空");
}
const result = convertJoinQuantToPtrade(codeContent);
// 确定输出目录和文件名
let outputDir: string;
if (args.output_dir && args.output_dir.trim().length > 0) {
outputDir = args.output_dir.trim();
} else if (originalFilePath) {
// 使用原文件所在目录
outputDir = path.dirname(originalFilePath);
} else {
// 使用当前工作目录
outputDir = process.cwd();
}
// 生成输出文件名
const outputFileName = `${originalFileName}_ptrade.py`;
const outputFilePath = path.join(outputDir, outputFileName);
// 保存转换后的代码到文件
try {
fs.writeFileSync(outputFilePath, result.convertedCode, 'utf-8');
} catch (error: any) {
throw new Error(`保存文件失败: ${error.message}`);
}
// 生成转换报告
let report = `# 🔄 聚宽 → Ptrade 策略转换报告\n\n`;
report += `## 📄 源文件信息\n\n${sourceInfo}\n\n`;
report += `## ✅ 转换状态:${result.success ? "成功" : "失败"}\n\n`;
report += `## 💾 输出文件\n\n`;
report += `**文件路径**: \`${outputFilePath}\`\n`;
report += `**文件名**: \`${outputFileName}\`\n`;
report += `**文件大小**: ${Buffer.byteLength(result.convertedCode, 'utf-8')} 字节\n\n`;
if (result.changes.length > 0) {
report += `## 📝 代码修改详情(共 ${result.changes.length} 处)\n\n`;
result.changes.forEach((change, idx) => {
report += `### 修改 ${idx + 1}:第 ${change.line} 行\n`;
report += `- **原代码**:\`${change.original}\`\n`;
report += `- **新代码**:\`${change.converted}\`\n`;
report += `- **原因**:${change.reason}\n\n`;
});
}
if (result.warnings.length > 0) {
report += `## ⚠️ 注意事项(${result.warnings.length} 项)\n\n`;
result.warnings.forEach((warning, idx) => {
report += `${idx + 1}. ${warning}\n`;
});
report += `\n`;
}
report += `## 🎯 转换后的Ptrade代码(已保存)\n\n`;
report += `代码已成功保存到文件,可直接使用!\n\n`;
report += `如需查看代码内容,打开文件:\`${outputFilePath}\`\n\n`;
// 可选:显示前20行预览
const previewLines = result.convertedCode.split('\n').slice(0, 20);
if (previewLines.length < result.convertedCode.split('\n').length) {
report += `<details>\n<summary>📝 点击查看代码预览(前20行)</summary>\n\n`;
report += `\`\`\`python\n${previewLines.join('\n')}\n... (更多内容请查看文件)\n\`\`\`\n\n`;
report += `</details>\n\n`;
} else {
report += `\`\`\`python\n${result.convertedCode}\n\`\`\`\n\n`;
}
report += `---\n\n`;
report += `### 📋 下一步操作\n\n`;
report += `1. 🎯 **直接使用文件**: 将 \`${outputFileName}\` 上传到Ptrade平台\n`;
report += `2. ⚠️ **检查注意事项**: 查看上方提到的可能需要手动调整的部分\n`;
report += `3. 🧪 **运行回测**: 在Ptrade上验证策略逻辑\n`;
report += `4. 🔧 **调整参数**: 根据回测结果优化参数\n\n`;
report += `💡 提示:转换后的文件已保存在 \`${outputDir}\` 目录下!\n`;
return {
content: [{
type: "text" as const,
text: report
}]
};
} catch (error: any) {
return {
content: [{
type: "text" as const,
text: `❌ 转换失败: ${error.message}\n\n请检查输入的代码格式是否正确。`
}],
isError: true
};
}
}
};
/**
* 核心转换逻辑
*/
function convertJoinQuantToPtrade(code: string): ConversionResult {
const lines = code.split('\n');
const convertedLines: string[] = [];
const changes: ConversionResult['changes'] = [];
const warnings: string[] = [];
// API映射规则 - 按优先级排序,特定模式优先匹配
const apiMappings = [
// ========== 导入模块 ==========
{
pattern: /from\s+jqdata\s+import\s+\*/g,
replacement: 'from ptrade.api import *',
reason: '更新导入模块为Ptrade'
},
{
pattern: /import\s+jqdata/g,
replacement: 'import ptrade',
reason: '更新导入模块为Ptrade'
},
// ========== 日志函数(关键差异)==========
// 必须在其他模式之前匹配,确保正确转换
{
pattern: /\blog\.info\s*\(/g,
replacement: 'log(',
reason: 'JoinQuant使用log.info(),Ptrade使用log()'
},
{
pattern: /\blog\.debug\s*\(/g,
replacement: 'log(',
reason: 'JoinQuant使用log.debug(),Ptrade使用log()'
},
{
pattern: /\blog\.warn\s*\(/g,
replacement: 'log(',
reason: 'JoinQuant使用log.warn(),Ptrade使用log()'
},
{
pattern: /\blog\.error\s*\(/g,
replacement: 'log(',
reason: 'JoinQuant使用log.error(),Ptrade使用log()'
},
// ========== Context对象持仓访问(关键差异)==========
{
pattern: /context\.portfolio\.positions\[([^\]]+)\]/g,
replacement: 'get_position($1)',
reason: 'JoinQuant使用context.portfolio.positions[stock],Ptrade使用get_position(stock)'
},
{
pattern: /context\.portfolio\.positions/g,
replacement: 'get_positions()',
reason: 'JoinQuant使用context.portfolio.positions,Ptrade使用get_positions()'
},
// ========== 历史数据获取 ==========
{
pattern: /\battribute_history\s*\(/g,
replacement: 'get_history(',
reason: 'JoinQuant使用attribute_history(),Ptrade使用get_history()'
},
{
pattern: /\bhistory\s*\(/g,
replacement: 'get_history(',
reason: 'JoinQuant使用history(),Ptrade使用get_history()'
},
// ========== 股票池和指数成分股 ==========
{
pattern: /\bget_index_stocks\s*\(/g,
replacement: 'get_index_stocks(',
reason: '获取指数成分股(Ptrade兼容,需确认参数)'
},
{
pattern: /\bget_all_securities\s*\(/g,
replacement: 'get_market_list(',
reason: 'JoinQuant使用get_all_securities(),Ptrade使用get_market_list()'
},
// ========== 交易相关函数 ==========
{
pattern: /\border_target_percent\s*\(/g,
replacement: 'order_target_value(',
reason: 'JoinQuant的order_target_percent在Ptrade中使用order_target_value实现'
},
{
pattern: /\border\s*\(/g,
replacement: 'order(',
reason: '按数量下单(Ptrade兼容)'
},
{
pattern: /\border_target\s*\(/g,
replacement: 'order_target(',
reason: '目标持仓下单(Ptrade兼容)'
},
{
pattern: /\border_value\s*\(/g,
replacement: 'order_value(',
reason: '按金额下单(Ptrade兼容)'
},
{
pattern: /\border_target_value\s*\(/g,
replacement: 'order_target_value(',
reason: '目标金额下单(Ptrade兼容)'
},
// ========== 回测设置 ==========
{
pattern: /\bset_benchmark\s*\(/g,
replacement: 'set_benchmark(',
reason: '设置基准(Ptrade兼容)'
},
{
pattern: /\bset_option\s*\(/g,
replacement: 'set_option(',
reason: '设置选项(Ptrade兼容)'
},
{
pattern: /\bset_commission\s*\(/g,
replacement: 'set_commission(',
reason: '设置手续费(Ptrade兼容)'
},
{
pattern: /\bset_slippage\s*\(/g,
replacement: 'set_slippage(',
reason: '设置滑点(Ptrade兼容)'
},
{
pattern: /\bset_order_cost\s*\(/g,
replacement: 'set_commission(',
reason: 'JoinQuant使用set_order_cost(),Ptrade使用set_commission()'
},
// ========== 数据获取函数 ==========
{
pattern: /\bget_price\s*\(/g,
replacement: 'get_price(',
reason: '获取历史价格数据(Ptrade兼容)'
},
{
pattern: /\bget_fundamentals\s*\(/g,
replacement: 'get_fundamentals(',
reason: '获取财务数据(Ptrade兼容)'
},
{
pattern: /\bget_current_data\s*\(/g,
replacement: 'get_snapshot(',
reason: 'JoinQuant使用get_current_data(),Ptrade使用get_snapshot()'
},
// ========== 定时任务 ==========
{
pattern: /\brun_daily\s*\(/g,
replacement: 'run_daily(',
reason: '按日运行(Ptrade兼容)'
},
{
pattern: /\brun_weekly\s*\(/g,
replacement: 'run_daily(',
reason: 'JoinQuant的run_weekly在Ptrade中用run_daily配合判断实现'
},
{
pattern: /\brun_monthly\s*\(/g,
replacement: 'run_daily(',
reason: 'JoinQuant的run_monthly在Ptrade中用run_daily配合判断实现'
},
// ========== Context对象其他属性 ==========
{
pattern: /\bcontext\.current_dt\b/g,
replacement: 'context.current_dt',
reason: '当前时间(Ptrade兼容)'
},
{
pattern: /\bcontext\.portfolio\.total_value\b/g,
replacement: 'context.portfolio.total_value',
reason: '总资产(Ptrade兼容)'
},
{
pattern: /\bcontext\.portfolio\.available_cash\b/g,
replacement: 'context.portfolio.available_cash',
reason: '可用资金(Ptrade兼容)'
},
{
pattern: /\bcontext\.portfolio\.market_value\b/g,
replacement: 'context.portfolio.market_value',
reason: '持仓市值(Ptrade兼容)'
},
{
pattern: /\bcontext\.portfolio\b/g,
replacement: 'context.portfolio',
reason: '账户信息(Ptrade兼容)'
},
{
pattern: /\bcontext\.run_info\b/g,
replacement: 'context.run_info',
reason: '运行信息(Ptrade兼容)'
}
];
// 逐行转换
lines.forEach((line, index) => {
let convertedLine = line;
let lineChanged = false;
apiMappings.forEach((mapping) => {
if (mapping.pattern.test(convertedLine)) {
const original = convertedLine;
convertedLine = convertedLine.replace(mapping.pattern, mapping.replacement);
if (original !== convertedLine) {
lineChanged = true;
changes.push({
line: index + 1,
original: original.trim(),
converted: convertedLine.trim(),
reason: mapping.reason
});
}
}
});
convertedLines.push(convertedLine);
});
// 检测潜在的兼容性问题和需要注意的部分
const codeText = code.toLowerCase();
const convertedText = convertedLines.join('\n').toLowerCase();
// 检查run_weekly和run_monthly的转换
if (codeText.includes('run_weekly') || codeText.includes('run_monthly')) {
warnings.push('⚠️ 定时任务:run_weekly/run_monthly已转换为run_daily,需要在函数内添加日期判断逻辑');
}
// 检查持仓访问的转换
if (codeText.includes('context.portfolio.positions')) {
warnings.push('✅ 持仓访问:已将context.portfolio.positions转换为get_position()/get_positions()函数调用');
}
// 检查日志函数的转换
if (codeText.includes('log.info') || codeText.includes('log.warn') || codeText.includes('log.error') || codeText.includes('log.debug')) {
warnings.push('✅ 日志函数:已将log.info()/warn()/error()/debug()统一转换为log()');
}
// 检查历史数据函数
if (codeText.includes('attribute_history') || codeText.includes('history(')) {
warnings.push('✅ 历史数据:已将attribute_history()/history()转换为get_history()');
}
// 检查get_current_data
if (codeText.includes('get_current_data')) {
warnings.push('✅ 行情快照:已将get_current_data()转换为get_snapshot()');
}
// 检查get_all_securities
if (codeText.includes('get_all_securities')) {
warnings.push('✅ 证券列表:已将get_all_securities()转换为get_market_list(),请确认参数格式');
}
// 检查order_target_percent
if (codeText.includes('order_target_percent')) {
warnings.push('⚠️ 按比例下单:order_target_percent已转换为order_target_value,需要手动计算目标金额');
}
// 检查set_order_cost
if (codeText.includes('set_order_cost')) {
warnings.push('✅ 手续费设置:已将set_order_cost()转换为set_commission()');
}
// 检查get_fundamentals的使用
if (codeText.includes('get_fundamentals')) {
warnings.push('📊 财务数据:get_fundamentals()在Ptrade中支持,但查询语法可能需要调整');
}
// 检查get_index_stocks
if (codeText.includes('get_index_stocks')) {
warnings.push('📋 指数成分股:get_index_stocks()在Ptrade中支持,请确认参数格式');
}
// 检查时间参数格式
if (codeText.includes('time=') || codeText.includes('date=')) {
warnings.push('⏰ 时间参数:请确认日期时间参数格式符合Ptrade要求(建议使用字符串格式)');
}
// 检查数据频率参数
if (codeText.includes("'1d'") || codeText.includes('"1d"') || codeText.includes("'daily'") || codeText.includes('"daily"')) {
warnings.push('📈 数据频率:Ptrade支持多种频率(1d/1w/1m/5m/15m/30m/60m等),请确认参数正确');
}
// 总结性提示
if (changes.length === 0) {
warnings.push('ℹ️ 未检测到需要转换的API调用,代码可能已经是Ptrade格式或使用了自定义函数');
} else {
warnings.push(`🎯 转换完成:共修改了${changes.length}处代码,请仔细检查转换结果`);
warnings.push('🧪 建议:在Ptrade平台上进行回测验证,确保策略逻辑正确');
}
return {
convertedCode: convertedLines.join('\n'),
changes,
warnings,
success: true
};
}