/******************************************************************************
* Copyright © 2013-2016 The Nxt Core Developers. *
* *
* See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* Nxt software, including this file, may be copied, modified, propagated, *
* or distributed except according to the terms contained in the LICENSE.txt *
* file. *
* *
* Removal or modification of this copyright notice is prohibited. *
* *
******************************************************************************/
package nxt;
import nxt.db.DbClause;
import nxt.db.DbIterator;
import nxt.db.DbKey;
import nxt.db.DbUtils;
import nxt.db.EntityDbTable;
import nxt.util.Listener;
import nxt.util.Listeners;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public final class Trade {
public enum Event {
TRADE
}
private static final Listeners<Trade,Event> listeners = new Listeners<>();
private static final DbKey.LinkKeyFactory<Trade> tradeDbKeyFactory = new DbKey.LinkKeyFactory<Trade>("ask_order_id", "bid_order_id") {
@Override
public DbKey newKey(Trade trade) {
return trade.dbKey;
}
};
private static final EntityDbTable<Trade> tradeTable = new EntityDbTable<Trade>("trade", tradeDbKeyFactory) {
@Override
protected Trade load(Connection con, ResultSet rs) throws SQLException {
return new Trade(rs);
}
@Override
protected void save(Connection con, Trade trade) throws SQLException {
trade.save(con);
}
};
public static DbIterator<Trade> getAllTrades(int from, int to) {
return tradeTable.getAll(from, to);
}
public static int getCount() {
return tradeTable.getCount();
}
public static boolean addListener(Listener<Trade> listener, Event eventType) {
return listeners.addListener(listener, eventType);
}
public static boolean removeListener(Listener<Trade> listener, Event eventType) {
return listeners.removeListener(listener, eventType);
}
public static Trade getTrade(long askOrderId, long bidOrderId) {
return tradeTable.get(tradeDbKeyFactory.newKey(askOrderId, bidOrderId));
}
public static DbIterator<Trade> getAssetTrades(long assetId, int from, int to) {
return tradeTable.getManyBy(new DbClause.LongClause("asset_id", assetId), from, to);
}
public static List<Trade> getLastTrades(long[] assetIds) {
try (Connection con = Db.db.getConnection();
PreparedStatement pstmt = con.prepareStatement("SELECT * FROM trade WHERE asset_id = ? ORDER BY height DESC, db_id DESC LIMIT 1")) {
List<Trade> result = new ArrayList<>();
for (long assetId : assetIds) {
pstmt.setLong(1, assetId);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
result.add(new Trade(rs));
}
}
}
return result;
} catch (SQLException e) {
throw new RuntimeException(e.toString(), e);
}
}
public static DbIterator<Trade> getAccountTrades(long accountId, int from, int to) {
Connection con = null;
try {
con = Db.db.getConnection();
PreparedStatement pstmt = con.prepareStatement("SELECT * FROM trade WHERE seller_id = ?"
+ " UNION ALL SELECT * FROM trade WHERE buyer_id = ? AND seller_id <> ? ORDER BY height DESC, db_id DESC"
+ DbUtils.limitsClause(from, to));
int i = 0;
pstmt.setLong(++i, accountId);
pstmt.setLong(++i, accountId);
pstmt.setLong(++i, accountId);
DbUtils.setLimits(++i, pstmt, from, to);
return tradeTable.getManyBy(con, pstmt, false);
} catch (SQLException e) {
DbUtils.close(con);
throw new RuntimeException(e.toString(), e);
}
}
public static DbIterator<Trade> getAccountAssetTrades(long accountId, long assetId, int from, int to) {
Connection con = null;
try {
con = Db.db.getConnection();
PreparedStatement pstmt = con.prepareStatement("SELECT * FROM trade WHERE seller_id = ? AND asset_id = ?"
+ " UNION ALL SELECT * FROM trade WHERE buyer_id = ? AND seller_id <> ? AND asset_id = ? ORDER BY height DESC, db_id DESC"
+ DbUtils.limitsClause(from, to));
int i = 0;
pstmt.setLong(++i, accountId);
pstmt.setLong(++i, assetId);
pstmt.setLong(++i, accountId);
pstmt.setLong(++i, accountId);
pstmt.setLong(++i, assetId);
DbUtils.setLimits(++i, pstmt, from, to);
return tradeTable.getManyBy(con, pstmt, false);
} catch (SQLException e) {
DbUtils.close(con);
throw new RuntimeException(e.toString(), e);
}
}
public static DbIterator<Trade> getAskOrderTrades(long askOrderId, int from, int to) {
return tradeTable.getManyBy(new DbClause.LongClause("ask_order_id", askOrderId), from, to);
}
public static DbIterator<Trade> getBidOrderTrades(long bidOrderId, int from, int to) {
return tradeTable.getManyBy(new DbClause.LongClause("bid_order_id", bidOrderId), from, to);
}
public static int getTradeCount(long assetId) {
return tradeTable.getCount(new DbClause.LongClause("asset_id", assetId));
}
static Trade addTrade(long assetId, Order.Ask askOrder, Order.Bid bidOrder) {
Trade trade = new Trade(assetId, askOrder, bidOrder);
tradeTable.insert(trade);
listeners.notify(trade, Event.TRADE);
return trade;
}
static void init() {}
private final int timestamp;
private final long assetId;
private final long blockId;
private final int height;
private final long askOrderId;
private final long bidOrderId;
private final int askOrderHeight;
private final int bidOrderHeight;
private final long sellerId;
private final long buyerId;
private final DbKey dbKey;
private final long quantityQNT;
private final long priceNQT;
private final boolean isBuy;
private Trade(long assetId, Order.Ask askOrder, Order.Bid bidOrder) {
Block block = Nxt.getBlockchain().getLastBlock();
this.blockId = block.getId();
this.height = block.getHeight();
this.assetId = assetId;
this.timestamp = block.getTimestamp();
this.askOrderId = askOrder.getId();
this.bidOrderId = bidOrder.getId();
this.askOrderHeight = askOrder.getHeight();
this.bidOrderHeight = bidOrder.getHeight();
this.sellerId = askOrder.getAccountId();
this.buyerId = bidOrder.getAccountId();
this.dbKey = tradeDbKeyFactory.newKey(this.askOrderId, this.bidOrderId);
this.quantityQNT = Math.min(askOrder.getQuantityQNT(), bidOrder.getQuantityQNT());
if (askOrderHeight < bidOrderHeight) {
this.isBuy = true;
} else if (askOrderHeight == bidOrderHeight) {
if (this.height <= Constants.VOTING_SYSTEM_BLOCK) {
this.isBuy = askOrderId < bidOrderId;
} else {
this.isBuy = askOrder.getTransactionHeight() < bidOrder.getTransactionHeight() ||
(askOrder.getTransactionHeight() == bidOrder.getTransactionHeight()
&& askOrder.getTransactionIndex() < bidOrder.getTransactionIndex());
}
} else {
this.isBuy = false;
}
this.priceNQT = isBuy ? askOrder.getPriceNQT() : bidOrder.getPriceNQT();
}
private Trade(ResultSet rs) throws SQLException {
this.assetId = rs.getLong("asset_id");
this.blockId = rs.getLong("block_id");
this.askOrderId = rs.getLong("ask_order_id");
this.bidOrderId = rs.getLong("bid_order_id");
this.askOrderHeight = rs.getInt("ask_order_height");
this.bidOrderHeight = rs.getInt("bid_order_height");
this.sellerId = rs.getLong("seller_id");
this.buyerId = rs.getLong("buyer_id");
this.dbKey = tradeDbKeyFactory.newKey(this.askOrderId, this.bidOrderId);
this.quantityQNT = rs.getLong("quantity");
this.priceNQT = rs.getLong("price");
this.timestamp = rs.getInt("timestamp");
this.height = rs.getInt("height");
this.isBuy = rs.getBoolean("is_buy");
}
private void save(Connection con) throws SQLException {
try (PreparedStatement pstmt = con.prepareStatement("INSERT INTO trade (asset_id, block_id, "
+ "ask_order_id, bid_order_id, ask_order_height, bid_order_height, seller_id, buyer_id, quantity, price, is_buy, timestamp, height) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")) {
int i = 0;
pstmt.setLong(++i, this.assetId);
pstmt.setLong(++i, this.blockId);
pstmt.setLong(++i, this.askOrderId);
pstmt.setLong(++i, this.bidOrderId);
pstmt.setInt(++i, this.askOrderHeight);
pstmt.setInt(++i, this.bidOrderHeight);
pstmt.setLong(++i, this.sellerId);
pstmt.setLong(++i, this.buyerId);
pstmt.setLong(++i, this.quantityQNT);
pstmt.setLong(++i, this.priceNQT);
pstmt.setBoolean(++i, this.isBuy);
pstmt.setInt(++i, this.timestamp);
pstmt.setInt(++i, this.height);
pstmt.executeUpdate();
}
}
public long getBlockId() { return blockId; }
public long getAskOrderId() { return askOrderId; }
public long getBidOrderId() { return bidOrderId; }
public int getAskOrderHeight() {
return askOrderHeight;
}
public int getBidOrderHeight() {
return bidOrderHeight;
}
public long getSellerId() {
return sellerId;
}
public long getBuyerId() {
return buyerId;
}
public long getQuantityQNT() { return quantityQNT; }
public long getPriceNQT() { return priceNQT; }
public long getAssetId() { return assetId; }
public int getTimestamp() { return timestamp; }
public int getHeight() {
return height;
}
public boolean isBuy() {
return isBuy;
}
@Override
public String toString() {
return "Trade asset: " + Long.toUnsignedString(assetId) + " ask: " + Long.toUnsignedString(askOrderId)
+ " bid: " + Long.toUnsignedString(bidOrderId) + " price: " + priceNQT + " quantity: " + quantityQNT + " height: " + height;
}
}