2018-12-26 13:11:35 +01:00
|
|
|
/*
|
2018-12-28 13:06:15 +01:00
|
|
|
* Copyright (c) 2018, 7u83
|
2018-12-26 13:11:35 +01:00
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
|
|
|
*
|
|
|
|
* * Redistributions of source code must retain the above copyright notice, this
|
|
|
|
* list of conditions and the following disclaimer.
|
|
|
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
|
|
* and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
package opensesim.world;
|
|
|
|
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.Set;
|
|
|
|
import java.util.SortedSet;
|
|
|
|
import java.util.TreeSet;
|
|
|
|
import opensesim.util.idgenerator.IDGenerator;
|
2018-12-29 01:39:38 +01:00
|
|
|
import opensesim.util.idgenerator.LongIDGenerator;
|
2018-12-28 13:06:15 +01:00
|
|
|
import opensesim.util.scheduler.EventListener;
|
|
|
|
import opensesim.util.scheduler.FiringEvent;
|
2018-12-26 13:11:35 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
2018-12-28 13:06:15 +01:00
|
|
|
* @author 7u83
|
2018-12-26 13:11:35 +01:00
|
|
|
*/
|
|
|
|
class TradingEngine implements TradingAPI {
|
2018-12-28 11:37:52 +01:00
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
private final Exchange outer;
|
|
|
|
|
2018-12-27 17:52:44 +01:00
|
|
|
/**
|
|
|
|
* Construct a trading engine for an asset pair
|
2018-12-28 11:37:52 +01:00
|
|
|
*
|
2018-12-29 01:39:38 +01:00
|
|
|
* @param pair The AssetPair object to create the trading engine for
|
2018-12-28 11:37:52 +01:00
|
|
|
* @param outer Outer class - points to an Exchange object thins trading
|
2018-12-27 17:52:44 +01:00
|
|
|
* engine belongs to.
|
|
|
|
*/
|
|
|
|
TradingEngine(AssetPair pair, final Exchange outer) {
|
2018-12-26 13:11:35 +01:00
|
|
|
this.outer = outer;
|
2018-12-27 17:52:44 +01:00
|
|
|
assetpair = pair;
|
2018-12-26 13:11:35 +01:00
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
2018-12-27 17:52:44 +01:00
|
|
|
@Override
|
|
|
|
public AssetPair getAssetPair() {
|
2018-12-26 13:11:35 +01:00
|
|
|
return assetpair;
|
|
|
|
}
|
|
|
|
IDGenerator id_generator = new IDGenerator();
|
2018-12-29 01:39:38 +01:00
|
|
|
LongIDGenerator quote_id_generator = new LongIDGenerator();
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
private HashMap<Order.Type, SortedSet<Order>> order_books;
|
2018-12-28 13:06:15 +01:00
|
|
|
private SortedSet<Order> bidbook, askbook;
|
2018-12-29 01:39:38 +01:00
|
|
|
private SortedSet<Order> ul_buy, ul_sell;
|
2018-12-26 13:11:35 +01:00
|
|
|
AssetPair assetpair;
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-29 01:39:38 +01:00
|
|
|
TreeSet<Quote> quote_history;
|
2018-12-29 11:14:04 +01:00
|
|
|
Quote last_quote;
|
2018-12-26 13:11:35 +01:00
|
|
|
|
|
|
|
protected final void reset() {
|
2018-12-28 13:06:15 +01:00
|
|
|
order_books = new HashMap<>();
|
2018-12-29 01:39:38 +01:00
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
// Create an order book for each order type
|
|
|
|
for (Order.Type type : Order.Type.values()) {
|
|
|
|
order_books.put(type, new TreeSet<>());
|
|
|
|
}
|
2018-12-28 13:06:15 +01:00
|
|
|
// Save order books to variables for quicker access
|
2018-12-26 13:11:35 +01:00
|
|
|
bidbook = order_books.get(Order.Type.BUYLIMIT);
|
|
|
|
askbook = order_books.get(Order.Type.SELLLIMIT);
|
2018-12-29 01:39:38 +01:00
|
|
|
ul_buy = order_books.get(Order.Type.BUY);
|
|
|
|
ul_sell = order_books.get(Order.Type.SELL);
|
|
|
|
|
2018-12-29 05:25:42 +01:00
|
|
|
quote_history = new TreeSet<>();
|
|
|
|
|
2018-12-29 11:14:04 +01:00
|
|
|
last_quote = null;
|
|
|
|
|
|
|
|
Quote q = new Quote(-1);
|
2018-12-29 13:30:07 +01:00
|
|
|
q.price = 100.0;
|
2018-12-29 11:14:04 +01:00
|
|
|
last_quote = q;
|
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
// ohlc_data = new HashMap();
|
|
|
|
}
|
2018-12-29 05:25:42 +01:00
|
|
|
|
|
|
|
void addQuoteToHistory(Quote q) {
|
|
|
|
/* if (statistics.heigh == null) {
|
2018-12-29 01:39:38 +01:00
|
|
|
statistics.heigh = q.price;
|
|
|
|
} else if (statistics.heigh < q.price) {
|
|
|
|
statistics.heigh = q.price;
|
|
|
|
}
|
|
|
|
if (statistics.low == null) {
|
|
|
|
statistics.low = q.price;
|
|
|
|
} else if (statistics.low > q.price) {
|
|
|
|
statistics.low = q.price;
|
|
|
|
}
|
2018-12-29 05:25:42 +01:00
|
|
|
*/
|
2018-12-29 01:39:38 +01:00
|
|
|
// Stock stock = getDefaultStock();
|
2018-12-29 05:25:42 +01:00
|
|
|
quote_history.add(q);
|
2018-12-29 01:39:38 +01:00
|
|
|
// stock.updateOHLCData(q);
|
2018-12-29 05:25:42 +01:00
|
|
|
// updateQuoteReceivers(q);
|
2018-12-29 01:39:38 +01:00
|
|
|
}
|
|
|
|
|
2018-12-29 05:25:42 +01:00
|
|
|
boolean compact_history = false;
|
|
|
|
boolean compact_last = true;
|
|
|
|
|
2019-01-14 08:43:24 +01:00
|
|
|
private void transferMoneyAndShares(AccountImpl src, AccountImpl dst, double money, double shares) {
|
2018-12-29 01:39:38 +01:00
|
|
|
// src.money -= money;
|
|
|
|
|
|
|
|
AssetPack pack;
|
|
|
|
|
|
|
|
pack = new AssetPack(assetpair.getCurrency(), money);
|
|
|
|
src.sub(pack);
|
|
|
|
dst.add(pack);
|
|
|
|
|
|
|
|
pack.asset = assetpair.getAsset();
|
|
|
|
pack.volume = shares;
|
|
|
|
src.add(pack);
|
|
|
|
dst.sub(pack);
|
|
|
|
|
|
|
|
/* src.addMoney(-money);
|
|
|
|
|
|
|
|
// dst.money += money;
|
|
|
|
dst.addMoney(money);
|
|
|
|
// src.shares -= shares;
|
|
|
|
src.addShares(-shares);
|
|
|
|
// dst.shares += shares;
|
|
|
|
|
|
|
|
src.addShares(shares);
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
private void finishTrade(Order b, Order a, double price, double volume) {
|
2018-12-29 11:01:49 +01:00
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
// Transfer money and shares
|
2018-12-29 11:01:49 +01:00
|
|
|
transferMoneyAndShares(b.account, a.account, volume * price, volume);
|
2018-12-29 10:35:47 +01:00
|
|
|
|
|
|
|
// Update volume
|
|
|
|
b.volume -= volume;
|
|
|
|
a.volume -= volume;
|
|
|
|
|
|
|
|
b.cost += price * volume;
|
|
|
|
a.cost += price * volume;
|
|
|
|
|
|
|
|
removeOrderIfExecuted(a);
|
|
|
|
removeOrderIfExecuted(b);
|
2018-12-29 11:01:49 +01:00
|
|
|
|
|
|
|
a.account.notfiyListeners();
|
|
|
|
b.account.notfiyListeners();
|
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
}
|
|
|
|
|
2018-12-29 01:39:38 +01:00
|
|
|
private void removeOrderIfExecuted(Order o) {
|
|
|
|
|
|
|
|
if (o.volume != 0) {
|
|
|
|
|
|
|
|
o.status = Order.Status.PARTIALLY_EXECUTED;
|
|
|
|
//o.account.update(o);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// o.account.orders.remove(o.id);
|
|
|
|
SortedSet book = order_books.get(o.type);
|
|
|
|
|
|
|
|
book.remove(book.first());
|
|
|
|
|
2018-12-29 05:25:42 +01:00
|
|
|
// o.status = OrderStatus.CLOSED;
|
|
|
|
// o.account.update(o);
|
2018-12-29 01:39:38 +01:00
|
|
|
}
|
2018-12-26 13:11:35 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
private void executeOrders() {
|
2018-12-28 11:37:52 +01:00
|
|
|
|
2018-12-29 05:25:42 +01:00
|
|
|
Quote q = null;
|
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
double volume_total = 0;
|
|
|
|
double money_total = 0;
|
|
|
|
while (true) {
|
2018-12-29 10:35:47 +01:00
|
|
|
|
|
|
|
// Match unlimited sell orders against unlimited buy orders
|
2018-12-29 13:30:07 +01:00
|
|
|
while (!ul_sell.isEmpty() && !ul_buy.isEmpty()) {
|
2018-12-29 10:35:47 +01:00
|
|
|
Order a = ul_sell.first();
|
|
|
|
Order b = ul_buy.first();
|
|
|
|
Double price = getBestPrice();
|
|
|
|
|
2018-12-29 11:01:49 +01:00
|
|
|
if (price == null) {
|
|
|
|
// Threre is no price available, we can't match, we
|
|
|
|
// have to wait until some limited orders come in
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-12-29 13:30:07 +01:00
|
|
|
// calculate volume by best fit
|
|
|
|
double volume = b.volume >= a.volume ? a.volume : b.volume;
|
|
|
|
|
|
|
|
double avdiff = b.limit - price * volume;
|
2019-01-08 09:24:08 +01:00
|
|
|
// b.account.addAvail(assetpair.getCurrency(), avdiff);
|
2018-12-29 13:30:07 +01:00
|
|
|
|
|
|
|
finishTrade(b, a, price, volume);
|
|
|
|
volume_total += volume;
|
|
|
|
money_total += price * volume;
|
|
|
|
|
|
|
|
Order.Type type = Order.Type.BUYLIMIT;
|
|
|
|
if (!compact_history) {
|
|
|
|
q = new Quote(quote_id_generator.getNext());
|
|
|
|
q.price = price;
|
|
|
|
q.volume = volume;
|
|
|
|
q.time = outer.world.currentTimeMillis();
|
2018-12-31 14:11:18 +01:00
|
|
|
q.type = type;
|
2018-12-29 13:30:07 +01:00
|
|
|
addQuoteToHistory(q);
|
|
|
|
}
|
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
//this.checkSLOrders(price);
|
2018-12-26 13:11:35 +01:00
|
|
|
}
|
2018-12-29 10:35:47 +01:00
|
|
|
|
|
|
|
/*
|
2018-12-26 13:11:35 +01:00
|
|
|
while (!ul_buy.isEmpty() && !ask.isEmpty()) {
|
|
|
|
Order a = ask.first();
|
|
|
|
Order b = ul_buy.first();
|
|
|
|
double price = a.limit;
|
|
|
|
double volume = b.volume >= a.volume ? a.volume : b.volume;
|
|
|
|
finishTrade(b, a, price, volume);
|
|
|
|
volume_total += volume;
|
|
|
|
money_total += price * volume;
|
|
|
|
this.checkSLOrders(price);
|
|
|
|
}
|
|
|
|
// Match unlimited sell orders against limited buy orders
|
|
|
|
while (!ul_sell.isEmpty() && !bid.isEmpty()) {
|
|
|
|
Order b = bid.first();
|
|
|
|
Order a = ul_sell.first();
|
|
|
|
double price = b.limit;
|
|
|
|
double volume = b.volume >= a.volume ? a.volume : b.volume;
|
|
|
|
finishTrade(b, a, price, volume);
|
|
|
|
volume_total += volume;
|
|
|
|
money_total += price * volume;
|
|
|
|
this.checkSLOrders(price);
|
|
|
|
}
|
|
|
|
*/
|
2018-12-29 01:39:38 +01:00
|
|
|
//
|
2018-12-26 13:11:35 +01:00
|
|
|
// Match limited orders against limited orders
|
2018-12-29 01:39:38 +01:00
|
|
|
//
|
2018-12-28 13:06:15 +01:00
|
|
|
if (bidbook.isEmpty() || askbook.isEmpty()) {
|
2018-12-29 21:57:49 +01:00
|
|
|
// no limit orders at all, nothing to do
|
2018-12-26 13:11:35 +01:00
|
|
|
break;
|
|
|
|
}
|
2018-12-29 01:39:38 +01:00
|
|
|
|
|
|
|
// Get the top orders - each from bidbook and askbook, but
|
|
|
|
// let orders stay in their order books
|
2018-12-28 13:06:15 +01:00
|
|
|
Order b = bidbook.first();
|
|
|
|
Order a = askbook.first();
|
2018-12-29 01:39:38 +01:00
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
if (b.limit < a.limit) {
|
2018-12-29 01:39:38 +01:00
|
|
|
// limits do not match, so there is nothing to do
|
2018-12-26 13:11:35 +01:00
|
|
|
break;
|
|
|
|
}
|
2018-12-29 01:39:38 +01:00
|
|
|
|
|
|
|
// There is a match. Next we calculate price and volume.
|
|
|
|
// The price is set by the order with lower ID because the
|
|
|
|
// order with lower ID was placed first. Also the order with
|
|
|
|
// the lower id is maker, while the higher ID is the taker.
|
2018-12-29 05:25:42 +01:00
|
|
|
double price;
|
|
|
|
Order.Type type;
|
|
|
|
if (b.id.compareTo(a.id) < 0) {
|
|
|
|
price = b.limit;
|
|
|
|
type = Order.Type.SELL;
|
|
|
|
} else {
|
|
|
|
price = a.limit;
|
2018-12-29 10:35:47 +01:00
|
|
|
type = Order.Type.BUY;
|
2018-12-29 05:25:42 +01:00
|
|
|
}
|
2018-12-29 01:39:38 +01:00
|
|
|
|
|
|
|
// The volume is calculated by best fit
|
2018-12-26 13:11:35 +01:00
|
|
|
double volume = b.volume >= a.volume ? a.volume : b.volume;
|
2018-12-29 01:39:38 +01:00
|
|
|
|
|
|
|
// Update available currency for the buyer.
|
|
|
|
// For sellers there is no need to update.
|
2018-12-29 05:25:42 +01:00
|
|
|
double avdiff = b.limit * volume - price * volume;
|
2019-01-08 09:24:08 +01:00
|
|
|
// b.account.addAvail(assetpair.getCurrency(), avdiff);
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2019-01-08 16:52:04 +01:00
|
|
|
// Unbind
|
|
|
|
|
|
|
|
double bound = b.account.getBound(assetpair.getCurrency());
|
|
|
|
double addbound = volume*b.limit;
|
|
|
|
|
|
|
|
b.account.addBound(assetpair.getCurrency(), volume*b.limit);
|
|
|
|
b.account.addBound(assetpair.getAsset(), -volume);
|
|
|
|
|
|
|
|
a.account.addBound(assetpair.getCurrency(), -volume*b.limit);
|
|
|
|
a.account.addBound(assetpair.getAsset(), volume);
|
|
|
|
|
|
|
|
|
2019-01-01 14:23:01 +01:00
|
|
|
// b.account.addMarginAvail(assetpair.getCurrency(), avdiff/b.account.getLeverage());
|
2018-12-29 11:01:49 +01:00
|
|
|
finishTrade(b, a, price, volume);
|
2018-12-29 01:39:38 +01:00
|
|
|
|
2018-12-29 05:25:42 +01:00
|
|
|
if (!compact_history) {
|
|
|
|
q = new Quote(quote_id_generator.getNext());
|
|
|
|
q.price = price;
|
|
|
|
q.volume = volume;
|
|
|
|
q.time = outer.world.currentTimeMillis();
|
2018-12-29 10:35:47 +01:00
|
|
|
q.type = type;
|
2018-12-29 05:25:42 +01:00
|
|
|
addQuoteToHistory(q);
|
|
|
|
}
|
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
volume_total += volume;
|
|
|
|
money_total += price * volume;
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
// statistics.trades++;
|
|
|
|
// this.checkSLOrders(price);
|
|
|
|
}
|
2018-12-29 11:14:04 +01:00
|
|
|
|
|
|
|
if (volume_total == 0) {
|
2018-12-26 13:11:35 +01:00
|
|
|
return;
|
|
|
|
}
|
2018-12-29 05:25:42 +01:00
|
|
|
|
|
|
|
Quote qc;
|
|
|
|
qc = new Quote(quote_id_generator.getNext());
|
|
|
|
qc.price = money_total / volume_total;
|
|
|
|
qc.volume = volume_total;
|
|
|
|
qc.time = outer.world.currentTimeMillis();
|
|
|
|
|
|
|
|
if (compact_history) {
|
|
|
|
addQuoteToHistory(qc);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (compact_last) {
|
|
|
|
last_quote = qc;
|
|
|
|
} else {
|
|
|
|
last_quote = q;
|
|
|
|
}
|
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
}
|
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
public Double getBestPrice() {
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
Order b;
|
|
|
|
Order a;
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
// Get first limited orders from bid and ask,
|
|
|
|
// assign null if no order is present
|
|
|
|
b = !bidbook.isEmpty() ? bidbook.first() : null;
|
|
|
|
a = !askbook.isEmpty() ? askbook.first() : null;
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
// If there is neither bid nor ask and also no last quote
|
|
|
|
// we can't return a price
|
|
|
|
if (last_quote == null && b == null && a == null) {
|
2018-12-26 13:11:35 +01:00
|
|
|
return null;
|
|
|
|
}
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
// Both limited bid and ask are present
|
|
|
|
if (a != null && b != null) {
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
// if there is no last quote, we calculate the prpice
|
|
|
|
// from bid and ask by simply averaging the limits
|
|
|
|
if (last_quote == null) {
|
|
|
|
return (bidbook.first().limit + askbook.first().limit) / 2.0;
|
2018-12-26 13:11:35 +01:00
|
|
|
}
|
2018-12-29 10:35:47 +01:00
|
|
|
|
|
|
|
// Last quote is below bid, so the best price is the
|
|
|
|
// current bid
|
|
|
|
if (last_quote.price < b.limit) {
|
|
|
|
return b.limit;
|
2018-12-26 13:11:35 +01:00
|
|
|
}
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
// Last price is grater ask, so return the current ask
|
|
|
|
if (last_quote.price > a.limit) {
|
2018-12-26 13:11:35 +01:00
|
|
|
return a.limit;
|
2018-12-29 10:35:47 +01:00
|
|
|
}
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
// Last price is somewhere between bid and ask,
|
|
|
|
// we return the last price
|
|
|
|
return last_quote.price;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// There is no limited ask, but limited bid
|
|
|
|
if (a != null) {
|
|
|
|
|
|
|
|
// retrun last quote if present or lower than ask,
|
|
|
|
// otherwise return the current ask
|
|
|
|
if (last_quote == null) {
|
|
|
|
return a.limit;
|
2018-12-26 13:11:35 +01:00
|
|
|
}
|
2018-12-29 10:35:47 +01:00
|
|
|
if (last_quote.price > a.limit) {
|
2018-12-26 13:11:35 +01:00
|
|
|
return a.limit;
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
}
|
2018-12-29 10:35:47 +01:00
|
|
|
return last_quote.price;
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
}
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-29 10:35:47 +01:00
|
|
|
// No bid, but ask is present
|
|
|
|
// Same as a !=null like before but reversed
|
|
|
|
if (b != null) {
|
|
|
|
if (last_quote == null) {
|
2018-12-26 13:11:35 +01:00
|
|
|
return b.limit;
|
|
|
|
}
|
2018-12-29 10:35:47 +01:00
|
|
|
if (last_quote.price
|
2018-12-29 05:25:42 +01:00
|
|
|
< b.limit) {
|
2018-12-26 13:11:35 +01:00
|
|
|
return b.limit;
|
|
|
|
}
|
2018-12-29 10:35:47 +01:00
|
|
|
return last_quote.price;
|
2018-12-26 13:11:35 +01:00
|
|
|
}
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-29 11:01:49 +01:00
|
|
|
// Both bid and ask are not present, return last quote.
|
|
|
|
// The case that last_quote is null can never happen here.
|
2018-12-29 10:35:47 +01:00
|
|
|
return last_quote.price;
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-26 13:11:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-01-14 08:43:24 +01:00
|
|
|
public Order createOrder(AccountImpl account, Order.Type type,
|
2018-12-29 11:14:04 +01:00
|
|
|
double volume, double limit) {
|
|
|
|
|
2018-12-29 01:39:38 +01:00
|
|
|
Order o;
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-28 11:37:52 +01:00
|
|
|
synchronized (account) {
|
|
|
|
// Round volume
|
2018-12-29 11:14:04 +01:00
|
|
|
double v = assetpair.getAsset().roundToDecimals(volume);
|
2018-12-28 11:37:52 +01:00
|
|
|
|
|
|
|
// Order volume must be grater than 0.0.
|
2018-12-29 11:14:04 +01:00
|
|
|
if (v <= 0.0) {
|
2018-12-28 11:37:52 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Round currency (limit)
|
2018-12-29 11:14:04 +01:00
|
|
|
double l = assetpair.getCurrency().roundToDecimals(limit);
|
2018-12-28 11:37:52 +01:00
|
|
|
|
|
|
|
double order_limit;
|
|
|
|
|
2019-01-08 09:24:08 +01:00
|
|
|
switch (type) {
|
|
|
|
case BUYLIMIT: {
|
|
|
|
|
|
|
|
if (!account.bind(assetpair, volume, limit)) {
|
|
|
|
System.out.printf("Not enough funds\n");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// return if not enough funds are available
|
2019-01-05 11:05:18 +01:00
|
|
|
// if (avail < v * l) {
|
|
|
|
// o = new Order(this, account, type, v, l);
|
|
|
|
// o.status = Order.Status.ERROR;
|
|
|
|
// System.out.printf("Error order no funds\n");
|
2019-01-08 09:24:08 +01:00
|
|
|
// return o;
|
|
|
|
// }
|
|
|
|
// account.margin_bound += v * l;
|
|
|
|
// reduce the available money
|
2019-01-05 11:05:18 +01:00
|
|
|
// account.assets_bound.put(currency, avail - v * l);
|
2018-12-31 14:11:18 +01:00
|
|
|
//account.addMarginAvail(currency, -((v * l)/account.getLeverage()));
|
2019-01-08 09:24:08 +01:00
|
|
|
order_limit = l;
|
|
|
|
break;
|
2018-12-28 11:37:52 +01:00
|
|
|
|
2019-01-08 09:24:08 +01:00
|
|
|
}
|
2018-12-28 11:37:52 +01:00
|
|
|
|
2019-01-08 09:24:08 +01:00
|
|
|
case BUY: {
|
|
|
|
// For an unlimited by order there is nothing to check
|
|
|
|
// other than currency is > 0.0
|
|
|
|
AbstractAsset currency = this.assetpair.getCurrency();
|
|
|
|
// Double avail = account.getAvail(currency);
|
|
|
|
Double avail = 1000.0;
|
|
|
|
// if (avail <= 0.0) {
|
|
|
|
// return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// }
|
|
|
|
// All available monney is assigned to this unlimited order
|
|
|
|
account.assets_bound.put(currency, 0.0);
|
|
|
|
// we "mis"use order_limit to memorize occupied ammount \
|
|
|
|
// of currency
|
|
|
|
order_limit = avail;
|
|
|
|
break;
|
2018-12-29 11:14:04 +01:00
|
|
|
|
2019-01-08 09:24:08 +01:00
|
|
|
}
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2019-01-08 09:24:08 +01:00
|
|
|
case SELLLIMIT:
|
|
|
|
case SELL: {
|
|
|
|
if (!account.bind(assetpair, -volume, limit)) {
|
|
|
|
System.out.printf("Not enough funds\n");
|
|
|
|
return null;
|
|
|
|
}
|
2018-12-28 11:37:52 +01:00
|
|
|
|
|
|
|
|
2019-01-08 09:24:08 +01:00
|
|
|
order_limit = l;
|
|
|
|
break;
|
2018-12-28 11:37:52 +01:00
|
|
|
|
2019-01-08 09:24:08 +01:00
|
|
|
}
|
2018-12-29 11:14:04 +01:00
|
|
|
|
2019-01-08 09:24:08 +01:00
|
|
|
default:
|
|
|
|
return null;
|
2018-12-29 05:25:42 +01:00
|
|
|
|
2018-12-28 11:37:52 +01:00
|
|
|
}
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
o = new Order(this, account, type, v, order_limit);
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
//System.out.printf("The new Order has: volume: %f limit: %f\n", o.getVolume(), o.getLimit());
|
|
|
|
synchronized (this) {
|
|
|
|
order_books.get(o.type).add(o);
|
2018-12-29 01:39:38 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
}
|
|
|
|
}
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
executeOrders();
|
2019-01-14 08:43:24 +01:00
|
|
|
// last_quote.price = 90; //75-12.5;
|
2019-01-04 19:09:20 +01:00
|
|
|
for (FiringEvent e : book_listener) {
|
2019-01-01 19:20:35 +01:00
|
|
|
e.fire();
|
2019-01-04 19:09:20 +01:00
|
|
|
}
|
2018-12-28 11:37:52 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
account.notfiyListeners();
|
|
|
|
return o;
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
}
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
HashSet<FiringEvent> book_listener
|
|
|
|
= new HashSet<>();
|
2018-12-26 13:11:35 +01:00
|
|
|
|
|
|
|
@Override
|
2019-01-08 09:24:08 +01:00
|
|
|
public void addOrderBookListener(EventListener listener) {
|
|
|
|
book_listener.add(new FiringEvent(listener));
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
}
|
2018-12-26 13:11:35 +01:00
|
|
|
|
2018-12-27 17:52:44 +01:00
|
|
|
@Override
|
2019-01-04 19:09:20 +01:00
|
|
|
public Set
|
|
|
|
getOrderBook(Order.Type type
|
|
|
|
) {
|
|
|
|
switch (type) {
|
|
|
|
case BUYLIMIT:
|
|
|
|
case BUY:
|
2019-01-01 19:20:35 +01:00
|
|
|
return Collections
|
2019-01-04 19:09:20 +01:00
|
|
|
.unmodifiableSet(bidbook
|
|
|
|
);
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
case SELLLIMIT:
|
|
|
|
case SELL:
|
2019-01-01 19:20:35 +01:00
|
|
|
return Collections
|
2019-01-04 19:09:20 +01:00
|
|
|
.unmodifiableSet(askbook
|
|
|
|
);
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
}
|
2018-12-27 17:52:44 +01:00
|
|
|
return null;
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
}
|
2018-12-26 13:11:35 +01:00
|
|
|
|
|
|
|
@Override
|
2019-01-04 19:09:20 +01:00
|
|
|
public Set
|
|
|
|
getBidBook() {
|
|
|
|
return getOrderBook(Order.Type.BUYLIMIT
|
|
|
|
);
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
}
|
2018-12-26 13:11:35 +01:00
|
|
|
|
|
|
|
@Override
|
2019-01-04 19:09:20 +01:00
|
|
|
public Set
|
|
|
|
getAskBook() {
|
|
|
|
return getOrderBook(Order.Type.SELL
|
|
|
|
);
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
}
|
2018-12-27 17:52:44 +01:00
|
|
|
|
2018-12-29 01:39:38 +01:00
|
|
|
@Override
|
2019-01-04 19:09:20 +01:00
|
|
|
public Set<Quote> getQuoteHistory() {
|
|
|
|
return Collections.unmodifiableSet(quote_history);
|
|
|
|
}
|
2019-01-01 19:20:35 +01:00
|
|
|
|
2019-01-04 19:09:20 +01:00
|
|
|
@Override
|
|
|
|
public Quote getLastQuote() {
|
|
|
|
return this.last_quote;
|
2018-12-29 01:39:38 +01:00
|
|
|
}
|
|
|
|
|
2018-12-28 11:37:52 +01:00
|
|
|
}
|