本类库提供了一些方法,用于帮助用户评估交易策略的表现。下面通过实例说明如何使用这些方法,并解释返回的指标及其含义。
在使用本类库之前,需要调用 $.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]} (最大回撤开始的时间点)`);
}