资源加载中... loading...

策略评价指标计算类库

Author: ianzeng123, Date: 2024-11-21 15:24:25
Tags:

策略评价指标计算类库使用说明

本类库提供了一些方法,用于帮助用户评估交易策略的表现。下面通过实例说明如何使用这些方法,并解释返回的指标及其含义。

1. 使用方法

初始化数据

在使用本类库之前,需要调用 $.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]} (最大回撤开始的时间点)`);

2. 返回的指标及其含义

盈亏比指标(WinLossCount)

  1. 盈亏比winLossRatio):盈利总额与亏损总额的比值,衡量了整体交易的盈利效率。盈亏比大于 1 表示盈利交易的总额大于亏损。
  2. 胜率winRate):盈利交易占所有交易的百分比,反映了交易系统的成功率。胜率较高表示系统稳定。
  3. 总盈利totalProfit):所有盈利交易的总和,反映了在所有盈利交易中获得的收益。
  4. 总亏损totalLoss):所有亏损交易的总和,反映了在所有亏损交易中遭受的损失。

策略评价指标(IndexCount)

  1. 起始时间startTime):策略开始运行的时间,帮助评估策略的历史背景。
  2. 总资产totalAssets):当前账户的总资产,用于衡量账户的当前规模。
  3. 运行天数runningDays):策略已运行的天数,反映了策略的运行时间。
  4. 累计收益率totalReturns):从策略开始到现在的总收益率,用来评估策略的长期表现。
  5. 年化收益率annualizedReturns):按照年化标准计算的收益率,有助于对比不同策略的长期回报。
  6. 波动率volatility):资产收益的标准差,反映了策略的风险程度,波动率越大表示风险越高。
  7. 夏普比率sharpeRatio):收益与风险的比值,夏普比率越高表示单位风险获得的收益越多。
  8. 索提诺比率sortinoRatio):基于下行风险的收益与风险比值,类似于夏普比率,但专注于负向波动的影响。
  9. 卡玛比率calmarRatio):年化收益率与最大回撤的比值,反映了策略的回报与风险的关系,卡玛比率越高表示策略越有效。
  10. 最大回撤maxDrawdown):历史资产的最大回撤比例,衡量账户最大亏损程度。
  11. 最大回撤时间maxDrawdownTime):发生最大回撤的时间点,帮助评估策略的耐用性。
  12. 最大资产时间maxAssetsTime):账户达到最大资产的时间点,表明策略的顶峰表现。
  13. 最大回撤起始时间maxDrawdownStartTime):最大回撤开始的时间点,有助于了解策略的回撤特征。

3. 总结

使用本类库,您可以:

  • 通过 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]} (最大回撤开始的时间点)`);
}



更多内容