戦略のバックテストで,Sharpe比率,最大引き下げ,リターンレート,および他の指標アルゴリズムを分析します

作者: リン・ハーンリディア, 作成日:2022-11-28 16:44:25, 更新日:2023-09-13 19:42:38

img

戦略のバックテストで,Sharpe比率,最大引き下げ,リターンレート,および他の指標アルゴリズムを分析します

戦略のいくつかのパフォーマンス指標アルゴリズムは,グループメンバーによってしばしば議論され,FMZのAPI文書でもアルゴリズムが公開されています.しかし,コメントなしで理解するのは簡単ではありません.この記事では,アルゴリズムを分析するためにあなたを連れて行きます.この記事を読んだ後に,シャープ比率,最大引き下げ,リターンレート,計算論理の概念を明確に理解できると信じています. ソースコードから始めます.JavaScript言語で書かれています. FMZのバックテストシステムもこのアルゴリズムを採用し,バックテストパフォーマンスデータを自動的に生成します.

return分析関数

function returnAnalyze(totalAssets, profits, ts, te, period, yearDays)

https://www.fmz.com/api#Backtestingシステム シャープアルゴリズム

計算関数なので,入力と出力がある必要があります. まず関数の入力を見てみましょう.

totalAssets, profits, ts, te, period, yearDays
  • 総資産 このパラメータは,戦略が実行開始時に最初の総資産です.

  • 利益 このパラメータは,この元のデータに基づいて一連のパフォーマンス指標が計算されるため,このパラメータは重要なものです.このパラメータは,次の形式の二次元配列です.[[timestamp1, profit1], [timestamp2, profit2], [timestamp3, profit3],..., [timestampN, profitN]]. returnAnalyze関数には,毎回返済の時間順を記録するデータ構造が必要であることがわかります. タイムスタンプ1からタイムスタンプNは,遠から近くまでの時間順であります.各時間点には利益値があります.例えば,返済記録の3番目の時間点は[タイムスタンプ3,利益3]. FMZのオンラインバックテストシステムでは,利益配列のデータはバックテストシステムによってこの関数に提供されます. もちろん,返済データを自分で記録して,そのような配列構造を形成した場合,結果を計算するために計算関数にも提供できます.

  • ツ バックテストの開始時刻スタンプです

  • さん バックテストの終わりのタイムスタンプです

期間 計算期間をミリ秒単位で計算する

  • 年 日 1年以内に取引日です

この関数の出力を見てみましょう.

return {
        totalAssets: totalAssets,
        yearDays: yearDays,
        totalReturns: totalReturns,
        annualizedReturns: annualizedReturns,
        sharpeRatio: sharpeRatio,
        volatility: volatility,
        maxDrawdown: maxDrawdown,
        maxDrawdownTime: maxDrawdownTime,
        maxAssetsTime: maxAssetsTime,
        maxDrawdownStartTime: maxDrawdownStartTime,
        winningRate: winningRate
    }
  • 総資産: 総資産
  • 年 日:取引日
  • 総収益: 総収益
  • 年収: 年収
  • sharpeRatio: シャープ比率
  • 不安定性: 不安定性
  • マックスドローダウン:最大ドローダウン
  • maxDrawdownTime: 最大抽出時にタイムスタンプ
  • maxAssetsTime: 最大資産のタイムスタンプ
  • maxDrawdownStartTime: 最大引き上げ開始時間
  • 勝率: 勝率

入力と出力を知ることで,関数が何を用いるのか理解できます.簡単に言うと,関数には返回統計数列などのオリジナルレコードを与えます.関数はバックテストのパフォーマンスを示す結果を提供します.

次に,コードの計算方法を見てみましょう.

function returnAnalyze(totalAssets, profits, ts, te, period, yearDays) {
    // force by days
    period = 86400000                  // The number of milliseconds in a day, that is 60 * 60 * 24 * 1000
    if (profits.length == 0) {         // If the length of the array of profits is 0, it cannot be calculated and it will return to the null value directly
        return null
    }
    var freeProfit = 0.03              // Risk-free interest rate, which can also be set according to the demand, such as 3% annualized national debt
    var yearRange = yearDays * 86400000          // Milliseconds of all accumulated trading days in a year
    var totalReturns = profits[profits.length - 1][1] / totalAssets      // Total return rate
    var annualizedReturns = (totalReturns * yearRange) / (te - ts)       // The annualized rate of return, the expected rate of return obtained by scaling the time of profit statistics to the scale of one year

    // MaxDrawDown
    var maxDrawdown = 0           // Initialize the maximum drawdown variable to 0
    var maxAssets = totalAssets   // Initialize maximum asset variable with initial net value assignment
    var maxAssetsTime = 0         // Timestamp of the time when the maximum asset is initialized
    var maxDrawdownTime = 0       // Timestamp of the time when the maximum drawdown is initialized
    var maxDrawdownStartTime = 0  // Timestamp of the time when the maximum start time is initialized
    var winningRate = 0           // Initialized win rate is 0
    var winningResult = 0         // Record the number of win time
    for (var i = 0; i < profits.length; i++) {      // Traverse the return array
        if (i == 0) {
            if (profits[i][1] > 0) {                // If the first return record point, the return is greater than 0, it means the profit
                winningResult++                     // The number of win times accumulates 1 
            }
        } else {                                    // If it is not the first returns record point, as long as the returns of the current point is greater than the returns of the previous moment (return point), it means profit, and the number of win times accumulates 1 
            if (profits[i][1] > profits[i - 1][1]) {
                winningResult++
            }
        }
        if ((profits[i][1] + totalAssets) > maxAssets) {    // If the return plus the initial net value at that moment is greater than the largest asset recorded to have occurred, the value of the largest asset is updated and the timestamp of this moment is recorded
            maxAssets = profits[i][1] + totalAssets
            maxAssetsTime = profits[i][0]
        }
        if (maxAssets > 0) {                                // When the maximum asset value recorded is greater than 0, the drawdown is calculated
            var drawDown = 1 - (profits[i][1] + totalAssets) / maxAssets
            if (drawDown > maxDrawdown) {                   // If the current drawdown is greater than the recorded maximum drawdown, update the maximum drawdown, maximum drawdown time, etc
                maxDrawdown = drawDown
                maxDrawdownTime = profits[i][0]
                maxDrawdownStartTime = maxAssetsTime
            }
        }
    }
    if (profits.length > 0) {                            // Calculate the winning rate
        winningRate = winningResult / profits.length
    }
    // trim profits
    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     // Process rangeEnd as an integer multiple of 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) {    // Ensure that when the timestamp is not out of bounds, the array length is also not out of bounds
            dayProfit += (profits[i][1] - preProfit)           // Calculate the daily returns
            preProfit = profits[i][1]                          // Record yesterday's returns
            i++                                                // Accumulate i for accessing the next profits node
        }
        perRatio = ((dayProfit / totalAssets) * yearRange) / period   // Calculate the annualized rate of return at that time
        sum += perRatio                                               // Accumulation
        datas.push(perRatio)                                          // Put in the array datas
    }

    var sharpeRatio = 0                    // Initial Sharpe ratio is 0
    var volatility = 0                     // Initial volatility is 0
    if (datas.length > 0) {
        var avg = sum / datas.length;      // Find the mean value
        var std = 0;
        for (i = 0; i < datas.length; i++) {
            std += Math.pow(datas[i] - avg, 2);      // The std is used to calculate the following variance. The following std/datas.length is the variance, and the square root of the number is the standard deviation
        }
        volatility = Math.sqrt(std / datas.length);  // In terms of years, volatility is the standard deviation
        if (volatility !== 0) {
            sharpeRatio = (annualizedReturns - freeProfit) / volatility   // Sharpe formula to calculate Sharpe ratio: (annualized return rate - risk-free rate) / standard deviation
        }
    }

    return {
        totalAssets: totalAssets,
        yearDays: yearDays,
        totalReturns: totalReturns,
        annualizedReturns: annualizedReturns,
        sharpeRatio: sharpeRatio,
        volatility: volatility,
        maxDrawdown: maxDrawdown,
        maxDrawdownTime: maxDrawdownTime,
        maxAssetsTime: maxAssetsTime,
        maxDrawdownStartTime: maxDrawdownStartTime,
        winningRate: winningRate
    }
}

全体として,アルゴリズムは複雑ではなく,事前に理解する必要があるいくつかの概念があるかもしれません.

  • バリアンス: 返信データとして理解できる. サンプル群の1,2,3,4と5の平均値は (1+2+3+4+5)/5 = 3で,分散は各データとその合計の差の平方和の平均値で,それぞれ [(1-3) ^ 2 + (2-3) ^ 2 + (3-3) ^ 2 + (4-3) ^ 2 + (5-3) ^ 2) / 5 = 2で,分散は 2です.

  • 標準偏差: 標準偏差である変数平方根を計算します

  • 波動性 計算スケールが年間化される場合,波動性は標準偏差です.

これらの概念と計算式を理解すると,関数のシャープ計算部分は一目で明らかになります. シャープ比率を計算するシャープ式: (年収率 - リスクフリー率) /標準偏差

もう習ったのか?


もっと