Added the OrderBook class
This commit is contained in:
parent
cb30d27c35
commit
8bd9f293a4
4 changed files with 185 additions and 31 deletions
|
@ -3,7 +3,6 @@ package de.bitsharesmunich.graphenej;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
|
|
||||||
import com.google.common.primitives.UnsignedLong;
|
|
||||||
import de.bitsharesmunich.graphenej.errors.IncompleteAssetError;
|
import de.bitsharesmunich.graphenej.errors.IncompleteAssetError;
|
||||||
import de.bitsharesmunich.graphenej.models.BucketObject;
|
import de.bitsharesmunich.graphenej.models.BucketObject;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package de.bitsharesmunich.graphenej;
|
||||||
|
|
||||||
|
import com.google.common.primitives.UnsignedLong;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.bitsharesmunich.graphenej.operations.LimitOrderCreateOperation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class will maintain a snapshot of the order book between two assets.
|
||||||
|
*
|
||||||
|
* It also provides a handy method that should return the appropriate LimitOrderCreateOperation
|
||||||
|
* object needed in case the user wants to perform market-priced operations.
|
||||||
|
*
|
||||||
|
* It is important to keep the order book updated, ideally by listening to blockchain events, and calling the 'update' method.
|
||||||
|
*
|
||||||
|
* Created by nelson on 3/25/17.
|
||||||
|
*/
|
||||||
|
public class OrderBook {
|
||||||
|
private List<LimitOrder> limitOrders;
|
||||||
|
|
||||||
|
public OrderBook(List<LimitOrder> limitOrders){
|
||||||
|
this.limitOrders = limitOrders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the current limit order by the list provided as parameter.
|
||||||
|
* @param limitOrders: New list of orders
|
||||||
|
*/
|
||||||
|
public void update(List<LimitOrder> limitOrders){
|
||||||
|
this.limitOrders = limitOrders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(LimitOrder limitOrder){
|
||||||
|
//TODO: Implement the method that will update a single limit order from the order book
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used to exchange a specific amount of an asset (The base) for another one (The quote) at market value.
|
||||||
|
*
|
||||||
|
* It should analyze the order book and figure out the optimal amount of the base asset to give
|
||||||
|
* away in order to obtain the desired amount of the quote asset.
|
||||||
|
*
|
||||||
|
* @param seller: User account of the seller, used to build the limit order create operation
|
||||||
|
* @param myBaseAsset: The asset the user is willing to give away
|
||||||
|
* @param myQuoteAmount: The amount of a given asset the user wants
|
||||||
|
*
|
||||||
|
* @return An instance of the LimitOrderCreateOperation class, which is ready to be broadcasted.
|
||||||
|
*/
|
||||||
|
public LimitOrderCreateOperation exchange(UserAccount seller, Asset myBaseAsset, AssetAmount myQuoteAmount){
|
||||||
|
long totalBought = 0;
|
||||||
|
long totalSold = 0;
|
||||||
|
for(int i = 0; i < this.limitOrders.size() && totalBought < myQuoteAmount.getAmount().longValue(); i++){
|
||||||
|
LimitOrder order = this.limitOrders.get(i);
|
||||||
|
|
||||||
|
// If the base asset is the same as our quote asset, we have a match
|
||||||
|
if(order.getSellPrice().base.getAsset().getObjectId().equals(myQuoteAmount.getAsset().getObjectId())){
|
||||||
|
// My quote amount, is the order's base amount
|
||||||
|
long orderAmount = order.getSellPrice().base.getAmount().longValue();
|
||||||
|
|
||||||
|
// The amount of the quote asset we still need
|
||||||
|
long stillNeed = myQuoteAmount.getAmount().longValue() - totalBought;
|
||||||
|
|
||||||
|
// If the offered amount is greater than what we still need, we exchange just what we need
|
||||||
|
if(orderAmount >= stillNeed) {
|
||||||
|
totalBought += stillNeed;
|
||||||
|
totalSold += (order.getSellPrice().quote.getAmount().longValue() * stillNeed) / order.getSellPrice().base.getAmount().longValue();;
|
||||||
|
}else{
|
||||||
|
// If the offered amount is less than what we need, we exchange the whole order
|
||||||
|
totalBought += orderAmount;
|
||||||
|
totalSold += order.getSellPrice().quote.getAmount().longValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssetAmount toSell = new AssetAmount(UnsignedLong.valueOf(totalSold), myBaseAsset);
|
||||||
|
AssetAmount toReceive = myQuoteAmount;
|
||||||
|
LimitOrderCreateOperation buyOrder = new LimitOrderCreateOperation(seller, toSell, toReceive, true);
|
||||||
|
|
||||||
|
return buyOrder;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,13 +4,18 @@ import com.google.common.primitives.Bytes;
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import de.bitsharesmunich.graphenej.*;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import de.bitsharesmunich.graphenej.AssetAmount;
|
||||||
|
import de.bitsharesmunich.graphenej.BaseOperation;
|
||||||
|
import de.bitsharesmunich.graphenej.OperationType;
|
||||||
|
import de.bitsharesmunich.graphenej.UserAccount;
|
||||||
|
import de.bitsharesmunich.graphenej.Util;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Operation used to denote the creation of a limit order on the blockchain.
|
* Operation used to denote the creation of a limit order on the blockchain.
|
||||||
*
|
*
|
||||||
|
@ -42,6 +47,14 @@ public class LimitOrderCreateOperation extends BaseOperation {
|
||||||
private int expiration;
|
private int expiration;
|
||||||
private boolean fillOrKill;
|
private boolean fillOrKill;
|
||||||
|
|
||||||
|
public LimitOrderCreateOperation(UserAccount seller, AssetAmount toSell, AssetAmount minToReceive, boolean fillOrKill){
|
||||||
|
super(OperationType.LIMIT_ORDER_CREATE_OPERATION);
|
||||||
|
this.seller = seller;
|
||||||
|
this.amountToSell = toSell;
|
||||||
|
this.minToReceive = minToReceive;
|
||||||
|
this.fillOrKill = fillOrKill;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param seller: Id of the seller
|
* @param seller: Id of the seller
|
||||||
* @param toSell: Id of the asset to sell
|
* @param toSell: Id of the asset to sell
|
||||||
|
|
|
@ -1,71 +1,89 @@
|
||||||
package de.bitsharesmunich.graphenej.api;
|
package de.bitsharesmunich.graphenej.api;
|
||||||
|
|
||||||
|
import com.google.common.primitives.UnsignedLong;
|
||||||
import com.neovisionaries.ws.client.WebSocket;
|
import com.neovisionaries.ws.client.WebSocket;
|
||||||
import com.neovisionaries.ws.client.WebSocketException;
|
import com.neovisionaries.ws.client.WebSocketException;
|
||||||
import com.neovisionaries.ws.client.WebSocketFactory;
|
import com.neovisionaries.ws.client.WebSocketFactory;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
import de.bitsharesmunich.graphenej.Asset;
|
import de.bitsharesmunich.graphenej.Asset;
|
||||||
|
import de.bitsharesmunich.graphenej.AssetAmount;
|
||||||
import de.bitsharesmunich.graphenej.Converter;
|
import de.bitsharesmunich.graphenej.Converter;
|
||||||
import de.bitsharesmunich.graphenej.LimitOrder;
|
import de.bitsharesmunich.graphenej.LimitOrder;
|
||||||
|
import de.bitsharesmunich.graphenej.OrderBook;
|
||||||
|
import de.bitsharesmunich.graphenej.UserAccount;
|
||||||
import de.bitsharesmunich.graphenej.interfaces.WitnessResponseListener;
|
import de.bitsharesmunich.graphenej.interfaces.WitnessResponseListener;
|
||||||
import de.bitsharesmunich.graphenej.models.BaseResponse;
|
import de.bitsharesmunich.graphenej.models.BaseResponse;
|
||||||
import de.bitsharesmunich.graphenej.models.WitnessResponse;
|
import de.bitsharesmunich.graphenej.models.WitnessResponse;
|
||||||
|
import de.bitsharesmunich.graphenej.operations.LimitOrderCreateOperation;
|
||||||
import de.bitsharesmunich.graphenej.test.NaiveSSLContext;
|
import de.bitsharesmunich.graphenej.test.NaiveSSLContext;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by nelson on 3/24/17.
|
* Created by nelson on 3/24/17.
|
||||||
*/
|
*/
|
||||||
public class GetLimitOrdersTest {
|
public class GetLimitOrdersTest {
|
||||||
private String BLOCK_PAY_DE = System.getenv("BLOCKPAY_DE");
|
private String BLOCK_PAY_DE = System.getenv("BLOCKPAY_DE");
|
||||||
private final Asset base = new Asset("1.3.120", "EUR", 4);
|
private UserAccount seller = new UserAccount("1.2.143563");
|
||||||
|
private final Asset base = new Asset("1.3.0", "BTS", 5);
|
||||||
private final Asset quote = new Asset("1.3.121", "USD", 4);
|
private final Asset quote = new Asset("1.3.121", "USD", 4);
|
||||||
|
|
||||||
|
private SSLContext context;
|
||||||
|
private WebSocket mWebSocket;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetLimitOrders(){
|
|
||||||
SSLContext context = null;
|
|
||||||
try {
|
|
||||||
context = NaiveSSLContext.getInstance("TLS");
|
context = NaiveSSLContext.getInstance("TLS");
|
||||||
WebSocketFactory factory = new WebSocketFactory();
|
WebSocketFactory factory = new WebSocketFactory();
|
||||||
|
|
||||||
// Set the custom SSL context.
|
// Set the custom SSL context.
|
||||||
factory.setSSLContext(context);
|
factory.setSSLContext(context);
|
||||||
|
|
||||||
WebSocket mWebSocket = factory.createSocket(BLOCK_PAY_DE);
|
mWebSocket = factory.createSocket(BLOCK_PAY_DE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetLimitOrders(){
|
||||||
|
try {
|
||||||
mWebSocket.addListener(new GetLimitOrders(base.getObjectId(), quote.getObjectId(), 100, new WitnessResponseListener() {
|
mWebSocket.addListener(new GetLimitOrders(base.getObjectId(), quote.getObjectId(), 100, new WitnessResponseListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(WitnessResponse response) {
|
public void onSuccess(WitnessResponse response) {
|
||||||
List<LimitOrder> orders = (List<LimitOrder>) response.result;
|
List<LimitOrder> orders = (List<LimitOrder>) response.result;
|
||||||
|
Assert.assertThat("Checking that we have orders", orders.isEmpty(), is(false));
|
||||||
Converter converter = new Converter();
|
Converter converter = new Converter();
|
||||||
System.out.println();
|
double baseToQuoteExchange, quoteToBaseExchange;
|
||||||
|
|
||||||
for(LimitOrder order : orders){
|
for(LimitOrder order : orders){
|
||||||
|
if(order.getSellPrice().base.getAsset().getObjectId().equals(base.getObjectId())){
|
||||||
order.getSellPrice().base.getAsset().setPrecision(base.getPrecision());
|
order.getSellPrice().base.getAsset().setPrecision(base.getPrecision());
|
||||||
order.getSellPrice().quote.getAsset().setPrecision(quote.getPrecision());
|
order.getSellPrice().quote.getAsset().setPrecision(quote.getPrecision());
|
||||||
double baseToQuoteExchange = converter.getConversionRate(order.getSellPrice(), Converter.BASE_TO_QUOTE);
|
|
||||||
double quoteToBaseExchange = converter.getConversionRate(order.getSellPrice(), Converter.QUOTE_TO_BASE);
|
baseToQuoteExchange = converter.getConversionRate(order.getSellPrice(), Converter.BASE_TO_QUOTE);
|
||||||
System.out.println(String.format("base to quote: %.5f, quote to base: %.5f", baseToQuoteExchange, quoteToBaseExchange));
|
quoteToBaseExchange = converter.getConversionRate(order.getSellPrice(), Converter.QUOTE_TO_BASE);
|
||||||
|
System.out.println(String.format("> id: %s, base to quote: %.5f, quote to base: %.5f", order.getObjectId(), baseToQuoteExchange, quoteToBaseExchange));
|
||||||
|
}else{
|
||||||
|
order.getSellPrice().base.getAsset().setPrecision(quote.getPrecision());
|
||||||
|
order.getSellPrice().quote.getAsset().setPrecision(base.getPrecision());
|
||||||
|
|
||||||
|
baseToQuoteExchange = converter.getConversionRate(order.getSellPrice(), Converter.BASE_TO_QUOTE);
|
||||||
|
quoteToBaseExchange = converter.getConversionRate(order.getSellPrice(), Converter.QUOTE_TO_BASE);
|
||||||
|
System.out.println(String.format("< id: %s, base to quote: %.5f, quote to base: %.5f", order.getObjectId(), baseToQuoteExchange, quoteToBaseExchange));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
synchronized (GetLimitOrdersTest.this){
|
synchronized (GetLimitOrdersTest.this){
|
||||||
GetLimitOrdersTest.this.notifyAll();
|
GetLimitOrdersTest.this.notifyAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(BaseResponse.Error error) {
|
public void onError(BaseResponse.Error error) {
|
||||||
|
@ -75,18 +93,62 @@ public class GetLimitOrdersTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
mWebSocket.connect();
|
mWebSocket.connect();
|
||||||
System.out.println("Waiting..");
|
|
||||||
synchronized (this){
|
synchronized (this){
|
||||||
wait();
|
wait();
|
||||||
}
|
}
|
||||||
System.out.println("Released!");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
System.out.println("NoSuchAlgorithmException. Msg: " + e.getMessage());
|
|
||||||
} catch (WebSocketException e) {
|
} catch (WebSocketException e) {
|
||||||
System.out.println("WebSocketException. Msg: " + e.getMessage());
|
System.out.println("WebSocketException. Msg: " + e.getMessage());
|
||||||
} catch (IOException e) {
|
} catch (InterruptedException e) {
|
||||||
System.out.println("IOException. Msg: " + e.getMessage());
|
System.out.println("InterruptedException. Msg: "+e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is designed to test how the OrderBook class handles data obtained
|
||||||
|
* from the GetLimitOrders API call.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOrderBook(){
|
||||||
|
LimitOrderCreateOperation limitOrderOperation;
|
||||||
|
|
||||||
|
try {
|
||||||
|
mWebSocket.addListener(new GetLimitOrders(base.getObjectId(), quote.getObjectId(), 100, new WitnessResponseListener() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(WitnessResponse response) {
|
||||||
|
List<LimitOrder> orders = (List<LimitOrder>) response.result;
|
||||||
|
OrderBook orderBook = new OrderBook(orders);
|
||||||
|
|
||||||
|
AssetAmount toBuy = new AssetAmount(UnsignedLong.valueOf(9850000), quote);
|
||||||
|
LimitOrderCreateOperation operation = orderBook.exchange(seller, base, toBuy);
|
||||||
|
|
||||||
|
// Testing the successfull creation of a limit order create operation
|
||||||
|
Assert.assertTrue(operation != null);
|
||||||
|
|
||||||
|
synchronized (GetLimitOrdersTest.this){
|
||||||
|
GetLimitOrdersTest.this.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(BaseResponse.Error error) {
|
||||||
|
System.out.println("onError. Msg: "+error.message);
|
||||||
|
synchronized (GetLimitOrdersTest.this){
|
||||||
|
GetLimitOrdersTest.this.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
mWebSocket.connect();
|
||||||
|
|
||||||
|
synchronized (this){
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (WebSocketException e) {
|
||||||
|
System.out.println("WebSocketException. Msg: " + e.getMessage());
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
System.out.println("InterruptedException. Msg: "+e.getMessage());
|
System.out.println("InterruptedException. Msg: "+e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -94,7 +156,6 @@ public class GetLimitOrdersTest {
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() throws Exception {
|
||||||
|
mWebSocket.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue