전략 백테스팅에서 샤프 비율, 최대 하락률, 수익률 및 기타 지표 알고리즘을 분석합니다.
그룹 회원들은 종종 전략의 성과 지표 알고리즘을 논의하며, FMZ API 문서에도 알고리즘이 공개되어 있습니다. 하지만 주석 없이 이해하기는 조금 어렵습니다. 이 글에서는 이 알고리즘을 분석해 보겠습니다. 이 글을 읽으면 샤프 비율, 최대값의 개념과 계산 논리를 더 잘 이해하실 수 있을 거라고 믿습니다. 인출, 수익률. 모두가 더 명확하게 이해합니다.
JavaScript로 작성된 소스 코드를 직접 살펴보겠습니다. FMZ의 백테스팅 시스템도 이 알고리즘을 사용하여 백테스팅 성과 데이터를 자동으로 생성합니다.
returnAnalyze 함수
function returnAnalyze(totalAssets, profits, ts, te, period, yearDays)
이는 계산 함수이므로 입력과 출력이 있어야 합니다. 먼저 함수의 입력을 살펴보겠습니다.
totalAssets, profits, ts, te, period, yearDays
-
totalAssets
이 매개변수는 전략이 실행되기 시작할 때의 초기 총 자산입니다. -
profits
이 매개변수는 일련의 성과 지표 계산이 이 원시 데이터를 기반으로 하기 때문에 비교적 중요한 매개변수입니다. 이 매개변수는 2차원 배열이며 형식은 다음과 같습니다.[[timestamp1, profit1], [timestamp2, profit2], [timestamp3, profit3], ....., [timestampN, profitN]], returnAnalyze 함수는 각 순간의 반환을 시간순으로 기록하는 데이터 구조가 필요하다는 것을 알 수 있습니다. timestamp1부터 timestampN까지 시간적으로 먼 것부터 가까운 것 순으로 정렬되어 있습니다. 각 시점마다 이익가치가 있습니다. 예를 들어, 수익 기록의 세 번째 시점은 다음과 같습니다.[타임스탬프3, 이익3]. FMZ 라인의 백테스팅 시스템에서 수익 배열 데이터는 백테스팅 시스템에 의해 이 함수에 제공됩니다. 물론 수익 데이터를 직접 기록하여 이러한 배열 구조를 형성하는 경우 이 계산 함수에 제공할 수도 있습니다. 결과를 계산합니다. -
ts
백테스트의 시작 타임스탬프. -
te
백테스트의 종료 타임스탬프. -
period
밀리초 단위의 계산 주기. -
yearDays
1년 동안의 거래일.
다음으로, 이 함수의 출력을 살펴보겠습니다.
return {
totalAssets: totalAssets,
yearDays: yearDays,
totalReturns: totalReturns,
annualizedReturns: annualizedReturns,
sharpeRatio: sharpeRatio,
volatility: volatility,
maxDrawdown: maxDrawdown,
maxDrawdownTime: maxDrawdownTime,
maxAssetsTime: maxAssetsTime,
maxDrawdownStartTime: maxDrawdownStartTime,
winningRate: winningRate
}
- totalAssets: 초기 순자산
- yearDays: 거래일
- totalReturns: 누적 반품율
- annualizedReturns: 연간 수익률
- sharpeRatio: 샤프 비율
- 변동성: 변동성
- maxDrawdown: 최대 되돌림
- maxDrawdownTime: 최대 하락의 타임스탬프
- maxAssetsTime: 최대 순가치의 타임스탬프
- maxDrawdownStartTime: 최대 되돌림 시작 시간
- winningRate: 승률
입력과 출력을 알았으니, 이제 이 함수가 무엇에 사용되는지 이해하게 되었습니다. 간단히 말해서, 이 함수에 수익 통계 배열과 같은 원시 레코드를 제공합니다. 이 함수는 백테스트 성과를 보여주기 위해 결과를 계산합니다.
다음으로, 코드가 어떻게 계산되는지 살펴보겠습니다.
javascript
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이다. . -
표준편차:
분산의 제곱근을 구하면 표준 편차가 됩니다. -
휘발성:
계산 규모가 연간화되면 변동성은 표준 편차가 됩니다.
이러한 개념과 계산 공식을 이해하면 함수의 샤프 계산 부분이 한눈에 명확해질 것입니다.
샤프 비율 계산 공식: (연간 수익률 - 무위험 이자율) / 표준편차
배웠어요?
- 1


