/*******************************************************************************
* Copyright (c) 2013 Luigi Sgro. All rights reserved. This
* program and the accompanying materials are made available under the terms of
* the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Luigi Sgro - initial API and implementation
******************************************************************************/
package com.quantcomponents.algo;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.quantcomponents.core.model.IContract;
import com.quantcomponents.core.model.ISeries;
import com.quantcomponents.core.model.ISeriesAugmentable;
import com.quantcomponents.core.model.ISeriesListener;
import com.quantcomponents.core.model.ISeriesPoint;
import com.quantcomponents.core.model.ISeriesProcessor;
import com.quantcomponents.core.model.OrderSide;
import com.quantcomponents.core.series.SimplePoint;
/**
* Processor that listens to trades and prices and generates a series of realtime trade statistics
*/
public class TradeStatsProcessor implements ISeriesProcessor<Date, Double>, IPositionProvider, ITradingStatsProvider, ISeriesListener<Date, Double> {
public static final String INPUT_SERIES_NAME = "TRADES_AND_POSITIONS";
private final Map<IContract, IPosition> positions = new ConcurrentHashMap<IContract, IPosition>();
private volatile ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>> inputSeries;
private volatile ISeriesAugmentable<Date, Double, ISeriesPoint<Date, Double>> outputSeries;
private volatile TradeStatsPoint currentTradePoint;
private volatile SimplePoint highestEquityPoint;
private volatile SimplePoint lowestEquityPoint;
private volatile ITradeStatsPoint worstTrade;
private volatile ITradeStatsPoint bestTrade;
private volatile SimplePoint startOfMaxDrawdown;
private volatile SimplePoint endOfMaxDrawdown;
private volatile SimplePoint startOfMaxRunup;
private volatile SimplePoint endOfMaxRunup;
@Override
public void wire(Map<String, ? extends ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>>> inputSeriesMap, ISeriesAugmentable<Date, Double, ISeriesPoint<Date, Double>> outputSeries) {
this.inputSeries = inputSeriesMap.get(INPUT_SERIES_NAME);
this.outputSeries = outputSeries;
if (!inputSeries.isEmpty()) {
for (ISeriesPoint<Date, Double> point : this.inputSeries) {
onItemAdded(point);
}
}
this.inputSeries.addSeriesListener(this);
}
@Override
public void unwire() {
if (inputSeries != null) {
inputSeries.removeSeriesListener(this);
inputSeries = null;
}
outputSeries = null;
}
@Override
public Map<IContract, IPosition> getPositions() {
return new HashMap<IContract, IPosition>(positions);
}
@Override
public SimplePoint getHighestEquityPoint() {
return highestEquityPoint;
}
@Override
public SimplePoint getLowestEquityPoint() {
return lowestEquityPoint;
}
@Override
public ITradeStatsPoint getWorstTrade() {
return worstTrade;
}
@Override
public ITradeStatsPoint getBestTrade() {
return bestTrade;
}
@Override
public SimplePoint getStartOfMaxDrawdown() {
return startOfMaxDrawdown;
}
@Override
public SimplePoint getEndOfMaxDrawdown() {
return endOfMaxDrawdown;
}
@Override
public SimplePoint getStartOfMaxRunup() {
return startOfMaxRunup;
}
@Override
public SimplePoint getEndOfMaxRunup() {
return endOfMaxRunup;
}
@Override
public void onItemUpdated(ISeriesPoint<Date, Double> existingItem, ISeriesPoint<Date, Double> updatedItem) { }
@Override
public void onItemAdded(ISeriesPoint<Date, Double> newItem) {
if (newItem instanceof IPositionPoint) {
IPositionPoint positionPoint = (IPositionPoint) newItem;
onPositionUpdate(positionPoint.getContract(), positionPoint.getPosition());
} else if (newItem instanceof ITradePoint) {
ITradePoint tradePoint = (ITradePoint) newItem;
onTrade(tradePoint.getTrade());
}
}
private void onTrade(ITrade trade) {
changeCurrentTrade(trade);
updatePriceStats(trade.getExecutionTime());
}
private void onPositionUpdate(IContract contract, IPosition position) {
positions.put(contract, position);
if (currentTradePoint != null) {
ITrade currentTrade = currentTradePoint.getTrade();
if (currentTrade.getOrder().getContract().equals(contract)) {
currentTradePoint.setTradePnl((position.getMarketPrice() - currentTrade.getAveragePrice()) * currentTrade.getAmount() * (currentTrade.getOrder().getSide() == OrderSide.BUY ? 1.0 : -1.0));
}
updateCurrentTradeStats();
updatePriceStats(position.getTimestamp());
}
}
private void changeCurrentTrade(ITrade nextTrade) {
if (currentTradePoint != null) {
ITrade currentTrade = currentTradePoint.getTrade();
currentTradePoint.setTradePnl(currentTrade.getAmount() * (nextTrade.getAveragePrice() - currentTrade.getAveragePrice()) * (currentTrade.getOrder().getSide() == OrderSide.BUY ? 1.0 : -1.0));
currentTradePoint.setTradeEnd(nextTrade.getExecutionTime());
updateCurrentTradeStats();
double tradePnl = currentTradePoint.getTradePnl();
if (worstTrade == null || tradePnl < worstTrade.getTradePnl()) {
worstTrade = currentTradePoint;
}
if (bestTrade == null || tradePnl > bestTrade.getTradePnl()) {
bestTrade = currentTradePoint;
}
if (outputSeries != null) {
outputSeries.updateTail(currentTradePoint);
}
}
currentTradePoint = new TradeStatsPoint(nextTrade, nextTrade.getExecutionTime());
if (outputSeries != null) {
outputSeries.insertFromTail(currentTradePoint);
}
}
private void updateCurrentTradeStats() {
double tradePnl = currentTradePoint.getTradePnl();
if (tradePnl < currentTradePoint.getMaxAdverseExcursion()) {
currentTradePoint.setMaxAdverseExcursion(tradePnl);
}
if (tradePnl > currentTradePoint.getMaxFavorableExcursion()) {
currentTradePoint.setMaxFavorableExcursion(tradePnl);
}
if (outputSeries != null) {
outputSeries.updateTail(currentTradePoint);
}
}
private void updatePriceStats(Date timestamp) {
double pnl = calculateTotalPnl();
SimplePoint point = new SimplePoint(timestamp, pnl);
if (lowestEquityPoint == null || pnl < lowestEquityPoint.getValue()) {
lowestEquityPoint = point;
}
if (highestEquityPoint == null || pnl > highestEquityPoint.getValue()) {
highestEquityPoint = point;
}
double currentRunup = pnl - lowestEquityPoint.getValue();
if (startOfMaxRunup == null || currentRunup > endOfMaxRunup.getValue() - startOfMaxRunup.getValue()) {
startOfMaxRunup = lowestEquityPoint;
endOfMaxRunup = point;
}
double currentDrawdown = pnl - highestEquityPoint.getValue();
if (startOfMaxDrawdown == null || currentDrawdown < endOfMaxDrawdown.getValue() - startOfMaxDrawdown.getValue()) {
startOfMaxDrawdown = highestEquityPoint;
endOfMaxDrawdown = point;
}
}
private double calculateTotalPnl() {
double pnl = 0.0;
for (IPosition position : positions.values()) {
pnl += position.getUnrealizedPnl();
pnl += position.getRealizedPnl();
}
return pnl;
}
}