package jtrade.trader;
import java.util.ArrayList;
import java.util.List;
import jtrade.Symbol;
import jtrade.marketfeed.MarketFeed;
import jtrade.timeseries.TimeSeries;
import jtrade.timeseries.TimeSeriesArray;
import jtrade.timeseries.TimeSeriesMap;
import jtrade.timeseries.TimeSeriesValuePair;
import jtrade.util.Configurable;
import jtrade.util.Util;
import org.joda.time.DateTime;
public class PerformanceTracker {
public final Configurable<Integer> BUSINESS_DAYS_PER_YEAR = new Configurable<Integer>("BUSINESS_DAYS_PER_YEAR", 252);
public final Configurable<Double> RISKFREE_RATE = new Configurable<Double>("RISKFREE_RATE", 0.0);
MarketFeed marketFeed;
DateTime start;
DateTime end;
TimeSeries portfolioByDate;
List<Trade> trades;
boolean needsUpdate;
TimeSeries pnlByDate;
TimeSeries returnsByDate;
TimeSeries cumPnlByDate;
TimeSeries cumReturnsByDate;
int numTrades;
double tradesPerDay;
double profitLoss;
double avgReturn;
double stdReturn;
double annualizedReturn;
double performanceIndex;
double sharpeRatio;
double profitFactor;
double maxDrawDown;
double winRate;
double longShort;
double expectancy;
public PerformanceTracker(MarketFeed marketFeed) {
this.marketFeed = marketFeed;
portfolioByDate = new TimeSeriesMap();
trades = new ArrayList<Trade>(128);
needsUpdate = true;
pnlByDate = new TimeSeriesArray();
returnsByDate = new TimeSeriesArray();
cumPnlByDate = new TimeSeriesArray();
cumReturnsByDate = new TimeSeriesArray();
numTrades = 0;
tradesPerDay = 0;
profitLoss = Double.NaN;
avgReturn = Double.NaN;
stdReturn = Double.NaN;
annualizedReturn = Double.NaN;
performanceIndex = Double.NaN;
sharpeRatio = Double.NaN;
profitFactor = Double.NaN;
maxDrawDown = Double.NaN;
winRate = Double.NaN;
longShort = Double.NaN;
expectancy = Double.NaN;
}
public void updatePortfolio(DateTime date, Portfolio portfolio) {
date = date.withTimeAtStartOfDay();
if (start == null || date.isBefore(start)) {
start = date;
}
if (end == null || date.isAfter(end)) {
end = date;
}
portfolioByDate.set(date, portfolio.getPortfolioValue());
needsUpdate = true;
}
public void updateTrades(DateTime date, Symbol symbol, int quantity, double costBasis, double price, double profitLoss) {
trades.add(new Trade(date, symbol, quantity, costBasis, price, profitLoss));
needsUpdate = true;
}
public void updateMetrics() {
if (!needsUpdate || portfolioByDate.size() <= 1) {
return;
}
pnlByDate = new TimeSeriesArray(portfolioByDate.diff(1));
returnsByDate = portfolioByDate.arithReturn(1);
cumPnlByDate = pnlByDate.cumsum();
returnsByDate = returnsByDate.cumsum();
double days = ((double)portfolioByDate.size());
double annualRiskFreeRate = RISKFREE_RATE.get();
double businessDaysPerYear = BUSINESS_DAYS_PER_YEAR.get();
double profit = 0.0;
double loss = 0.0;
double cumProfitLoss = 0.0;
double profitSquared = 0.0;
double peak = Double.NEGATIVE_INFINITY;
double mdd = Double.NEGATIVE_INFINITY;
for (TimeSeriesValuePair vp : pnlByDate) {
double pl = vp.getValue();
if (pl >= 0) {
profit += pl;
} else {
loss -= pl;
}
cumProfitLoss += pl;
profitSquared += pl > 0 ? pl * pl : 0;
if (cumProfitLoss != 0.0) {
peak = Math.max(peak, cumProfitLoss);
mdd = Math.max(mdd, peak - cumProfitLoss);
}
}
int l = 0;
int w = 0;
for (Trade t : trades) {
if (t.getQuantity() > 0) {
l++;
}
if (t.getProfitLoss() >= 0) {
w++;
}
}
numTrades = trades.size();
tradesPerDay = numTrades / days;
profitLoss = cumProfitLoss;
avgReturn = returnsByDate.mean();
stdReturn = returnsByDate.std();
annualizedReturn = ((portfolioByDate.last() - portfolioByDate.first()) / portfolioByDate.first()) / (days / businessDaysPerYear);
performanceIndex = numTrades != 0 ? (Math.sqrt(numTrades) * (cumProfitLoss / numTrades)) / (Math.sqrt(numTrades * profitSquared - cumProfitLoss * cumProfitLoss) / numTrades) : Double.NaN;
profitFactor = loss != 0.0 ? profit / loss : Double.POSITIVE_INFINITY;
sharpeRatio = ((avgReturn - (annualRiskFreeRate / businessDaysPerYear)) / stdReturn) * Math.sqrt(businessDaysPerYear);
maxDrawDown = -mdd;
winRate = ((double)w) / numTrades;
longShort = numTrades > 0 ? ((double)l) / numTrades : 1.0;
expectancy = (winRate * (profit / w)) - ((1 - winRate) * (loss / (numTrades - w)));
needsUpdate = false;
}
public MarketFeed getMarketFeed() {
return marketFeed;
}
public DateTime getStart() {
return start;
}
public DateTime getEnd() {
return end;
}
public List<Trade> getTrades() {
return trades;
}
public TimeSeries getPortfolioByDate() {
updateMetrics();
return portfolioByDate;
}
public TimeSeries getPnlByDate() {
updateMetrics();
return pnlByDate;
}
public TimeSeries getReturnsByDate() {
updateMetrics();
return returnsByDate;
}
public TimeSeries getCumPnlByDate() {
updateMetrics();
return cumPnlByDate;
}
public TimeSeries getCumReturnsByDate() {
updateMetrics();
return cumReturnsByDate;
}
public int getNumTrades() {
updateMetrics();
return numTrades;
}
public double getTradesPerDay() {
updateMetrics();
return tradesPerDay;
}
public double getProfitLoss() {
updateMetrics();
return profitLoss;
}
public double getAvgReturn() {
updateMetrics();
return avgReturn;
}
public double getStdReturn() {
updateMetrics();
return stdReturn;
}
public double getAnnualizedReturn() {
updateMetrics();
return annualizedReturn;
}
public double getPerformanceIndex() {
updateMetrics();
return performanceIndex;
}
public double getSharpeRatio() {
updateMetrics();
return sharpeRatio;
}
public double getProfitFactor() {
updateMetrics();
return profitFactor;
}
public double getMaxDrawDown() {
updateMetrics();
return maxDrawDown;
}
public double getWinRate() {
updateMetrics();
return winRate;
}
public double getLongShort() {
updateMetrics();
return longShort;
}
public double getExpectancy() {
updateMetrics();
return expectancy;
}
@Override
public String toString() {
updateMetrics();
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append("start=");
sb.append(start != null ? start.toString("yyyy-MM-dd") : "");
sb.append(", ");
sb.append("end=");
sb.append(end != null ? end.toString("yyyy-MM-dd") : "");
sb.append(", trades=");
sb.append(trades.size());
sb.append(", trades/day=");
sb.append(Util.round(tradesPerDay, 2));
sb.append(", pnl=");
sb.append(Util.round(profitLoss, 2));
sb.append(", return/year=");
sb.append(Util.round(annualizedReturn, 2));
sb.append(", pi=");
sb.append(Util.round(performanceIndex, 2));
sb.append(", sr=");
sb.append(Util.round(sharpeRatio, 2));
sb.append(", pf=");
sb.append(Util.round(profitFactor, 2));
sb.append(", mdd=");
sb.append(Util.round(maxDrawDown, 2));
sb.append(", win ratio=");
sb.append(Util.round(winRate, 2));
sb.append(", long/short ratio=");
sb.append(Util.round(longShort, 2));
sb.append("]");
return sb.toString();
}
}