本类库提供了一些方法,用于帮助用户评估交易策略的表现。下面通过实例说明如何使用这些方法,并解释返回的指标及其含义。
在使用本类库之前,需要调用 $.InitData()
方法来初始化数据,确保所有必要的参数都被正确设置。
$.InitData(); // 初始化参数
示例中展示了如何使用 exchange.CreateOrder()
创建一个交易订单。CreateOrder
方法可以用来模拟买入和卖出操作。下面是一个基本的交易流程:
CreateOrder
方法进行买入操作。// 创建买入订单
exchange.CreateOrder('rb888', 'buy', -1, 30); // 买入 rb888 合约
exchange.CreateOrder('hc888', 'sell', -1, 1); // 买入 hc888 合约
// 等待 1 天后
Sleep(1000 * 60 * 60 * 24);
// 创建平仓订单
exchange.CreateOrder('rb888', 'closebuy', -1, 30); // 平仓 rb888 合约
exchange.CreateOrder('hc888', 'closesell', -1, 1); // 平仓 hc888 合约
$.WinLossCount()
方法用于计算并返回策略的整体盈亏比。你可以调用此方法来获取所有合约的统计数据,也可以传入特定的合约名称,计算某一合约的盈亏比。
// 获取所有合约的盈亏比
const overallWinLoss = $.WinLossCount();
Log('整体盈亏比:');
Log(`盈亏比:${overallWinLoss[0]} (盈利总额与亏损总额的比值)`);
Log(`胜率:${overallWinLoss[1]} (盈利交易占总交易的百分比)`);
Log(`总盈利:${overallWinLoss[2]} (所有盈利交易的总和)`);
Log(`总亏损:${overallWinLoss[3]} (所有亏损交易的总和)`);
// 获取指定合约(如rb888)的盈亏比
const specificWinLoss = $.WinLossCount(['rb']);
Log('需求盈亏比(rb 合约):');
Log(`盈亏比:${specificWinLoss[0]} (盈利总额与亏损总额的比值)`);
Log(`胜率:${specificWinLoss[1]} (盈利交易占总交易的百分比)`);
Log(`总盈利:${specificWinLoss[2]} (所有盈利交易的总和)`);
Log(`总亏损:${specificWinLoss[3]} (所有亏损交易的总和)`);
$.IndexCount()
方法返回多个策略评价指标,可以帮助用户了解策略的整体表现。这些指标包括起始时间、总资产、累计收益等。
const indexMetrics = $.IndexCount();
Log('策略评价指标:');
Log(`起始时间:${indexMetrics[0]} (策略开始运行的时间)`);
Log(`总资产:${indexMetrics[1]} (当前账户总资产)`);
Log(`运行天数:${indexMetrics[2]} (策略运行的天数)`);
Log(`累计收益率:${indexMetrics[3]} (从策略开始到现在的总收益率)`);
Log(`年化收益率:${indexMetrics[4]} (按照年化标准计算的收益率)`);
Log(`波动率:${indexMetrics[5]} (资产收益的标准差,反映风险程度)`);
Log(`夏普比率:${indexMetrics[6]} (收益与风险的比值)`);
Log(`索提诺比率:${indexMetrics[7]} (基于下行风险的收益与风险比值)`);
Log(`卡玛比率:${indexMetrics[8]} (年化收益率与最大回撤的比值)`);
Log(`最大回撤:${indexMetrics[9]} (历史资产的最大回撤比例)`);
Log(`最大回撤时间:${indexMetrics[10]} (发生最大回撤的时间点)`);
Log(`最大资产时间:${indexMetrics[11]} (账户达到最大资产的时间点)`);
Log(`最大回撤起始时间:${indexMetrics[12]} (最大回撤开始的时间点)`);
winLossRatio
):盈利总额与亏损总额的比值,衡量了整体交易的盈利效率。盈亏比大于 1 表示盈利交易的总额大于亏损。winRate
):盈利交易占所有交易的百分比,反映了交易系统的成功率。胜率较高表示系统稳定。totalProfit
):所有盈利交易的总和,反映了在所有盈利交易中获得的收益。totalLoss
):所有亏损交易的总和,反映了在所有亏损交易中遭受的损失。startTime
):策略开始运行的时间,帮助评估策略的历史背景。totalAssets
):当前账户的总资产,用于衡量账户的当前规模。runningDays
):策略已运行的天数,反映了策略的运行时间。totalReturns
):从策略开始到现在的总收益率,用来评估策略的长期表现。annualizedReturns
):按照年化标准计算的收益率,有助于对比不同策略的长期回报。volatility
):资产收益的标准差,反映了策略的风险程度,波动率越大表示风险越高。sharpeRatio
):收益与风险的比值,夏普比率越高表示单位风险获得的收益越多。sortinoRatio
):基于下行风险的收益与风险比值,类似于夏普比率,但专注于负向波动的影响。calmarRatio
):年化收益率与最大回撤的比值,反映了策略的回报与风险的关系,卡玛比率越高表示策略越有效。maxDrawdown
):历史资产的最大回撤比例,衡量账户最大亏损程度。maxDrawdownTime
):发生最大回撤的时间点,帮助评估策略的耐用性。maxAssetsTime
):账户达到最大资产的时间点,表明策略的顶峰表现。maxDrawdownStartTime
):最大回撤开始的时间点,有助于了解策略的回撤特征。使用本类库,您可以:
WinLossCount()
方法获取策略的盈亏比、胜率以及盈利和亏损的具体数值。IndexCount()
方法获取策略的综合评价指标,如总资产、年化收益率、夏普比率、最大回撤等。这些指标将帮助您深入了解策略的表现,并做出进一步优化。
var openContracts = {}; // 追踪所有未平仓的合约,按 baseSymbol 存储 var profitLossHistory = {}; // 追踪每个合约的盈亏历史 var processedOrderIds = new Set(); // 记录已处理的订单 ID var winLossCount = {}; // 按 baseSymbol 统计胜败和总交易数量 var symbolList = []; // 追踪所有出现过的 baseSymbol $.WinLossCount = function(symbols = null) { while (!exchange.IO("status")) { Sleep(3000); } // 获取所有历史订单 var totalOrders = exchange.GetHistoryOrders(); // 检查 totalOrders 是否为 null 或空数组 if (!totalOrders || totalOrders.length === 0) { totalOrders = []; } return updateContracts(totalOrders, symbols, openContracts, profitLossHistory, processedOrderIds, winLossCount, symbolList); }; function updateContracts(newOrders, symbols, openContracts, profitLossHistory, processedOrderIds, winLossCount, symbolList) { // 遍历所有订单 for (let order of newOrders) { let orderId = order.Id; // 跳过已处理订单 if (processedOrderIds.has(orderId)) continue; // 跳过未成交订单 if (order.Status !== 1) continue; let baseSymbol = order.Symbol.replace(/\d+/g, ''); // 如果当前 baseSymbol 尚未加入 symbolList,添加到 symbolList 中 if (!symbolList.includes(baseSymbol)) { symbolList.push(baseSymbol); } // 确保 openContracts、profitLossHistory 和 winLossCount 为每个 baseSymbol 初始化 if (!openContracts[baseSymbol]) { openContracts[baseSymbol] = {}; } if (!openContracts[baseSymbol][order.Symbol]) { openContracts[baseSymbol][order.Symbol] = { longPositions: [], shortPositions: [] }; } if (!profitLossHistory[baseSymbol]) { profitLossHistory[baseSymbol] = {totalProfit: 0, totalLoss: 0}; } if (!winLossCount[baseSymbol]) { winLossCount[baseSymbol] = {win: 0, loss: 0, total: 0}; } // 获取当前 symbol 对应的合约 let currentContracts = openContracts[baseSymbol][order.Symbol]; // 处理开仓和加仓订单 if (order.Offset === 0) { if (order.Type === 0) { // 买入开仓(多单) currentContracts.longPositions.push({ amount: order.Amount, price: order.AvgPrice }); } else if (order.Type === 1) { // 卖出开仓(空单) currentContracts.shortPositions.push({ amount: order.Amount, price: order.AvgPrice }); } } // 处理平仓订单 if (order.Offset === 1) { let remainingAmount = order.Amount; let profitOrLoss = 0; if (order.Type === 1) { // 平多单 while (remainingAmount > 0 && currentContracts.longPositions.length > 0) { let position = currentContracts.longPositions[0]; // 举例仓位信息:100 : 2; 123: 3; 订单平仓信息: 3 let amountToClose = Math.min(position.amount, remainingAmount); //100平了两个 还剩一个 = 2 profitOrLoss += (order.AvgPrice - position.price) * amountToClose; //平单均价 - 仓位均价 * 数量 remainingAmount -= amountToClose; //还剩一个 position.amount -= amountToClose; //平完了,继续下一个仓位 if (position.amount === 0) currentContracts.longPositions.shift(); //除掉该仓位,返回开始,因为remainingAmount > 0,直到平完 } if (remainingAmount > 0) isError = true; //所有仓位平完,但是订单中的仓位没平完,说明有些仓位信息没有记录,报错处理 } if (order.Type === 0) { // 平空单 同样的逻辑 while (remainingAmount > 0 && currentContracts.shortPositions.length > 0) { let position = currentContracts.shortPositions[0]; let amountToClose = Math.min(position.amount, remainingAmount); profitOrLoss += (position.price - order.AvgPrice) * amountToClose; remainingAmount -= amountToClose; position.amount -= amountToClose; if (position.amount === 0) currentContracts.shortPositions.shift(); } if (remainingAmount > 0){ Log(`错误: 平仓数量大于开仓数量,订单ID ${orderId}`); }; } // 计算手续费并更新盈亏 let volumeMultiple = exchange.SetContractType(order.Symbol).VolumeMultiple; profitOrLoss = profitOrLoss * volumeMultiple; let totalCommissionFee = 5 * order.Amount * 2; profitOrLoss -= totalCommissionFee; // 更新盈亏记录 let symbolProfitLoss = profitLossHistory[baseSymbol]; if (profitOrLoss > 0) { symbolProfitLoss.totalProfit += profitOrLoss; winLossCount[baseSymbol].win++; } else { symbolProfitLoss.totalLoss += Math.abs(profitOrLoss); winLossCount[baseSymbol].loss++; } winLossCount[baseSymbol].total++; } // 记录订单 ID 为已处理 processedOrderIds.add(orderId); } // 汇总结果 let totalProfit = 0; let totalLoss = 0; let totalWinCount = 0; let totalTotalCount = 0; // 如果 symbols 为 null,则使用 symbolList let targetlist = (Array.isArray(symbols) && symbols.length > 0) ? symbols : (Array.isArray(symbolList) && symbolList.length > 0) ? symbolList : []; // 计算各个合约的盈亏和交易统计 for (let baseSymbol of targetlist) { let symbolProfit = profitLossHistory[baseSymbol]?.totalProfit || 0; let symbolLoss = profitLossHistory[baseSymbol]?.totalLoss || 0; totalProfit += symbolProfit; totalLoss += symbolLoss; totalWinCount += winLossCount[baseSymbol]?.win || 0; totalTotalCount += winLossCount[baseSymbol]?.total || 0; } // 计算总盈亏比和胜率 let winLossRatio = totalLoss > 0 ? totalProfit / totalLoss : totalProfit > 0 ? 1 : 0; let winRate = totalTotalCount > 0 ? totalWinCount / totalTotalCount : 0; return [_N(winLossRatio, 3), _N(winRate, 3), _N(totalProfit, 2), _N(totalLoss, 2)]; } // 评价系统指标 const period = 86400000; // 1 天的毫秒数 const yearDays = 252; var ts = null var initassets = 0 var profitslist = [] $.InitData = function (){ while (!exchange.IO("status")) { Sleep(3000); } if(_G('init') == null){ Log('开启策略,初始化策略参数') initassets = exchange.GetAccount().Equity ts = new Date().getTime() profitslist = [] _G('init', [ts, initassets, profitslist]) }else{ Log('重启策略,使用保存参数') ts = _G('init')[0] initassets = _G('init')[1] profitslist = _G('init')[2] } } $.IndexCount = function () { while (!exchange.IO("status")) { Sleep(3000); } // 获取账户信息 let te = new Date().getTime(); let curacc = exchange.GetAccount().Equity - initassets; profitslist.push([te, curacc]); _G('init', [ts, initassets, profitslist]) return returnAnalyze(initassets, profitslist, ts, te, yearDays); }; function returnAnalyze(totalAssets, profits, ts, te, yearDays) { const period = 86400000; // 1 天的毫秒数 const yearRange = yearDays * period; const runningDays = Math.ceil((te - ts) / period); if (profits.length === 0) return null; let totalReturns = profits[profits.length - 1][1] / totalAssets; let annualizedReturns = (totalReturns * yearRange) / (te - ts); // 最大回撤计算 let maxDrawdown = 0; let maxDrawdownTime = ts; let maxAssetsTime = ts; let maxDrawdownStartTime = ts; let maxAssets = totalAssets; profits.forEach(([timestamp, profit]) => { const currentAssets = profit + totalAssets; if (currentAssets > maxAssets) { maxAssets = currentAssets; maxAssetsTime = timestamp; } if (maxAssets > 0) { const drawDown = 1 - currentAssets / maxAssets; if (drawDown > maxDrawdown) { maxDrawdown = drawDown; maxDrawdownTime = timestamp; maxDrawdownStartTime = maxAssetsTime; } } }); // 修剪利润并计算每期的利润率 var i = 0; var datas = []; var sum = 0; var preProfit = 0; var perRatio = 0; var rangeEnd = te; if ((te - ts) % period > 0) { rangeEnd = (parseInt(te / period) + 1) * period; } for (var n = ts; n < rangeEnd; n += period) { var dayProfit = 0.0; var cut = n + period; while (i < profits.length && profits[i][0] < cut) { dayProfit += profits[i][1] - preProfit; preProfit = profits[i][1]; i++; } perRatio = ((dayProfit / totalAssets) * yearRange) / period; sum += perRatio; datas.push(perRatio); } // 夏普比率和波动率计算 const freeProfit = 0.03; // 无风险收益率 let sharpeRatio = 0; let volatility = 0; if (datas.length > 0 && !datas.every(items => items == 0)) { const avg = sum / datas.length; const squaredDiffs = datas.reduce((acc, value) => acc + Math.pow(value - avg, 2), 0); volatility = Math.sqrt(squaredDiffs / datas.length); if (volatility !== 0) { sharpeRatio = (annualizedReturns - freeProfit) / volatility; } } // Sortino比率计算 let sortinoRatio = 0; let downsideVolatility = 0; if (datas.length > 0 && !datas.every(items => items == 0)) { const downsideSquaredDiffs = datas.reduce((acc, value) => { if (value < freeProfit / yearDays) { return acc + Math.pow(value - freeProfit / yearDays, 2); } return acc; }, 0); downsideVolatility = Math.sqrt(downsideSquaredDiffs / datas.length); if (downsideVolatility !== 0) { sortinoRatio = (annualizedReturns - freeProfit) / downsideVolatility; } } // Calmar比率计算 let calmarRatio = 0; if (maxDrawdown > 0) { calmarRatio = annualizedReturns / maxDrawdown; } startTime = _D(ts) totalReturns = (totalReturns * 100).toFixed(4) + '%' annualizedReturns = (annualizedReturns * 100).toFixed(4) + '%' volatility = (volatility * 100).toFixed(4) + '%' sharpeRatio = (sharpeRatio * 100).toFixed(4) + '%' sortinoRatio = (sortinoRatio * 100).toFixed(4) + '%' calmarRatio = (calmarRatio * 100).toFixed(4) + '%' maxDrawdown = (maxDrawdown * 100).toFixed(4) + '%' maxDrawdownTime = _D(maxDrawdownTime); maxAssetsTime = _D(maxAssetsTime); maxDrawdownStartTime = _D(maxDrawdownStartTime); return [ startTime, totalAssets, runningDays, totalReturns, annualizedReturns, volatility, sharpeRatio, sortinoRatio, calmarRatio, maxDrawdown, maxDrawdownTime, maxAssetsTime, maxDrawdownStartTime ]; } /*backtest start: 2024-11-20 09:00:00 end: 2024-11-21 00:00:00 period: 1d basePeriod: 1m exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}] */ function main() { $.InitData(); // 初始化参数 // 创建订单模拟交易 exchange.CreateOrder('rb888', 'buy', -1, 30); // 买入 rb888 合约 exchange.CreateOrder('hc888', 'sell', -1, 1); // 买入 hc888 合约 Sleep(1000 * 60 * 60 * 24); // 等待 10 分钟 exchange.CreateOrder('rb888', 'closebuy', -1, 30); // 平仓 rb888 合约 exchange.CreateOrder('hc888', 'closesell', -1, 1); // 平仓 hc888 合约 // 计算整体盈亏比 const overallWinLoss = $.WinLossCount(); Log('整体盈亏比:'); Log(`盈亏比:${overallWinLoss[0]} (盈利总额与亏损总额的比值)`); Log(`胜率:${overallWinLoss[1]} (盈利交易占总交易的百分比)`); Log(`总盈利:${overallWinLoss[2]} (所有盈利交易的总和)`); Log(`总亏损:${overallWinLoss[3]} (所有亏损交易的总和)`); Log('--------------------') // 计算指定合约盈亏比 const specificWinLoss = $.WinLossCount(['rb']); Log('需求盈亏比(rb 合约):'); Log(`盈亏比:${specificWinLoss[0]} (盈利总额与亏损总额的比值)`); Log(`胜率:${specificWinLoss[1]} (盈利交易占总交易的百分比)`); Log(`总盈利:${specificWinLoss[2]} (所有盈利交易的总和)`); Log(`总亏损:${specificWinLoss[3]} (所有亏损交易的总和)`); Log('--------------------') // 计算策略评价指标 const indexMetrics = $.IndexCount(); Log('策略评价指标:'); Log(`起始时间:${indexMetrics[0]} (策略开始运行的时间)`); Log(`总资产:${indexMetrics[1]} (当前账户总资产)`); Log(`运行天数:${indexMetrics[2]} (策略运行的天数)`); Log(`累计收益率:${indexMetrics[3]} (从策略开始到现在的总收益率)`); Log(`年化收益率:${indexMetrics[4]} (按照年化标准计算的收益率)`); Log(`波动率:${indexMetrics[5]} (资产收益的标准差,反映风险程度)`); Log(`夏普比率:${indexMetrics[6]} (收益与风险的比值)`); Log(`索提诺比率:${indexMetrics[7]} (基于下行风险的收益与风险比值)`); Log(`卡玛比率:${indexMetrics[8]} (年化收益率与最大回撤的比值)`); Log(`最大回撤:${indexMetrics[9]} (历史资产的最大回撤比例)`); Log(`最大回撤时间:${indexMetrics[10]} (发生最大回撤的时间点)`); Log(`最大资产时间:${indexMetrics[11]} (账户达到最大资产的时间点)`); Log(`最大回撤起始时间:${indexMetrics[12]} (最大回撤开始的时间点)`); }