package org.cryptocoinpartners.schema; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.Nullable; import javax.persistence.Cacheable; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.NamedAttributeNode; import javax.persistence.NamedEntityGraph; import javax.persistence.NamedEntityGraphs; import javax.persistence.NamedSubgraph; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Transient; import org.cryptocoinpartners.enumeration.PositionEffect; import org.cryptocoinpartners.schema.dao.Dao; import org.cryptocoinpartners.schema.dao.PositionDao; import org.cryptocoinpartners.util.Remainder; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; /** * A Position represents an amount of some Asset within an Exchange. If the Position is related to an Order * then the Position is being held in reserve (not tradeable) to cover the costs of the open Order. * * @author Tim Olson */ @Entity @Cacheable @NamedEntityGraphs({ @NamedEntityGraph(name = "graph.Position.fills", attributeNodes = @NamedAttributeNode(value = "fills", subgraph = "fills"), subgraphs = { @NamedSubgraph(name = "fills", attributeNodes = @NamedAttributeNode("order")) }), @NamedEntityGraph(name = "graph.Position.portfolio", attributeNodes = @NamedAttributeNode("portfolio")) // @NamedAttributeNode("sender"), // @NamedAttributeNode("body") }) public class Position extends Holding { @Inject protected PositionDao positionDao; protected Portfolio portfolio; protected static final DateTimeFormatter FORMAT = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); // private static final SimpleDateFormat FORMAT = new SimpleDateFormat("dd.MM.yyyy kk:mm:ss"); protected static final String SEPARATOR = ","; // public Position(Portfolio portfolio, Exchange exchange, Market market, Asset asset, Amount volume, Amount price) { // // this.exchange = exchange; // this.market = market; // this.volume = volume; // this.volumeCount = volume.toBasis(market.getVolumeBasis(), Remainder.ROUND_EVEN).getCount(); // this.longVolume = volume.isPositive() ? volume : this.longVolume; // this.longVolumeCount = volume.isPositive() ? volume.toBasis(market.getVolumeBasis(), Remainder.ROUND_EVEN).getCount() : this.longVolumeCount; // this.shortVolume = volume.isNegative() ? volume : this.shortVolume; // this.shortVolumeCount = volume.isNegative() ? volume.toBasis(market.getVolumeBasis(), Remainder.ROUND_EVEN).getCount() : this.shortVolumeCount; // this.longAvgPrice = volume.isPositive() ? price : this.longAvgPrice; // this.shortAvgPrice = volume.isNegative() ? price : this.shortAvgPrice; // this.asset = asset; // this.portfolio = portfolio; // } @AssistedInject public Position(@Assisted Fill fill) { this.fills = new CopyOnWriteArrayList<Fill>(); fill.setPosition(this); addFill(fill); this.exchange = fill.getMarket().getExchange(); this.market = fill.getMarket(); // this.volume = fill.getVolume(); //this.volumeCount = volume.toBasis(market.getVolumeBasis(), Remainder.ROUND_EVEN).getCount(); //this.longVolume = volume.isPositive() ? volume : this.longVolume; //this.longVolumeCount = volume.isPositive() ? volume.toBasis(market.getVolumeBasis(), Remainder.ROUND_EVEN).getCount() : this.longVolumeCount; //this.shortVolume = volume.isNegative() ? volume : this.shortVolume; //this.shortVolumeCount = volume.isNegative() ? volume.toBasis(market.getVolumeBasis(), Remainder.ROUND_EVEN).getCount() : this.shortVolumeCount; //this.longAvgPrice = volume.isPositive() ? fill.getPrice() : this.longAvgPrice; //this.shortAvgPrice = volume.isNegative() ? fill.getPrice() : this.shortAvgPrice; //this.shortAvgStopPrice = fill.getStopPrice() != null && fill.isShort() ? fill.getStopPrice() : DecimalAmount.ZERO; //this.longAvgStopPrice = fill.getStopPrice() != null && fill.isLong() ? fill.getStopPrice() : DecimalAmount.ZERO; this.asset = fill.getMarket().getListing().getBase(); // this.id = getId(); this.portfolio = fill.getPortfolio(); // fill.getPortfolio().addPosition(this); } @AssistedInject public Position(@Assisted Collection<Fill> fills) { this.fills = new CopyOnWriteArrayList<Fill>(); this.addFill(fills); int index = 0; if (!fills.isEmpty()) { for (Fill fill : fills) { if (index > 0) break; this.exchange = fill.getMarket().getExchange(); this.market = fill.getMarket(); this.asset = fill.getMarket().getListing().getBase(); this.portfolio = fill.getPortfolio(); index++; } // this.id = getId(); } } @Transient public boolean isOpen() { return ((getLongVolume() != null && !getLongVolume().isZero()) || (getShortVolume() != null && !getShortVolume().isZero())); } @Transient public boolean isLong() { return (getVolume() != null && getVolume().isPositive()); } @Transient public boolean isShort() { return (getVolume() != null && getVolume().isNegative()); } @Transient public boolean isFlat() { //if (getVolume()!=null) return ((getLongVolume() == null && getLongVolume() == null) || (getLongVolume().isZero() && getShortVolume().isZero())); } @ManyToOne(optional = false) public Market getMarket() { return market; } // @Transient // @Inject // public PositionJpaDao getDao(PositionJpaDao localPositionDao) { // if (positionDao != null) // return positionDao; // else // return localPositionDao; // } public @ManyToOne @JoinColumn(name = "portfolio") Portfolio getPortfolio() { return portfolio; } public void setPortfolio(Portfolio portfolio) { this.portfolio = portfolio; } @Transient public Amount getMarginAmount() { Amount marginAmount = DecimalAmount.ZERO; if (isOpen() && marginAmount != null) { return marginAmount; } else { return DecimalAmount.ONE; } } @Transient public Amount getVolume() { // if (volume == null) // volume = new DiscreteAmount(volumeCount, market.getVolumeBasis()); //return volume; if (getLongVolume() != null && getShortVolume() != null) { return getLongVolume().plus(getShortVolume()); } else if (getLongVolume() != null) { return getLongVolume(); } else if (getShortVolume() != null) { return getShortVolume(); } else { return DecimalAmount.ZERO; } } @Transient public Amount getAvgPrice() { // if (volume == null) // volume = new DiscreteAmount(volumeCount, market.getVolumeBasis()); //return volume; return (getVolume().isNegative()) ? getShortAvgPrice() : getLongAvgPrice(); // return ((getLongAvgPrice().times(getLongVolume(), Remainder.ROUND_EVEN)).plus(getShortAvgPrice().times(getShortVolume(), Remainder.ROUND_EVEN))) // .dividedBy(getLongVolume().plus(getShortVolume()), Remainder.ROUND_EVEN); } @Transient public Amount getAvgStopPrice() { // if (volume == null) // volume = new DiscreteAmount(volumeCount, market.getVolumeBasis()); //return volume; return (getVolume().isNegative()) ? getShortAvgStopPrice() : getLongAvgStopPrice(); } @Transient public Amount getOpenVolume() { if (getMarket() == null) return DecimalAmount.ZERO; // if (longVolume == null) Amount openVolume = new DiscreteAmount(getOpenVolumeCount(), getMarket().getVolumeBasis()); return openVolume; } @Transient public Amount getLongVolume() { if (getMarket() == null) return DecimalAmount.ZERO; // if (longVolume == null) Amount longVolume = new DiscreteAmount(getLongVolumeCount(), getMarket().getVolumeBasis()); return longVolume; } @Transient public Amount getShortVolume() { if (getMarket() == null) return DecimalAmount.ZERO; // if (shortVolume == null) Amount shortVolume = new DiscreteAmount(getShortVolumeCount(), getMarket().getVolumeBasis()); return shortVolume; } @Transient public synchronized Amount getLongAvgPrice() { // if (longAvgPrice == null) { Amount longCumVolume = DecimalAmount.ZERO; Amount longAvgPrice = DecimalAmount.ZERO; Iterator<Fill> itf = getFills().iterator(); while (itf.hasNext()) { // for (Fill pos : getFills()) { Fill pos = itf.next(); if (pos.isLong() && !(longCumVolume.plus(pos.getOpenVolume()).isZero())) { longAvgPrice = longAvgPrice == null ? DecimalAmount.ZERO : ((longAvgPrice.times(longCumVolume, Remainder.ROUND_EVEN)).plus(pos.getOpenVolume() .times(pos.getPrice(), Remainder.ROUND_EVEN))).dividedBy(longCumVolume.plus(pos.getOpenVolume()), Remainder.ROUND_EVEN); longCumVolume = longCumVolume.plus(pos.getOpenVolume()); } } return longAvgPrice; } public <T> T find() { // synchronized (persistanceLock) { try { return (T) positionDao.find(Order.class, this.getId()); //if (duplicate == null || duplicate.isEmpty()) } catch (Exception | Error ex) { return null; //System.out.println("Unable to perform request in " + this.getClass().getSimpleName() + ":find, full stack trace follows:" + ex); // ex.printStackTrace(); } } @Transient public synchronized Amount getShortAvgPrice() { // if (shortAvgPrice == null) { Amount shortCumVolume = DecimalAmount.ZERO; Amount shortAvgPrice = DecimalAmount.ZERO; Iterator<Fill> itf = getFills().iterator(); while (itf.hasNext()) { // for (Fill pos : getFills()) { Fill pos = itf.next(); if (pos.isShort() && !(shortCumVolume.plus(pos.getOpenVolume()).isZero())) { if (pos.getOpenVolume() == null) System.out.println("douggie"); shortAvgPrice = shortAvgPrice == null ? DecimalAmount.ZERO : ((shortAvgPrice.times(shortCumVolume, Remainder.ROUND_EVEN)).plus(pos .getOpenVolume().times(pos.getPrice(), Remainder.ROUND_EVEN))) .dividedBy(shortCumVolume.plus(pos.getOpenVolume()), Remainder.ROUND_EVEN); shortCumVolume = shortCumVolume.plus(pos.getOpenVolume()); } } // } return shortAvgPrice; } @Transient public Amount getLongAvgStopPrice() { // if (longAvgStopPrice == null) { Amount longCumVolume = DecimalAmount.ZERO; Amount longAvgStopPrice = DecimalAmount.ZERO; Iterator<Fill> itf = getFills().iterator(); while (itf.hasNext()) { // for (Fill pos : getFills()) { Fill pos = itf.next(); if (pos.isLong()) { Amount parentStopPrice = null; if (pos.getOrder() != null && pos.getOrder().getParentOrder() != null && pos.getOrder().getParentOrder().getStopAmount() != null) parentStopPrice = pos.getPrice().minus(pos.getOrder().getParentOrder().getStopAmount()); // Amount stopPrice = (pos.getStopPrice() == null) ? parentStopPrice : pos.getStopPrice(); Amount stopPrice = pos.getStopPrice(); if (stopPrice == null) continue; if (stopPrice != null) longAvgStopPrice = ((longAvgStopPrice.times(longCumVolume, Remainder.ROUND_EVEN)).plus(pos.getOpenVolume().times(stopPrice, Remainder.ROUND_EVEN))).dividedBy(longCumVolume.plus(pos.getOpenVolume()), Remainder.ROUND_EVEN); longCumVolume = longCumVolume.plus(pos.getOpenVolume()); } } // } return longAvgStopPrice; } protected void setMarket(Market market) { this.market = market; } @Transient public Amount getShortAvgStopPrice() { // if (shortAvgStopPrice == null) { Amount shortAvgStopPrice = DecimalAmount.ZERO; Amount shortCumVolume = DecimalAmount.ZERO; // if (shortAvgStopPrice == null) Iterator<Fill> itf = getFills().iterator(); while (itf.hasNext()) { // for (Fill pos : getFills()) { Fill pos = itf.next(); if (pos.isShort()) { Amount parentStopPrice = null; if (pos.getOrder() != null && pos.getOrder().getParentOrder() != null && pos.getOrder().getParentOrder().getStopAmount() != null) parentStopPrice = pos.getPrice().plus(pos.getOrder().getParentOrder().getStopAmount()); // Amount stopPrice = (pos.getStopPrice() == null) ? parentStopPrice : pos.getStopPrice(); Amount stopPrice = pos.getStopPrice(); if (stopPrice == null) continue; if (stopPrice != null) shortAvgStopPrice = ((shortAvgStopPrice.times(shortCumVolume, Remainder.ROUND_EVEN)).plus(pos.getOpenVolume().times(stopPrice, Remainder.ROUND_EVEN))).dividedBy(shortCumVolume.plus(pos.getOpenVolume()), Remainder.ROUND_EVEN); shortCumVolume = shortCumVolume.plus(pos.getOpenVolume()); } } //} return shortAvgStopPrice; } /** If the SpecificOrder is not null, then this Position is being held in reserve as payment for that Order */ /** * Modifies this Position in-place by the amount of the position argument. * @param position a Position to add to this one. * @return true iff the positions both have the same Asset and the same Exchange, in which case this Position * has modified its volume by the amount in the position argument. */ @Override public String toString() { return "Id=" + (getId() != null ? getId() : "") + SEPARATOR + "Exchange=" + exchange + (getShortVolume() != null ? (SEPARATOR + ", Short Qty=" + getShortVolume()) : "") + (getShortAvgPrice() != null ? (SEPARATOR + ", Short Avg Price=" + getShortAvgPrice()) : "") + (getShortAvgStopPrice() != null ? (SEPARATOR + ", Short Avg Stop Price=" + getShortAvgStopPrice()) : "") + (getLongVolume() != null ? (SEPARATOR + "Long Qty=" + getLongVolume()) : "") + (getLongAvgPrice() != null ? (SEPARATOR + "Long Avg Price=" + getLongAvgPrice()) : "") + (getLongAvgStopPrice() != null ? (SEPARATOR + "Long Avg Stop Price=" + getLongAvgStopPrice()) : "") + ", Net Qty=" + getVolume().toString() + " Vol Count=" + getVolumeCount() + ", Entry Date=" + ", Instrument=" + asset; } //JPA public Position() { } @Nullable @Transient protected long getVolumeCount() { long volumeCount = 0; // reset(); if (hasFills()) { Iterator<Fill> itf = getFills().iterator(); while (itf.hasNext()) { // for (Fill pos : getFills()) { Fill fill = itf.next(); volumeCount += fill.getOpenVolumeCount(); } } return volumeCount; } @Nullable @Transient protected long getOpenVolumeCount() { long volumeCount = 0; // reset(); if (hasFills()) { Iterator<Fill> itf = getFills().iterator(); while (itf.hasNext()) { // for (Fill pos : getFills()) { Fill fill = itf.next(); if (fill.getPositionEffect() == null || fill.getPositionEffect() == PositionEffect.OPEN) volumeCount += fill.getOpenVolumeCount(); } } return volumeCount; } @Nullable @Transient protected long getLongVolumeCount() { // reset(); //System.out.println(getFills().toString()); Iterator<Fill> itf = getFills().iterator(); long longVolumeCount = 0; while (itf.hasNext()) { // for (Fill pos : getFills()) { Fill fill = itf.next(); if (fill.getPositionEffect() != null && ((fill.getPositionEffect() == PositionEffect.OPEN && fill.isLong()) || (fill.getPositionEffect() == PositionEffect.CLOSE && fill .isShort()))) { longVolumeCount += fill.getOpenVolumeCount(); } else if ((fill.getPositionEffect() == null || fill.getPositionEffect() == PositionEffect.OPEN) && fill.isLong()) longVolumeCount += fill.getOpenVolumeCount(); } long vol = this.getVolumeCount(); // if (vol > 0 && vol != longVolumeCount) //System.out.println("issue with long volume cacl"); return longVolumeCount; } @Nullable @Transient protected long getShortVolumeCount() { long shortVolumeCount = 0; // reset(); if (hasFills()) { Iterator<Fill> itf = getFills().iterator(); while (itf.hasNext()) { // for (Fill pos : getFills()) { Fill fill = itf.next(); // if it is entering & short or exiting and long if (fill.getPositionEffect() != null && ((fill.getPositionEffect() == PositionEffect.OPEN && fill.isShort()) || (fill.getPositionEffect() == PositionEffect.CLOSE && fill .isLong()))) { shortVolumeCount += fill.getOpenVolumeCount(); } else if ((fill.getPositionEffect() == null || fill.getPositionEffect() == PositionEffect.OPEN) && fill.isShort()) shortVolumeCount += fill.getOpenVolumeCount(); } } long vol = this.getVolumeCount(); //System.out.println(getFills()); // if (vol < 0 && vol != shortVolumeCount) // System.out.println("issue with short volume cacl"); return shortVolumeCount; } // fetch = FetchType.EAGER, @OneToMany(mappedBy = "position") //, fetch = FetchType.EAGER) // ;;@OrderColumn(name = "time") //, orphanRemoval = true, cascade = CascadeType.REMOVE) @OrderBy //, cascade = { CascadeType.MERGE, CascadeType.REFRESH }) public List<Fill> getFills() { return fills; } @Override public void merge() { try { positionDao.merge(this); //if (duplicate == null || duplicate.isEmpty()) } catch (Exception | Error ex) { System.out.println("Unable to perform request in " + this.getClass().getSimpleName() + ":merge, full stack trace follows:" + ex); // ex.printStackTrace(); } } @Override public synchronized void delete() { try { positionDao.delete(this); //if (duplicate == null || duplicate.isEmpty()) } catch (Exception | Error ex) { System.out.println("Unable to perform request in " + this.getClass().getSimpleName() + ":remove, full stack trace follows:" + ex); // ex.printStackTrace(); } } @Override public EntityBase refresh() { try { return positionDao.refresh(this); //if (duplicate == null || duplicate.isEmpty()) } catch (Exception | Error ex) { System.out.println("Unable to perform request in " + this.getClass().getSimpleName() + ":refresh, full stack trace follows:" + ex); // ex.printStackTrace(); } return null; } @Override @Transient public Dao getDao() { return positionDao; } @Override public void persit() { // List<Fill> duplicate = fillDao.queryList(Fill.class, "select f from Fill f where f=?1", this); // synchronized (persistanceLock) { // List<Position> duplicate = positionDao.queryList(Position.class, "select p from Position p where p=?1", this); // if (this.hasFills()) { // for (Fill fill : this.getFills()) // PersistUtil.merge(fill); // } // if (duplicate == null || duplicate.isEmpty()) try { positionDao.persist(this); //if (duplicate == null || duplicate.isEmpty()) } catch (Exception | Error ex) { System.out.println("Unable to perform request in " + this.getClass().getSimpleName() + ":persist, full stack trace follows:" + ex); // ex.printStackTrace(); } // else // positionDao.merge(this); //PersistUtil.insert(this); // else // PersistUtil.merge(this); // } // if (hasFills()) { // Iterator<Fill> itf = getFills().iterator(); // while (itf.hasNext()) { // for (Fill pos : getFills()) { // Fill fill = itf.next(); //if (fill.getPosition() == this) // fill.persit(); //PersistUtil.merge(fill); // } // } } //protected void Merge() { // synchronized (persistanceLock) { // if (this.hasFills()) { // for (Fill fill : this.getFills()) // PersistUtil.merge(fill); // } // .. PersistUtil.merge(this); // } // } public synchronized boolean addFill(Fill fill) { // synchronized (lock) { if (getFills().contains(fill)) return false; else return (getFills().add(fill)); //TODO We should do a check to make sure the fill is the samme attributes as position //} //this.exchange = fill.getMarket().getExchange(); //this.market = fill.getMarket(); //this.asset = fill.getMarket().getListing().getBase(); //this.portfolio = fill.getPortfolio(); // reset(); } public synchronized void addFill(Collection<Fill> fills) { // synchronized (lock) { getFills().addAll(fills); //TODO We should do a check to make sure the fill is the samme attributes as position //} //this.exchange = fill.getMarket().getExchange(); //this.market = fill.getMarket(); //this.asset = fill.getMarket().getListing().getBase(); //this.portfolio = fill.getPortfolio(); // reset(); } public synchronized void removeFills(Collection<Fill> removedFills) { // synchronized (lock) { if (getFills().removeAll(removedFills)) for (Fill removedFill : removedFills) removedFill.setPosition(null); // removeFill(removedFill); //ODO We should do a check to make sure the fill is the samme attributes as position //} //this.exchange = fill.getMarket().getExchange(); //this.market = fill.getMarket(); //this.asset = fill.getMarket().getListing().getBase(); //this.portfolio = fill.getPortfolio(); // reset(); } public synchronized void removeAllFills() { // synchronized (lock) { // if (this.fills.removeAll(removedFills)) for (Fill removedFill : getFills()) removedFill.setPosition(null); getFills().clear(); // removeFill(removedFill); //ODO We should do a check to make sure the fill is the samme attributes as position //} //this.exchange = fill.getMarket().getExchange(); //this.market = fill.getMarket(); //this.asset = fill.getMarket().getListing().getBase(); //this.portfolio = fill.getPortfolio(); // reset(); } public synchronized void removeFill(Fill fill) { // synchronized (lock) { System.out.println("removing fill: " + fill + " from position: " + this); if (getFills().remove(fill)) fill.setPosition(null); //TODO We should do a check to make sure the fill is the samme attributes as position //} //this.exchange = fill.getMarket().getExchange(); //this.market = fill.getMarket(); //this.asset = fill.getMarket().getListing().getBase(); //this.portfolio = fill.getPortfolio(); // reset(); } // public void reset() { // this.volume = null; // this.longVolume = null; // this.shortVolume = null; // this.volumeCount = 0; // this.longVolumeCount = 0; // this.shortVolumeCount = 0; // this.longAvgPrice = null; // this.shortAvgPrice = null; // // this.longAvgStopPrice = null; // this.shortAvgStopPrice = null; // } // public void removeFill(Fill fill) { // synchronized (lock) { //this.fills.remove(fill); //fill.setPosition(null); //} // fill.persit(); //PersistUtil.merge(fill); // this.exchange = fill.getMarket().getExchange(); // this.market = fill.getMarket(); // this.portfolio = fill.getPortfolio(); //reset(); // } @Transient public boolean hasFills() { return !getFills().isEmpty(); } protected void setFills(List<Fill> fills) { // reset(); this.fills = fills; } // private Amount longVolume = DecimalAmount.ZERO; //private Amount shortVolume = DecimalAmount.ZERO; //private Amount volume = DecimalAmount.ZERO; private Market market; //private Amount longAvgPrice = DecimalAmount.ZERO; //private Amount shortAvgPrice = DecimalAmount.ZERO; //private Amount longAvgStopPrice = DecimalAmount.ZERO; //private Amount shortAvgStopPrice = DecimalAmount.ZERO; //private final Amount marginAmount = DecimalAmount.ZERO; //private long longVolumeCount; //private long shortVolumeCount; //private long volumeCount; //private SpecificOrder order; private List<Fill> fills; private static Object lock = new Object(); private static Object persistanceLock = new Object(); }