package org.cryptocoinpartners.schema;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import javax.persistence.Cacheable;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.NamedEntityGraphs;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.PostPersist;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.cryptocoinpartners.enumeration.FillType;
import org.cryptocoinpartners.enumeration.PositionEffect;
import org.cryptocoinpartners.enumeration.PositionType;
import org.cryptocoinpartners.schema.dao.Dao;
import org.cryptocoinpartners.schema.dao.FillDao;
import org.cryptocoinpartners.util.EM;
import org.cryptocoinpartners.util.FeesUtil;
import org.hibernate.Hibernate;
import org.joda.time.Instant;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
/**
* A Fill represents some completion of an Order. The volume of the Fill might be less than the requested volume of the
* Order
*
* @author Tim Olson
*/
@Entity
@Cacheable
@Table(indexes = { @Index(columnList = "`order`"), @Index(columnList = "market"), @Index(columnList = "portfolio"), @Index(columnList = "position") })
@NamedQueries({ @NamedQuery(name = "Fill.findFill", query = "select f from Fill f where id=?1") })
//
@NamedEntityGraphs({ @NamedEntityGraph(name = "fillWithChildOrders", attributeNodes = { @NamedAttributeNode("fillChildOrders") })
//})
//, subgraph = "childrenWithFills") }, subgraphs = { @NamedSubgraph(name = "childrenWithFills", attributeNodes = { @NamedAttributeNode("fills") }) })
// @NamedEntityGraph(name = "fillWithChildOrders", attributeNodes = { @NamedAttributeNode(value = "fillChildOrders", subgraph = "childrenWithFills") }, subgraphs = { @NamedSubgraph(name = "childrenWithFills", attributeNodes = { @NamedAttributeNode("fills") }) })
// @NamedSubgraph(name = "fills", attributeNodes = @NamedAttributeNode(value = "fills", subgraph = "order"))
//,@NamedSubgraph(name = "order", attributeNodes = @NamedAttributeNode("order"))
})
public class Fill extends RemoteEvent {
private static final DateTimeFormatter FORMAT = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
private static Object lock = new Object();
// private static final SimpleDateFormat FORMAT = new SimpleDateFormat("dd.MM.yyyy kk:mm:ss");
private static final String SEPARATOR = ",";
private PositionEffect positionEffect;
@Inject
protected FillDao fillDao;
protected static Logger log = LoggerFactory.getLogger("org.cryptocoinpartners.fill");
// @Inject
@Inject
public Fill(@Assisted SpecificOrder order, @Assisted("fillTime") Instant time, @Assisted("fillTimeReceived") Instant timeReceived, @Assisted Market market,
@Assisted("fillPriceCount") long priceCount, @Assisted("fillVolumeCount") long volumeCount, @Assisted String remoteKey) {
super(time, timeReceived, remoteKey);
this.fillChildOrders = new CopyOnWriteArrayList<Order>();
this.transactions = new CopyOnWriteArrayList<Transaction>();
this.priceCount = priceCount;
this.volumeCount = volumeCount;
this.openVolumeCount = volumeCount;
this.positionType = (openVolumeCount > 0) ? PositionType.LONG : PositionType.SHORT;
this.order = order;
this.order.addFill(this);
this.remoteKey = order.getId().toString();
this.market = market;
if (priceCount == 0)
this.priceCount = priceCount;
this.portfolio = order.getPortfolio();
this.stopAmountCount = (order.getStopAmount() != null) ? order.getStopAmount().getCount() : 0;
this.positionEffect = order.getPositionEffect();
// this.id = getId();
this.version = getVersion();
}
// @Override
// @Basic(optional = true)
// public String getRemoteKey() {
// return remoteKey;
// }
//
// @Override
// public void setRemoteKey(@Nullable String remoteKey) {
// this.remoteKey = remoteKey;
// }
public Fill(@Assisted SpecificOrder order, @Assisted Instant time, @Assisted Instant timeReceived, @Assisted Market market, @Assisted long priceCount,
@Assisted long volumeCount, @Assisted Amount commission, @Assisted String remoteKey) {
super(time, timeReceived, remoteKey);
this.fillChildOrders = new CopyOnWriteArrayList<Order>();
this.transactions = new CopyOnWriteArrayList<Transaction>();
this.remoteKey = order.getId().toString();
this.order = order;
this.order.addFill(this);
this.market = market;
if (priceCount == 0)
this.priceCount = priceCount;
this.priceCount = priceCount;
this.volumeCount = volumeCount;
this.openVolumeCount = volumeCount;
this.positionType = (openVolumeCount > 0) ? PositionType.LONG : PositionType.SHORT;
this.commission = commission;
this.portfolio = order.getPortfolio();
this.stopAmountCount = (order.getStopAmount() != null) ? order.getStopAmount().getCount() : 0;
this.positionEffect = order.getPositionEffect();
}
public <T> T find() {
// synchronized (persistanceLock) {
try {
return (T) fillDao.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();
}
}
// public @ManyToOne(cascade = { CascadeType.MERGE, CascadeType.REMOVE, CascadeType.REFRESH })
public @ManyToOne
//(cascade = { CascadeType.MERGE })
@JoinColumn(name = "`order`")
SpecificOrder getOrder() {
return order;
}
@Override
public synchronized void merge() {
// try {
// if (getOrder() != null)
// getOrder().persit();
// i//f (getPosition() != null)
// getPosition().persit();
// if (order != null)
// order.find();
try {
// find();
fillDao.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();
}
//fillDao.merge(this);
}
@Override
public EntityBase refresh() {
return fillDao.refresh(this);
}
@Override
public synchronized void persit() {
// Any @ManyToOne I need to persist first to get them in the EM context.
// synchronized (persistanceLock) {
// if (this.hasFills()) {
// for (Fill fill : this.getFills())
//PersistUtil.merge(fill);
//}
// if (getOrder() != null)
// getOrder().persit();
// if (getPosition() != null)
// getPosition().persit();
//List<Fill> duplicate = fillDao.queryList(Fill.class, "select f from Fill f where f=?1", this);
//if (duplicate == null || duplicate.isEmpty())
// if (fillDao != null)
// avoid session conflicts due to lazy loading.
//for (Order childOrder : this.getFillChildOrders())
// childOrder.p
// childOrder.persit();
// try {
fillDao.persist(this);
// } catch (Exception | Error ex) {
// fillDao.merge(this);
// System.out.println("Unable to perform request in " + this.getClass().getSimpleName() + ":persist, full stack trace follows:" + ex);
//ex.printStackTrace();
// }
// PersistUtil.insert(this);
//else
// fillDao.merge(this);
// PersistUtil.merge(this);
// }
//Iterator<Order> itc = getChildren().iterator();
//while (itc.hasNext()) {
// // for (Fill pos : getFills()) {
// Order order = itc.next();
// order.persit();
// }
// synchronized (persistanceLock) {
// if (this.hasFills()) {
// for (Fill fill : this.getFills())
//PersistUtil.merge(fill);
//}
//if (this.hasFills()) {
// for (Fill fill : getFills()) {
// if (this.hasChildren()) {
// for (Order order : this.getChildren())
// if (order.getParentFill() == this)
// order.persit();
//PersistUtil.merge(order);
// }
}
@Nullable
@ManyToOne(optional = true)
//, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
//cascade = { CascadeType.ALL })
@JoinColumn(name = "position")
public Position getPosition() {
return position;
}
@Transient
@Nullable
public Double getPriceAsDouble() {
Amount price = getPrice();
return price == null ? null : price.asDouble();
}
@Transient
@Nullable
public Double getPriceCountAsDouble() {
Long price = getPriceCount();
return price == null ? null : price.doubleValue();
}
@Transient
public boolean isLong() {
if (getOpenVolume() == null)
return getOpenVolume().isZero();
return getOpenVolume().isPositive();
}
public synchronized void addChildOrder(Order order) {
if (!getFillChildOrders().contains(order))
getFillChildOrders().add(order);
}
public synchronized void removeChildOrder(Order order) {
order.setParentOrder(null);
getFillChildOrders().remove(order);
}
@PostPersist
private void postPersist() {
//detach();
}
// @PrePersist
private void prePersist() {
if (getDao() != null) {
UUID orderId = null;
UUID positionId = null;
Order parentOrder = null;
Position fillPosition = null;
// context.
if (getOrder() != null) {
parentOrder = (getDao().find(Order.class, getOrder().getId()));
// orderId = (fillDao.queryZeroOne(UUID.class, "select o.id from Order o where o.id=?1", order.getId()));
if (parentOrder == null)
getDao().persist(getOrder());
// order.merge();
}
if (getPosition() != null) {
fillPosition = (getDao().find(Position.class, getPosition().getId()));
// positionId = (fillDao.queryZeroOne(UUID.class, "select p.id from Position p where p.id=?1", position.getId()));
if (fillPosition == null)
getDao().persist(getPosition());
// position.merge();
}
}
//detach();
}
@Override
public void detach() {
fillDao.detach(this);
}
//@Nullable
//@OneToMany(cascade = CascadeType.MERGE)
//(mappedBy = "parentFill")
//@OrderBy
//@Transient
//@OneToMany(mappedBy = "parentFill")
// @OneToMany(orphanRemoval = true)
// @JoinColumn(name = "parentFill")
//@Column
//@ElementCollection(targetClass = Order.class)
@Nullable
@OneToMany
//(cascade = CascadeType.PERSIST)
//, mappedBy = "order")
(mappedBy = "parentFill")
// @OrderColumn(name = "version")
// @OrderColumn(name = "time")
//, fetch = FetchType.EAGER)
//, cascade = CascadeType.MERGE)
//, fetch = FetchType.LAZY)
@OrderBy
public List<Order> getFillChildOrders() {
if (fillChildOrders == null)
return new CopyOnWriteArrayList<Order>();
// synchronized (//) {
return fillChildOrders;
// }
}
protected void setFillChildOrders(List<Order> children) {
this.fillChildOrders = children;
}
@Transient
public boolean hasChildren() {
return !getFillChildOrders().isEmpty();
}
@Transient
public boolean hasTransaction() {
return !getTransactions().isEmpty();
}
@Transient
public boolean isShort() {
return getOpenVolume().isNegative();
}
@Nullable
// @OneToMany(mappedBy = "fill")
//, orphanRemoval = true, cascade = CascadeType.REMOVE)
// , fetch = FetchType.EAGER)
// , cascade = { CascadeType.MERGE, CascadeType.REFRESH })
//, mappedBy = "fill")
//(fetch = FetchType.EAGER)
// @Transient
@OneToMany
//(cascade = CascadeType.PERSIST)
//, mappedBy = "order")
(mappedBy = "fill", orphanRemoval = true)
//, fetch = FetchType.EAGER)
//, cascade = CascadeType.MERGE)
//, fetch = FetchType.LAZY)
@OrderBy
public List<Transaction> getTransactions() {
if (transactions == null)
return new CopyOnWriteArrayList<Transaction>();
//synchronized (lock) {
return transactions;
// }
}
public synchronized void addTransaction(Transaction transaction) {
// synchronized (lock) {
if (!getTransactions().contains(transaction))
getTransactions().add(transaction);
// this.transactions.add(transaction);
// }
}
public synchronized void removeTransaction(Transaction transaction) {
transaction.setFill(null);
getTransactions().remove(transaction);
}
protected void setTransactions(List<Transaction> transactions) {
this.transactions = transactions;
}
@ManyToOne
public Market getMarket() {
return market;
}
@Transient
public DiscreteAmount getPrice() {
if (getPriceCount() == 0)
return null;
if (market.getPriceBasis() == 0)
return null;
return new DiscreteAmount(getPriceCount(), getMarket().getPriceBasis());
}
@Transient
public Amount getStopPrice() {
if (getStopPriceCount() == 0)
return null;
return new DiscreteAmount(getStopPriceCount(), getMarket().getPriceBasis());
}
@Transient
public Amount getTargetPrice() {
if (getTargetPriceCount() == 0)
return null;
return new DiscreteAmount(getTargetPriceCount(), getMarket().getPriceBasis());
}
public long getPriceCount() {
return priceCount;
}
public long getStopPriceCount() {
if (getOpenVolumeCount() != 0)
return stopPriceCount;
else
return 0;
}
public long getTargetPriceCount() {
return targetPriceCount;
}
@Transient
public Amount getVolume() {
if (getVolumeCount() == 0)
return null;
return new DiscreteAmount(getVolumeCount(), getMarket().getVolumeBasis());
}
public long getVolumeCount() {
return volumeCount;
}
@Transient
public Amount getOpenVolume() {
// if (openVolumeCount == 0)
// return null;
if (openVolume == null)
openVolume = new DiscreteAmount(getOpenVolumeCount(), getMarket().getVolumeBasis());
return openVolume;
}
public long getOpenVolumeCount() {
return openVolumeCount;
}
@Transient
public Amount getCommission() {
if (commission == null)
setCommission(FeesUtil.getCommission(this));
return commission;
}
@Transient
public Amount getMargin() {
if (margin == null)
setMargin(FeesUtil.getMargin(this));
return margin;
}
@Transient
public PositionType getPositionType() {
// if (getOpenVolumeCount() != 0)
if (positionType == null)
if (getVolume().isPositive())
return PositionType.LONG;
else
return PositionType.SHORT;
// If i have children and all the children are fully filled, then I set to flat
if (hasChildren() && getUnfilledVolume().isZero())
return PositionType.FLAT;
else
return positionType;
// return PositionType.FLAT;
}
@ManyToOne(optional = false)
public Portfolio getPortfolio() {
return portfolio;
}
@Transient
public FillType getFillType() {
if (getOrder() != null)
return getOrder().getFillType();
return null;
}
public PositionEffect getPositionEffect() {
return positionEffect;
}
protected void setPositionEffect(PositionEffect positionEffect) {
this.positionEffect = positionEffect;
}
@Override
public String toString() {
// + (order.getId() != null ? order.getId() : "")
// + (getFillType() != null ? getFillType() : "")
// getVolume()
return "Id=" + (getId() != null ? getId() : "") + SEPARATOR + "time=" + (getTime() != null ? (FORMAT.print(getTime())) : "") + SEPARATOR
+ "PositionType=" + (getPositionType() != null ? getPositionType() : "") + SEPARATOR + "Market=" + (market != null ? market : "") + SEPARATOR
+ "Price=" + (getPrice() != null ? getPrice() : "") + SEPARATOR + "Volume=" + (getVolume() != null ? getVolume() : "") + SEPARATOR
+ "Unfilled Volume=" + (hasChildren() ? getUnfilledVolume() : "") + SEPARATOR + "Open Volume="
+ (getOpenVolume() != null ? getOpenVolume() : "") + SEPARATOR + "Position Effect=" + (getPositionEffect() != null ? getPositionEffect() : "")
+ SEPARATOR + "Position=" + (getPosition() != null ? getPosition() : "") + SEPARATOR + "Comment="
+ (getOrder() != null && getOrder().getComment() != null ? getOrder().getComment() : "") + SEPARATOR + "Order="
+ (getOrder() != null ? getOrder().getId() : "") + SEPARATOR + "Parent Fill="
+ ((getOrder() != null && getOrder().getParentFill() != null) ? getOrder().getParentFill().getId() : "");
}
// JPA
protected Fill() {
}
public void setOrder(SpecificOrder order) {
this.order = order;
}
protected void setMarket(Market market) {
this.market = market;
}
public void setPositionType(PositionType positionType) {
if (this.positionType == (PositionType.FLAT))
System.out.println("previous was flat");
this.positionType = positionType;
}
@Transient
public Amount getUnfilledVolume() {
Amount filled = DecimalAmount.ZERO;
Amount unfilled = getVolume();
if (getVolume().isZero())
return DecimalAmount.ZERO;
for (Order childOrder : getFillChildOrders()) {
ArrayList<Fill> allChildFills = new ArrayList<Fill>();
childOrder.getAllFillsByParentOrder(childOrder, allChildFills);
for (Fill childFill : allChildFills) {
if (getVolume() == null || childFill.getVolume() == null)
System.out.println("null fill volume");
if (getVolume() != null && !getVolume().isZero() && (childFill.getVolume().isPositive() && getVolume().isNegative())
|| (childFill.getVolume().isNegative() && getVolume().isPositive()))
filled = filled.plus(childFill.getVolume());
}
unfilled = (getVolume().isNegative()) ? (getVolume().abs().minus(filled.abs())).negate() : getVolume().abs().minus(filled.abs());
}
return unfilled;
}
public void loadAllChildOrdersByFill(Fill parentFill, Map<Order, Order> orders, Map<Fill, Fill> fills) {
Map withFillsHints = new HashMap();
Map withTransHints = new HashMap();
Map withChildrenHints = new HashMap();
Map withChildOrderHints = new HashMap();
// Map orderHints = new HashMap();
// UUID portfolioID = EM.queryOne(UUID.class, queryStr, portfolioName);
// withFillsHints.put("javax.persistence.fetchgraph", "fillWithChildOrders");
// withTransHints.put("javax.persistence.fetchgraph", "orderWithTransactions");
withChildOrderHints.put("javax.persistence.fetchgraph", "fillWithChildOrders");
Fill fillWithFills;
Fill fillWithChildren;
Fill fillWithTransactions;
// Set test = new HashSet();
Map test = new HashMap();
// so for each fill in the open position we need to load the whole order tree
// getorder, then get all childe orders, then for each child, load child orders, so on and so forth.
// load all child orders, and theri child ordres
// load all parent orders and thier parent orders
// need to laod all parent fills, their child orders, and their children
// get a list of all orders in the tree then load
// orderId = fill.getOrder().getId();
// so we are
// Fill
// - Order
// - Fills
// -Fiil
// -Fill
// so if any of the fills wihtin the order are equal to the order's parent fill, set this the fill to the parent fill memory ref.
// if (getOrder() != null)
// getOrder().loadAllChildOrdersByParentOrder(getOrder(), orders, fills);
try {
fillWithChildren = EM.namedQueryZeroOne(Fill.class, "Fill.findFill", withChildOrderHints, parentFill.getId());
} catch (Error | Exception ex) {
log.error("Fill:loadAllChildOrdersByFill unable to get fill for fillID: " + parentFill.getId());
return;
}
if (fillWithChildren != null && fillWithChildren.getFillChildOrders() != null && fillWithChildren.getId().equals(parentFill.getId())
&& Hibernate.isInitialized(fillWithChildren.getFillChildOrders())) {
int index = 0;
for (Order order : fillWithChildren.getFillChildOrders()) {
if (order.getPortfolio().equals(parentFill.getPortfolio()))
order.setPortfolio(parentFill.getPortfolio());
if (order.equals(getOrder()))
//child = parentOrder;
continue;
if (!orders.containsKey(order)) {
orders.put(order, order);
System.out.println(" loading child order for " + order.getId());
order.loadAllChildOrdersByParentOrder(order, orders, fills);
} else
fillWithChildren.getFillChildOrders().set(index, orders.get(order));
index++;
}
parentFill.setFillChildOrders(fillWithChildren.getFillChildOrders());
} else
parentFill.setFillChildOrders(new CopyOnWriteArrayList<Order>());
if (orders.containsKey(getOrder()))
setOrder((SpecificOrder) orders.get(getOrder()));
else if (getOrder().getPortfolio().equals(parentFill.getPortfolio())) {
getOrder().setPortfolio(parentFill.getPortfolio());
orders.put(order, order);
System.out.println(" loading child order for " + order.getId());
getOrder().loadAllChildOrdersByParentOrder(getOrder(), orders, fills);
}
}
public void getAllSpecificOrdersByParentFill(Fill parentFill, Collection allChildren) {
for (Order child : parentFill.getFillChildOrders()) {
if (child instanceof SpecificOrder) {
allChildren.add(child);
child.getAllSpecificOrderByParentOrder(child, allChildren);
}
}
}
public void getAllOrdersByParentFill(Collection allChildren) {
List<Order> allSpecificChildOrders = Collections.synchronizedList(new ArrayList<Order>());
List<Order> allGeneralChildOrders = Collections.synchronizedList(new ArrayList<Order>());
List<Order> allChildOrders = Collections.synchronizedList(new ArrayList<Order>());
getAllSpecificOrdersByParentFill(this, allSpecificChildOrders);
getAllGeneralOrdersByParentFill(this, allGeneralChildOrders);
allChildren.addAll(allGeneralChildOrders);
allChildren.addAll(allSpecificChildOrders);
}
void getAllGeneralOrdersByParentFill(Fill parentFill, Collection allChildren) {
for (Order child : parentFill.getFillChildOrders()) {
if (child instanceof GeneralOrder) {
allChildren.add(child);
child.getAllGeneralOrderByParentOrder(child, allChildren);
}
}
}
protected void setPriceCount(long priceCount) {
if (priceCount == 0)
this.priceCount = priceCount;
this.priceCount = priceCount;
}
public void setStopAmountCount(long stopAmountCount) {
this.stopAmountCount = stopAmountCount;
}
public void setTargetAmountCount(long targetAmountCount) {
this.targetAmountCount = targetAmountCount;
}
public void setStopPriceCount(long stopPriceCount) {
if (stopPriceCount != 0)
this.stopPriceCount = stopPriceCount;
}
public void setTargetPriceCount(long targetPriceCount) {
this.targetPriceCount = targetPriceCount;
}
protected void setPortfolio(Portfolio portfolio) {
this.portfolio = portfolio;
}
protected void setVolumeCount(long volumeCount) {
this.volumeCount = volumeCount;
}
protected void setOpenVolumeCount(long openVolumeCount) {
openVolume = null;
this.openVolumeCount = openVolumeCount;
}
protected void setCommission(Amount commission) {
this.commission = commission;
}
protected void setMargin(Amount margin) {
this.margin = margin;
}
@Override
@Transient
public Dao getDao() {
return fillDao;
}
protected void setPosition(Position position) {
this.position = position;
}
private volatile List<Order> fillChildOrders;
private SpecificOrder order;
private Market market;
private volatile long priceCount;
private volatile long stopAmountCount;
private volatile long stopPriceCount;
private volatile long targetAmountCount;
private volatile long targetPriceCount;
private volatile long volumeCount;
private volatile long openVolumeCount;
private DiscreteAmount openVolume;
private volatile Amount commission;
private volatile Amount margin;
private PositionType positionType;
private volatile List<Transaction> transactions;
private Portfolio portfolio;
private Position position;
@Override
public void delete() {
// TODO Auto-generated method stub
}
}