package org.cryptocoinpartners.schema; import javax.annotation.Nullable; import javax.inject.Inject; import javax.persistence.Cacheable; import javax.persistence.Entity; import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; import javax.persistence.Transient; import org.cryptocoinpartners.enumeration.PositionEffect; import org.cryptocoinpartners.enumeration.TransactionType; import org.cryptocoinpartners.schema.dao.Dao; import org.cryptocoinpartners.schema.dao.TransactionDao; import org.cryptocoinpartners.util.Remainder; 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.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; /** * A Transaction represents the modification of multiple Positions, whether it is a purchase on a Market or a * Transfer of Fungibles between Accounts * @author Tim Olson */ @Entity @Cacheable @Table(indexes = { @Index(columnList = "type") }) public class Transaction extends Event { enum TransactionStatus { OFFERED, ACCEPTED, CLOSED, SETTLED, CANCELLED } private static final DateTimeFormatter FORMAT = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); private static final String SEPARATOR = ","; @Inject protected TransactionDao transactionDao; @AssistedInject public Transaction(@Assisted Portfolio portfolio, @Assisted Exchange exchange, @Assisted Asset currency, @Assisted TransactionType type, @Assisted("transactionAmount") Amount amount, @Assisted("transactionPrice") Amount price) { // this.id = getId(); this.version = getVersion(); this.setAmount(amount); this.amountCount = amount.toBasis(currency.getBasis(), Remainder.ROUND_EVEN).getCount(); this.setCurrency(currency); this.setPrice(price); this.priceCount = price.toBasis(currency.getBasis(), Remainder.ROUND_EVEN).getCount(); this.setType(type); this.setPortfolio(portfolio); this.setExchange(exchange); this.setPortfolioName(portfolio); } @AssistedInject public Transaction(@Assisted Fill fill, @Assisted Portfolio portfolio, @Assisted Exchange exchange, @Assisted Asset currency, @Assisted TransactionType type, @Assisted("transactionAmount") Amount amount, @Assisted("transactionPrice") Amount price) { // this.id = getId(); this.version = getVersion(); fill.addTransaction(this); this.setAmount(amount); this.amountCount = amount.toBasis(currency.getBasis(), Remainder.ROUND_EVEN).getCount(); this.setCurrency(currency); this.setPrice(price); this.priceCount = price.toBasis(currency.getBasis(), Remainder.ROUND_EVEN).getCount(); this.setType(type); this.setPortfolio(portfolio); this.setExchange(exchange); this.setPortfolioName(portfolio); this.fill = fill; } public Transaction(Portfolio portfolio, Exchange exchange, Asset currency, TransactionType type, Amount amount) { // this.id = getId(); this.version = getVersion(); this.setAmount(amount); this.amountCount = amount.toBasis(currency.getBasis(), Remainder.ROUND_EVEN).getCount(); this.setCurrency(currency); this.setType(type); this.setPortfolio(portfolio); this.setExchange(exchange); this.setPortfolioName(portfolio); } @AssistedInject public Transaction(@Assisted Fill fill, @Assisted Instant creationTime) { // this.id = getId(); this.version = getVersion(); Portfolio portfolio = fill.getOrder().getPortfolio(); TransactionType transactionType = null; if (fill.getOrder().getPositionEffect() == PositionEffect.OPEN || fill.getOrder().getPositionEffect() == PositionEffect.CLOSE) { //is entering or exiting trade transactionType = (fill.getVolume().isPositive()) ? TransactionType.BUY : TransactionType.SELL; } else { // is either buying base currency and selling quote or selling base currency and buying quote transactionType = TransactionType.REBALANCE; } this.time = creationTime; this.asset = fill.getMarket().getTradedCurrency(); this.currency = fill.getMarket().getBase(); fill.addTransaction(this); this.setPositionEffect(fill.getOrder().getPositionEffect()); this.setPrice(fill.getPrice()); this.setPriceCount(fill.getPriceCount()); //f this.time = fill.getTime(); this.setType(transactionType); this.setPortfolio(portfolio); this.setPortfolioName(portfolio); this.setCommission(fill.getCommission()); this.setMargin(fill.getMargin()); this.setCommissionCurrency(fill.getMarket().getTradedCurrency()); this.assetAmount = this.getCommission().plus(this.getMargin()); this.amount = this.getCommission().plus(this.getMargin()); this.setMarket(fill.getMarket()); this.setExchange(fill.getMarket().getExchange()); this.fill = fill; } @AssistedInject public Transaction(@Assisted Order order, @Assisted Instant creationTime) { // this.id = getId(); this.version = getVersion(); Portfolio portfolio = order.getPortfolio(); TransactionType transactionType = order.getVolume().isPositive() ? TransactionType.BUY_RESERVATION : TransactionType.SELL_RESERVATION; order.addTransaction(this); this.time = creationTime; this.asset = order.getMarket().getTradedCurrency(); this.currency = order.getMarket().getBase(); this.setPrice(order.getLimitPrice()); this.setType(transactionType); this.setPortfolio(portfolio); this.setPositionEffect(order.getPositionEffect()); this.setCommission(order.getForcastedCommission()); this.setMargin(order.getForcastedMargin()); this.setCommissionCurrency(order.getMarket().getTradedCurrency()); //if traded=quote, then do this, if traded== base then just volume this.amount = this.getCommission().plus(this.getMargin()); this.assetAmount = this.getCommission().plus(this.getMargin()); this.setMarket(order.getMarket()); this.setPortfolioName(portfolio); // this.time = order.getTime(); this.setExchange(order.getMarket().getExchange()); this.order = order; } private void setDateTime(Instant time) { // TODO Auto-generated method stub } @Transient public Amount getValue() { Amount value = DecimalAmount.ZERO; if (getType() == (TransactionType.BUY) || getType() == (TransactionType.SELL)) { Amount notional = getAssetAmount(); //Amount totalvalue = notional.plus(getCommission()); value = notional; } else if (getType() == (TransactionType.BUY_RESERVATION) || getType() == (TransactionType.SELL_RESERVATION)) { value = getAssetAmount().minus(getCommission()); } else if (getType() == (TransactionType.CREDIT) || getType() == (TransactionType.INTREST)) { value = getAmount(); } else if (getType() == (TransactionType.DEBIT) || getType() == (TransactionType.FEES)) { value = getAmount(); } else if (getType() == (TransactionType.REBALANCE)) { value = getAmount(); } else { throw new IllegalArgumentException("unsupported transactionType: " + getType()); } return value; } @Transient public Amount getCost() { Amount value = DecimalAmount.ZERO; if (getType() == (TransactionType.BUY) || getType() == (TransactionType.SELL) || getType() == (TransactionType.REBALANCE)) { // issue works when entering position on margin, howeever when exiting no margin applies. // so open postion with 3 times mulitpler, so it costs me a 3rd // whne a close a postion it still tinks i it is a 3rd so we over cacluated by 1/3rd of the PnL so always overstating the cash balance //(FeesUtil.getMargin(orderBuilder.getOrder()).plus(FeesUtil.getCommission(orderBuilder.getOrder()))).negate() // if (getAmount().isNegative() && getMarket().getContractSize() == 1) // cost = cost.negate(); //Amount notional = getAssetAmount(); //Amount cost = notional.divide(getExchange().getMargin(), Remainder.ROUND_EVEN); Amount totalcost = value.plus(getCommission()); value = totalcost; } else if (getType() == (TransactionType.BUY_RESERVATION) || getType() == (TransactionType.SELL_RESERVATION)) { Amount notional = (getCommission()); value = notional; } else if (getType() == (TransactionType.CREDIT) || getType() == (TransactionType.INTREST)) { value = getAmount(); } else if (getType() == (TransactionType.DEBIT) || getType() == (TransactionType.FEES)) { value = getAmount(); } return value; } @Nullable @ManyToOne(optional = true) public Asset getAsset() { return asset; } public @Nullable Long getPriceCount() { return priceCount; } public Long getAmountCount() { return amountCount; } public @Nullable Long getCommissionCount() { return commissionCount; } @Nullable @ManyToOne(optional = true) public Market getMarket() { return market; } private Asset currency; @ManyToOne(optional = false) public Asset getCurrency() { return currency; } @Transient public Amount getAmount() { return amount; } @Transient public Amount getAssetAmount() { return assetAmount; } @Transient public Amount getCommission() { return commission; } @Transient public Amount getMargin() { return margin; } @Nullable @ManyToOne(optional = true) public Asset getCommissionCurrency() { return commissionCurrency; } public @ManyToOne(optional = true) //, cascade = { CascadeType.MERGE, CascadeType.REMOVE }) @JoinColumn(name = "`order`") Order getOrder() { return order; } // @PrePersist private void prePersist() { if (getDao() != null) { Portfolio transactionPortfolio = null; Order transactionOrder = null; Fill transactionFill = null; //UUID parentOrderId = null; // if (portfolio != null) { // transactionPortfolio = (transactionDao.find(Portfolio.class, portfolio.getId())); // positionId = (fillDao.queryZeroOne(UUID.class, "select p.id from Position p where p.id=?1", position.getId())); // if (!transactionDao.contains(getPortfolio())) // transactionDao.persist(getPortfolio()); // portfolio.merge(); //} if (getOrder() != null) { // transactionOrder = (transactionDao.find(Order.class, order.getId())); // positionId = (fillDao.queryZeroOne(UUID.class, "select p.id from Position p where p.id=?1", position.getId())); // if (transactionOrder == null) // transactionDao.persist(order); // order.merge(); transactionOrder = (getDao().find(getOrder().getClass(), getOrder().getId())); if (transactionOrder == null) getDao().persist(getOrder()); } if (getFill() != null) { //transactionFill = (transactionDao.find(Fill.class, fill.getId())); transactionFill = (getDao().find(getFill().getClass(), getFill().getId())); // positionId = (fillDao.queryZeroOne(UUID.class, "select p.id from Position p where p.id=?1", position.getId())); if (transactionFill == null) getDao().persist(getFill()); } } // // UUID portfolioId = null; // UUID orderId = null; // UUID fillId = null; // if (portfolio != null) { // portfolioId = (transactionDao == null) ? (EM.queryZeroOne(UUID.class, "select p.id from Portfolio p where p.id=?1", portfolio.getId())) // : (transactionDao.queryZeroOne(UUID.class, "select p.id from Portfolio p where p.id=?1", portfolio.getId())); // if (portfolioId == null) // portfolio.persit(); // } // // if (order != null) { // orderId = (transactionDao == null) ? (EM.queryZeroOne(UUID.class, "select o.id from Order o where o.id=?1", order.getId())) : (transactionDao // .queryZeroOne(UUID.class, "select o.id from Order o where o.id=?1", order.getId())); // // if (orderId == null) // order.persit(); // } // if (fill != null) { // fillId = (transactionDao == null) ? (EM.queryZeroOne(UUID.class, "select f.id from Fill f where f.id=?1", fill.getId())) : (transactionDao // .queryZeroOne(UUID.class, "select f.id from Fill f where f.id=?1", fill.getId())); // // if (fillId == null) // fill.persit(); // } //detach(); } public @ManyToOne(optional = true) @JoinColumn(name = "fill") //, cascade = { CascadeType.MERGE, CascadeType.REFRESH }) Fill getFill() { return fill; } @Override public synchronized void merge() { try { if (fill != null) fill.find(); if (order != null) order.find(); transactionDao.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 EntityBase refresh() { return transactionDao.refresh(this); } @Override public synchronized void persit() { // // // List<Transaction> duplicate = transactionDao.queryList(Transaction.class, "select t from Transaction t where t=?1", this); //if (duplicate == null || duplicate.isEmpty()) try { transactionDao.persist(this); // System.out.println("saved:" + 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(); } // //PersistUtil.insert(this); //else // transactionDao.merge(this); // PersistUtil.merge(this); // } // if (this.parentOrder != null) // parentOrder.persit(); } @ManyToOne(optional = false) public Portfolio getPortfolio() { return portfolio; } @Transient public String getPortfolioName() { return portfolioName; } @ManyToOne(optional = false) private TransactionType type; public TransactionType getType() { return type; } @ManyToOne(optional = true) private PositionEffect positionEffect; public PositionEffect getPositionEffect() { return positionEffect; } @Transient public Amount getPrice() { return price; } @Nullable @ManyToOne(optional = true) public Exchange getExchange() { return exchange; } @Override public String toString() { return "id=" + getId() + SEPARATOR + "time=" + (getTime() != null ? (FORMAT.print(getTime())) : "") + SEPARATOR + "Portfolio=" + getPortfolio() + SEPARATOR + "Exchange=" + getExchange() + SEPARATOR + "type=" + getType() + (getFill() != null ? (SEPARATOR + "fill=" + getFill()) : "") + (getOrder() != null ? (SEPARATOR + "order=" + getOrder()) : "") + SEPARATOR + "amount=" + getAmount() + (getAsset() != null ? (SEPARATOR + "currency=" + getAsset()) : "") + SEPARATOR + "price=" + (getPrice() != DecimalAmount.ZERO ? getPrice() : "") + (getCurrency() != null ? (SEPARATOR + "currency=" + getCurrency()) : ""); } protected void setAmount(Amount amount) { this.amount = amount; } protected void setPortfolio(Portfolio portfolio) { this.portfolio = portfolio; } protected void setOrder(Order order) { this.order = order; } protected void setFill(Fill fill) { this.fill = fill; } protected void setExchange(Exchange exchange) { this.exchange = exchange; } protected void setAsset(Asset asset) { this.asset = asset; } protected void setCommission(Amount commission) { this.commission = commission; } protected void setMargin(Amount margin) { this.margin = margin; } protected void setCommissionCount(Long commissionCount) { this.commissionCount = commissionCount; } protected void setPriceCount(Long priceCount) { this.priceCount = priceCount; } protected void setAmountCount(Long amountCount) { this.amountCount = amountCount; } protected void setCurrency(Asset asset) { this.currency = asset; } protected void setCommissionCurrency(Asset asset) { this.commissionCurrency = asset; } protected void setPortfolioName(Portfolio portfolio) { this.portfolioName = portfolio.getName(); } protected void setType(TransactionType type) { this.type = type; } protected void setPositionEffect(PositionEffect positionEffect) { this.positionEffect = positionEffect; } protected void setPrice(Amount price) { this.price = price; } protected void setMarket(Market market) { this.market = market; } Transaction() { } // protected Instant getTime() { return acceptedTime; } private Amount price; @Nullable private Portfolio portfolio; @Nullable private Order order; @Nullable private Fill fill; private Asset asset; private Amount amount; private Amount assetAmount; private Long commissionCount; private long amountCount; private String portfolioName; private long priceCount; private Amount commission; private Amount margin; private Exchange exchange; private Asset commissionCurrency; private Market market; protected static Logger log = LoggerFactory.getLogger("org.cryptocoinpartners.transaction"); @Override @Transient public Dao getDao() { return transactionDao; } @Override public void detach() { transactionDao.detach(this); } @Override public void delete() { // TODO Auto-generated method stub } }