Certains algorithmes d'indicateur de performance de la stratégie sont souvent discutés par les membres du groupe, et un algorithme a également été divulgué dans le document API de FMZ. Cependant, il n'est pas facile à comprendre sans commentaires. Dans cet article, je vous emmènerai analyser l'algorithme. Je crois que vous aurez une compréhension claire des concepts de ratio de Sharpe, de tirage maximum, de taux de rendement et de la logique de calcul après avoir lu cet article. Nous commencerons par le code source, qui est écrit en langage JavaScript. Le système de backtesting de FMZ adopte également cet algorithme pour générer automatiquement des données de performance de backtesting.
function returnAnalyze(totalAssets, profits, ts, te, period, yearDays)
https://www.fmz.com/api#BacktestingSystème algorithme Sharpe
Puisqu'il s'agit d'une fonction de calcul, il doit y avoir des entrées et des sorties.
totalAssets, profits, ts, te, period, yearDays
Total des actifs Ce paramètre représente le total des actifs initiaux au début de l'exécution de la stratégie.
les bénéfices
Ce paramètre est important, car une série d'indicateurs de performance sont calculés sur la base de ces données originales.[[timestamp1, profit1], [timestamp2, profit2], [timestamp3, profit3],..., [timestampN, profitN]]
. On peut voir que la fonction returnAnalyze a besoin d'une telle structure de données qui enregistre l'ordre chronologique des rendements à chaque fois. Timestamp1 à timestampN sont dans l'ordre chronologique de loin à près. Il y a une valeur de profit à chaque point de temps. Par exemple, le troisième point de temps dans l'enregistrement des rendements est [timestamp 3, profit3]. Dans le système de backtesting en ligne de FMZ, les données du tableau des bénéfices sont fournies à cette fonction par le système de backtesting. Bien sûr, si vous enregistrez les données de retour par vous-même et formez une telle structure de tableau, vous pouvez également les fournir à la fonction de calcul pour calculer les résultats.
ts L'horodatage de début du backtest.
le L'horodatage de la fin du backtest.
période Période de calcul au niveau des millisecondes.
Ensuite, regardons ensemble la sortie de cette fonction:
return {
totalAssets: totalAssets,
yearDays: yearDays,
totalReturns: totalReturns,
annualizedReturns: annualizedReturns,
sharpeRatio: sharpeRatio,
volatility: volatility,
maxDrawdown: maxDrawdown,
maxDrawdownTime: maxDrawdownTime,
maxAssetsTime: maxAssetsTime,
maxDrawdownStartTime: maxDrawdownStartTime,
winningRate: winningRate
}
Connaissant l'entrée et la sortie, nous pouvons comprendre à quoi sert la fonction. Pour le dire simplement, elle donne à la fonction quelques enregistrements originaux, tels que le tableau de statistiques de retour. La fonction vous donnera un résultat pour montrer la performance du backtest.
Ensuite, voyons comment le code est calculé:
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
}
}
Dans l'ensemble, l'algorithme n'est pas complexe, et il peut y avoir plusieurs concepts qui doivent être compris à l'avance.
Variance: Il peut être compris comme un ensemble de données de retour. Le groupe d'échantillons, 1, 2, 3, 4 et 5, a une valeur moyenne de (1+2+3+4+5)/5 = 3, tandis que la variance est la valeur moyenne de la somme des carrés des différences entre chaque donnée et sa somme respectivement, qui est [(1-3) ^ 2 + (2-3) ^ 2 + (3-3) ^ 2 + (4-3) ^ 2 + (5-3) ^ 2) / 5 = 2, et la variance est 2.
Déviation type: Calculez la racine carrée arithmétique de la variance, qui est l'écart type.
La volatilité: Lorsque l'échelle de calcul est annualisée, la volatilité est l'écart type.
Avec la compréhension de ces concepts et formules de calcul, la partie de calcul de Sharpe de la fonction sera claire en un coup d'œil. Formule de Sharpe pour le calcul du ratio de Sharpe: (taux de rendement annualisé - taux sans risque) / écart type
Tu l'as déjà appris?