/******************************************************************************* * 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.service; import java.util.Collections; import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.quantcomponents.algo.IPosition; import com.quantcomponents.algo.ITrade; import com.quantcomponents.algo.ITradingListener; import com.quantcomponents.algo.PositionBean; import com.quantcomponents.core.model.IContract; import com.quantcomponents.core.model.ISeriesPoint; import com.quantcomponents.core.model.OrderSide; import com.quantcomponents.core.model.beans.ContractBean; public class PositionCalculator implements ITradingListener { private final Map<IContract, PositionBean> positions = new ConcurrentHashMap<IContract, PositionBean>(); private final Map<IContract, PositionBean> readonlyPositions = Collections.unmodifiableMap(positions); public Map<IContract, ? extends IPosition> getPositions() { return readonlyPositions; } public synchronized void reset() { positions.clear(); } @Override public synchronized void onTrade(ITrade trade) { IContract contract = trade.getOrder().getContract(); IContract currency = ContractBean.cash(contract.getCurrency()); PositionBean contractPosition = positions.get(contract); if (contractPosition == null) { contractPosition = new PositionBean(); positions.put(contract, contractPosition); } PositionBean cashPosition = positions.get(currency); if (cashPosition == null) { cashPosition = new PositionBean(); cashPosition.setAveragePrice(1.0); cashPosition.setMarketPrice(1.0); positions.put(currency, cashPosition); } Integer contractMultiplier = contract.getMultiplier(); if (contractMultiplier == null || contractMultiplier == 0) { contractMultiplier = 1; } contractPosition.setMarketPrice(trade.getExecutionPrice()); contractPosition.setTimestamp(trade.getExecutionTime()); cashPosition.setTimestamp(trade.getExecutionTime()); double residueSignedPositionAmount = contractPosition.getSignedAmount(); int residueTradeAmount = trade.getAmount(); // reducing position if (residueSignedPositionAmount != 0) { if (residueSignedPositionAmount > 0 && trade.getOrder().getSide() == OrderSide.SELL) { // reducing long position double positionReduction = Math.min(residueSignedPositionAmount, residueTradeAmount); contractPosition.setRealizedPnl(contractPosition.getRealizedPnl() + (trade.getAveragePrice() - contractPosition.getAveragePrice()) * contractMultiplier * positionReduction); residueSignedPositionAmount -= positionReduction; residueTradeAmount -= positionReduction; } else if (residueSignedPositionAmount < 0 && trade.getOrder().getSide() == OrderSide.BUY) { // reducing short position double positionReduction = Math.min(-residueSignedPositionAmount, residueTradeAmount); contractPosition.setRealizedPnl(contractPosition.getRealizedPnl() + (contractPosition.getAveragePrice() - trade.getAveragePrice()) * contractMultiplier * positionReduction); residueSignedPositionAmount += positionReduction; residueTradeAmount -= positionReduction; } } double newSignedPositionAmount = residueSignedPositionAmount; if (newSignedPositionAmount == 0) { contractPosition.setAveragePrice(0.0); } // building position if (residueTradeAmount > 0) { if (trade.getOrder().getSide() == OrderSide.BUY) { // building long position from 0 or more newSignedPositionAmount = residueSignedPositionAmount + residueTradeAmount; contractPosition.setAveragePrice((contractPosition.getAveragePrice() * residueSignedPositionAmount + trade.getAveragePrice() * residueTradeAmount) / newSignedPositionAmount); } else { // building long position from 0 or less newSignedPositionAmount = residueSignedPositionAmount - residueTradeAmount; contractPosition.setAveragePrice((contractPosition.getAveragePrice() * residueSignedPositionAmount - trade.getAveragePrice() * residueTradeAmount) / newSignedPositionAmount); } } contractPosition.setSignedAmount(newSignedPositionAmount); double cashChange = trade.getAveragePrice() * trade.getAmount() * contractMultiplier * (trade.getOrder().getSide() == OrderSide.BUY ? -1 : 1);; cashPosition.setSignedAmount(cashPosition.getSignedAmount() + cashChange); recalculateMarketValueAndUPnl(); } @Override public void onPriceUpdate(IContract contract, ISeriesPoint<Date, Double> price) { updateMarketPrice(contract, price.getIndex(), price.getValue()); } private void updateMarketPrice(IContract contract, Date timestamp, double price) { PositionBean position = positions.get(contract); if (position != null) { if (price != position.getMarketPrice()) { position.setMarketPrice(price); position.setTimestamp(timestamp); recalculateMarketValueAndUPnl(); } } } private void recalculateMarketValueAndUPnl() { for (Map.Entry<IContract, PositionBean> entry : positions.entrySet()) { Integer contractMultiplier = entry.getKey().getMultiplier(); if (contractMultiplier == null || contractMultiplier == 0) { contractMultiplier = 1; } PositionBean position = entry.getValue(); position.setMarketValue(position.getMarketPrice() * position.getSignedAmount() * contractMultiplier); position.setUnrealizedPnl(position.getMarketValue() - position.getAveragePrice() * position.getSignedAmount() * contractMultiplier); } } }