// Copyright 2015 Ivan Popivanov
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package net.tradelib.core;
import java.util.logging.Logger;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
public class TradeSummaryBuilder {
private long numTrades;
private double grossProfits;
private double grossLosses;
private SummaryStatistics dailyPnlStats;
private SummaryStatistics pnlStats;
private long nonZero;
private long positive;
private long negative;
private double maxWin;
private double maxLoss;
private Average averageWinTrade;
private Average averageLossTrade;
private Series pnl;
private int pnlId;
private double equity;
private double minEquity;
private double maxEquity;
private double maxDD;
private double maxDDPct;
public TradeSummaryBuilder(Series pnl) {
numTrades = 0;
grossProfits = 0.0;
grossLosses = 0.0;
dailyPnlStats = new SummaryStatistics();
pnlStats = new SummaryStatistics();
nonZero = 0; positive = 0; negative = 0;
maxWin = 0.0;
maxLoss = 0.0;
averageWinTrade = new Average();
averageLossTrade = new Average();
this.pnl = pnl;
pnlId = 0;
equity = 0.0;
minEquity = Double.MAX_VALUE;
maxEquity = Double.MIN_VALUE;
maxDD = Double.MAX_VALUE;
maxDDPct = Double.MAX_VALUE;
}
public void add(Trade ts) {
++numTrades;
if(ts.pnl < 0.0) {
++nonZero;
++negative;
averageLossTrade.add(ts.pnl);
grossLosses += ts.pnl;
} else if(ts.pnl > 0.0) {
++nonZero;
++positive;
averageWinTrade.add(ts.pnl);
grossProfits += ts.pnl;
}
pnlStats.addValue(ts.pnl);
maxWin = Math.max(maxWin, ts.pnl);
maxLoss = Math.min(maxLoss, ts.pnl);
// Set the PnL to zero until the current trade begins
while(pnlId < pnl.size() && pnl.getTimestamp(pnlId).isBefore(ts.start)) {
pnl.set(pnlId, 0.0);
++pnlId;
}
// Keep the PnL as it is inside the current trade
while(pnlId < pnl.size() && !pnl.getTimestamp(pnlId).isAfter(ts.end)) {
equity += pnl.get(pnlId);
maxEquity = Math.max(maxEquity, equity);
minEquity = Math.min(minEquity, equity);
maxDD = Math.min(maxDD, equity - maxEquity);
double prev = maxDDPct;
maxDDPct = Math.min(maxDDPct, equity/maxEquity-1);
if(Double.isNaN(maxDDPct) || !Double.isFinite(maxDDPct)) {
maxDDPct = Double.MAX_VALUE;
}
if(pnl.get(pnlId) != 0.0) {
dailyPnlStats.addValue(pnl.get(pnlId));
}
++pnlId;
}
}
public TradeSummary summarize() {
TradeSummary summary = new TradeSummary();
summary.numTrades = numTrades;
if(numTrades > 0) {
summary.grossLosses = grossLosses;
summary.grossProfits = grossProfits;
if(grossLosses != 0.0) {
summary.profitFactor = Math.abs(grossProfits/grossLosses);
} else {
summary.profitFactor = Math.abs(grossProfits);
}
summary.averageTradePnl = pnlStats.getMax();
summary.tradePnlStdDev = pnlStats.getStandardDeviation();
if(numTrades > 0) {
summary.pctNegative = (double)negative/numTrades*100.0;
summary.pctPositive = (double)positive/numTrades*100.0;
} else {
summary.pctNegative = 0.0;
summary.pctPositive = 0.0;
}
summary.maxLoss = maxLoss;
summary.maxWin = maxWin;
summary.averageLoss = averageLossTrade.get();
summary.averageWin = averageWinTrade.get();
if(summary.averageLoss != 0.0) {
summary.averageWinLoss = summary.averageWin/(summary.averageLoss*(-1.0));
} else {
summary.averageWinLoss = summary.averageWin;
}
summary.equityMin = minEquity;
summary.equityMax = maxEquity;
summary.maxDD = maxDD;
summary.maxDDPct = maxDDPct*100;
if(Double.isNaN(summary.maxDDPct) || !Double.isFinite(summary.maxDDPct)) {
Logger.getLogger("").warning(String.format("Fixing a bad maximum drawdown [%f]", summary.maxDDPct));
summary.maxDDPct = Double.MAX_VALUE;
}
summary.averageDailyPnl = dailyPnlStats.getMean();
summary.dailyPnlStdDev = dailyPnlStats.getStandardDeviation();
if(summary.dailyPnlStdDev == 0) {
summary.dailyPnlStdDev = 1e-10;
}
summary.sharpeRatio = Functions.sharpeRatio(summary.averageDailyPnl, summary.dailyPnlStdDev, 252);
}
return summary;
}
}