
グループのメンバーは、戦略のパフォーマンス指標アルゴリズムについて頻繁に議論しており、アルゴリズムは FMZ API ドキュメントにも公開されています。しかし、コメントなしで理解するのは少し難しいです。この記事では、このアルゴリズムを分析します。この記事を読んだ後、シャープ比、最大値の概念と計算ロジックをよりよく理解できると思います。ドローダウンと収益率。誰もがより明確に理解できるようになります。
JavaScript で記述されたソース コードに直接アクセスします。 FMZ のバックテスト システムでもこのアルゴリズムを使用して、バックテストのパフォーマンス データを自動的に生成します。
function returnAnalyze(totalAssets, profits, ts, te, period, yearDays)
https://www.fmz.com/api#%E5%9B%9E%E6%B5%8B%E7%B3%BB%E7%BB%9F%E5%A4%8F%E6%99%AE%E7%AE%97%E6%B3%95
計算関数なので、入力と出力が必要です。まず関数の入力を見てみましょう。
totalAssets, profits, ts, te, period, yearDays
totalAssets このパラメータは、戦略の実行が開始されたときの初期の総資産です。
profits
このパラメーターは、一連のパフォーマンス指標の計算がこの生データに基づいて行われるため、比較的重要なパラメーターです。このパラメータは 2 次元配列であり、形式は次のとおりです。[[timestamp1, profit1], [timestamp2, profit2], [timestamp3, profit3], ....., [timestampN, profitN]]から、returnAnalyze 関数には、各瞬間のリターンの時系列を記録するデータ構造が必要であることがわかります。 timestamp1 から timestampN は、時間的に遠いものから近いものの順になっています。各時点において利益値が存在します。例えば、収益記録の3番目の時点は[タイムスタンプ3、利益3]。 FMZラインのバックテストシステムでは、バックテストシステムによって利益配列データがこの関数に提供されます。もちろん、利益データを自分で記録してこのような配列構造を形成する場合は、この計算関数に提供して結果を計算します。
ts バックテストの開始タイムスタンプ。
te バックテストの終了タイムスタンプ。
period 計算サイクル(ミリ秒単位)。
yearDays 年間の取引日数。
次に、この関数の出力を見てみましょう。
return {
totalAssets: totalAssets,
yearDays: yearDays,
totalReturns: totalReturns,
annualizedReturns: annualizedReturns,
sharpeRatio: sharpeRatio,
volatility: volatility,
maxDrawdown: maxDrawdown,
maxDrawdownTime: maxDrawdownTime,
maxAssetsTime: maxAssetsTime,
maxDrawdownStartTime: maxDrawdownStartTime,
winningRate: winningRate
}

入力と出力がわかったので、この関数が何に使用されるかがわかりました。簡単に言うと、収益統計の配列など、いくつかの生のレコードをこの関数に渡します。この関数はバックテストのパフォーマンスを表示するための結果を計算します。
次に、コードがどのように計算されるかを見てみましょう。
function returnAnalyze(totalAssets, profits, ts, te, period, yearDays) {
// force by days
period = 86400000 // 一天的毫秒数,即 60 * 60 * 24 * 1000
if (profits.length == 0) { // 如果参数profits数组长度为0,无法计算直接返回空值
return null
}
var freeProfit = 0.03 // 无风险利率 ,也可以根据需求设置,例如国债年化3%
var yearRange = yearDays * 86400000 // 一年所有累计的交易日的毫秒数
var totalReturns = profits[profits.length - 1][1] / totalAssets // 累计收益率
var annualizedReturns = (totalReturns * yearRange) / (te - ts) // 年华收益率,把收益统计的时间缩放到一年的尺度上得出的预期收益率
// MaxDrawDown
var maxDrawdown = 0 // 初始化最大回撤变量为0
var maxAssets = totalAssets // 以初始净值赋值初始化最大资产变量
var maxAssetsTime = 0 // 初始化最大资产时刻的时间戳
var maxDrawdownTime = 0 // 初始化最大回撤时刻的时间戳
var maxDrawdownStartTime = 0 // 初始化最大回撤开始时刻的时间戳
var winningRate = 0 // 初始化胜率为0
var winningResult = 0 // 记录赢的次数
for (var i = 0; i < profits.length; i++) { // 遍历收益数组
if (i == 0) {
if (profits[i][1] > 0) { // 如果第一个收益记录点,收益大于0,表示盈利
winningResult++ // 赢的次数累加1
}
} else { // 如果不是第一个收益记录点,只要当前的点的收益,大于前一个时刻(收益点)的收益,表示盈利,赢的次数累加1
if (profits[i][1] > profits[i - 1][1]) {
winningResult++
}
}
if ((profits[i][1] + totalAssets) > maxAssets) { // 如果该时刻的收益加初始净值大于记录出现过的最大资产,就更新最大资产数值,记录这个时刻的时间戳
maxAssets = profits[i][1] + totalAssets
maxAssetsTime = profits[i][0]
}
if (maxAssets > 0) { // 当记录的最大资产数值大于0时,计算回撤
var drawDown = 1 - (profits[i][1] + totalAssets) / maxAssets
if (drawDown > maxDrawdown) { // 如果当前回撤大于记录过的最大回撤,更新最大回撤、最大回撤时间等
maxDrawdown = drawDown
maxDrawdownTime = profits[i][0]
maxDrawdownStartTime = maxAssetsTime
}
}
}
if (profits.length > 0) { // 计算胜率
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 // 把rangeEnd处理为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++ // 累加i用于访问下一个profits节点
}
perRatio = ((dayProfit / totalAssets) * yearRange) / period // 计算当时年华的收益率
sum += perRatio // 累计
datas.push(perRatio) // 放入数组 datas
}
var sharpeRatio = 0 // 初始夏普比率为0
var volatility = 0 // 初始波动率为0
if (datas.length > 0) {
var avg = sum / datas.length; // 求均值
var std = 0;
for (i = 0; i < datas.length; i++) {
std += Math.pow(datas[i] - avg, 2); // std用于计算后面的方差,后面的std / datas.length就是方差,求算数平方根就是标准差
}
volatility = Math.sqrt(std / datas.length); // 当按年时,波动率就是标准差
if (volatility !== 0) {
sharpeRatio = (annualizedReturns - freeProfit) / volatility // 夏普计算公式计算夏普率:(年华收益率 - 无风险利率) / 标准差
}
}
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である。 。
標準偏差: 分散の平方根をとります。これが標準偏差です。
ボラティリティ: 計算スケールが年換算の場合、ボラティリティは標準偏差となります。
これらの概念と計算式を理解すれば、関数のシャープ計算部分が一目でわかるようになります。 シャープレシオの計算式: (年率収益率 - リスクフリーレート) / 標準偏差
学びましたか?