fix tests
This commit is contained in:
336
src/test/java/ConcurrentCurrencyExchangeTest.java
Normal file
336
src/test/java/ConcurrentCurrencyExchangeTest.java
Normal file
@@ -0,0 +1,336 @@
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.lionarius.api.CurrencyExchange;
|
||||
import ru.lionarius.api.client.Client;
|
||||
import ru.lionarius.api.currency.Currency;
|
||||
import ru.lionarius.api.currency.CurrencyPair;
|
||||
import ru.lionarius.api.order.OrderType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public abstract class ConcurrentCurrencyExchangeTest {
|
||||
protected abstract CurrencyExchange createExchange(Set<CurrencyPair> pairs);
|
||||
protected abstract void shutdownExchange(CurrencyExchange exchange);
|
||||
|
||||
private CurrencyExchange exchange;
|
||||
private CurrencyPair RUB_CNY;
|
||||
private ExecutorService executorService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Currency RUB = new Currency("RUB");
|
||||
Currency CNY = new Currency("CNY");
|
||||
RUB_CNY = new CurrencyPair(RUB, CNY);
|
||||
|
||||
exchange = createExchange(Set.of(RUB_CNY));
|
||||
|
||||
executorService = Executors.newFixedThreadPool(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConcurrentClientCreation() throws InterruptedException {
|
||||
int numClients = 100;
|
||||
var latch = new CountDownLatch(numClients);
|
||||
var futures = new ArrayList<CompletableFuture<Client>>();
|
||||
|
||||
for (int i = 0; i < numClients; i++) {
|
||||
var future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return exchange.createClient("Client " + UUID.randomUUID()).join();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
|
||||
assertEquals(numClients, futures.stream().map(CompletableFuture::join).distinct().count(),
|
||||
"All created clients should be unique");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testConcurrentOrderPlacement() throws InterruptedException {
|
||||
var numSellers = 10;
|
||||
var numBuyers = 10;
|
||||
var ordersPerClient = 20;
|
||||
var latch = new CountDownLatch(numSellers + numBuyers);
|
||||
|
||||
var sellers = new ArrayList<Client>();
|
||||
for (int i = 0; i < numSellers; i++) {
|
||||
var seller = exchange.createClient("Seller " + i).join();
|
||||
sellers.add(seller);
|
||||
}
|
||||
|
||||
var buyers = new ArrayList<Client>();
|
||||
for (int i = 0; i < numBuyers; i++) {
|
||||
var buyer = exchange.createClient("Buyer " + i).join();
|
||||
buyers.add(buyer);
|
||||
}
|
||||
|
||||
var sellerFutures = sellers.stream()
|
||||
.map(seller -> CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (int i = 0; i < ordersPerClient; i++) {
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 10.0, 10.0).join();
|
||||
Thread.sleep(10);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService))
|
||||
.toList();
|
||||
|
||||
var buyerFutures = buyers.stream()
|
||||
.map(buyer -> CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (int i = 0; i < ordersPerClient; i++) {
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 1.2, 10.0).join();
|
||||
Thread.sleep(10);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService))
|
||||
.toList();
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(
|
||||
Stream.concat(sellerFutures.stream(), buyerFutures.stream())
|
||||
.toArray(CompletableFuture[]::new)
|
||||
).join();
|
||||
|
||||
for (var seller : sellers) {
|
||||
assertTrue((long) exchange.getOrders(seller.id()).join().size() <= ordersPerClient);
|
||||
}
|
||||
|
||||
for (var buyer : buyers) {
|
||||
assertTrue((long) exchange.getOrders(buyer.id()).join().size() <= ordersPerClient);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConcurrentOrderMatchingWithDifferentPrices() throws InterruptedException {
|
||||
var numOrders = 50;
|
||||
var latch = new CountDownLatch(2);
|
||||
|
||||
var seller = exchange.createClient("Seller").join();
|
||||
var buyer = exchange.createClient("Buyer").join();
|
||||
|
||||
var sellerFuture = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (int i = 0; i < numOrders; i++) {
|
||||
var price = 1.0 + (i * 0.01);
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, price, 10.0).join();
|
||||
Thread.sleep(5);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
|
||||
var buyerFuture = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
var price = 2.0 - (i * 0.01);
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, price, 10.0).join();
|
||||
Thread.sleep(5);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(sellerFuture, buyerFuture).join();
|
||||
|
||||
var sellerOrders = exchange.getOrders(seller.id()).join();
|
||||
var buyerOrders = exchange.getOrders(buyer.id()).join();
|
||||
|
||||
var remainingSellerOrders = sellerOrders.stream().filter(order -> !order.closed()).count();
|
||||
var remainingBuyerOrders = buyerOrders.stream().filter(order -> !order.closed()).count();
|
||||
|
||||
assertTrue(remainingSellerOrders + remainingBuyerOrders < numOrders * 2,
|
||||
"Some orders should have been matched");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBalanceConsistencyWithConcurrentTradesSamePrices() throws InterruptedException {
|
||||
var numTraders = 100;
|
||||
var numOrders = 1000;
|
||||
var latch = new CountDownLatch(numTraders * 2);
|
||||
|
||||
var sellers = new ArrayList<Client>();
|
||||
var buyers = new ArrayList<Client>();
|
||||
var sellAmount = 100.0;
|
||||
var buyAmount = 100.0;
|
||||
|
||||
for (var i = 0; i < numTraders; i++) {
|
||||
var seller = exchange.createClient("Seller " + i).join();
|
||||
var buyer = exchange.createClient("Buyer " + i).join();
|
||||
|
||||
sellers.add(seller);
|
||||
buyers.add(buyer);
|
||||
}
|
||||
|
||||
var futures = new ArrayList<CompletableFuture<Void>>();
|
||||
|
||||
for (var seller : sellers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, buyAmount, sellAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, sellAmount, buyAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
var totalRubMoney = 0.0;
|
||||
var totalCnyMoney = 0.0;
|
||||
|
||||
for (var seller : sellers) {
|
||||
var orders = exchange.getOrders(seller.id()).join();
|
||||
|
||||
totalCnyMoney += orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney += orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(numOrders * numTraders * sellAmount, totalCnyMoney, 0.001);
|
||||
assertEquals(numOrders * numTraders * buyAmount, totalRubMoney, 0.001);
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var orders = exchange.getOrders(buyer.id()).join();
|
||||
|
||||
totalCnyMoney -= orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney -= orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(0.0, totalRubMoney, 0.001);
|
||||
assertEquals(0.0, totalCnyMoney, 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBalanceConsistencyWithConcurrentTrades() throws InterruptedException {
|
||||
var numTraders = 100;
|
||||
var numOrders = 1000;
|
||||
var latch = new CountDownLatch(numTraders * 3);
|
||||
|
||||
var sellers = new ArrayList<Client>();
|
||||
var buyers = new ArrayList<Client>();
|
||||
var sellAmount = 100.0;
|
||||
var buyAmount = 50.0;
|
||||
|
||||
for (var i = 0; i < numTraders; i++) {
|
||||
var seller = exchange.createClient("Seller " + i).join();
|
||||
var buyer = exchange.createClient("Buyer " + i).join();
|
||||
var buyer2 = exchange.createClient("Buyer " + i).join();
|
||||
|
||||
sellers.add(seller);
|
||||
buyers.add(buyer);
|
||||
buyers.add(buyer2);
|
||||
}
|
||||
|
||||
var futures = new ArrayList<CompletableFuture<Void>>();
|
||||
|
||||
for (var seller : sellers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, buyAmount, sellAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, sellAmount, buyAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
var totalRubMoney = 0.0;
|
||||
var totalCnyMoney = 0.0;
|
||||
|
||||
for (var seller : sellers) {
|
||||
var orders = exchange.getOrders(seller.id()).join();
|
||||
|
||||
totalCnyMoney += orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney += orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(numOrders * numTraders * sellAmount, totalCnyMoney, 0.001);
|
||||
assertEquals(numOrders * numTraders * buyAmount, totalRubMoney, 0.001);
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var orders = exchange.getOrders(buyer.id()).join();
|
||||
|
||||
totalCnyMoney -= orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney -= orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(0.0, totalCnyMoney, 0.001);
|
||||
assertEquals(0.0, totalRubMoney, 0.001);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
executorService.shutdown();
|
||||
try {
|
||||
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
|
||||
shutdownExchange(exchange);
|
||||
}
|
||||
}
|
||||
@@ -1,333 +1,16 @@
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.lionarius.api.CurrencyExchange;
|
||||
import ru.lionarius.api.client.Client;
|
||||
import ru.lionarius.api.currency.Currency;
|
||||
import ru.lionarius.api.currency.CurrencyPair;
|
||||
import ru.lionarius.api.order.OrderType;
|
||||
import ru.lionarius.impl.plain.PlainCurrencyExchange;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ConcurrentPlainCurrencyExchangeTest {
|
||||
private CurrencyExchange exchange;
|
||||
private CurrencyPair RUB_CNY;
|
||||
private ExecutorService executorService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Currency RUB = new Currency("RUB");
|
||||
Currency CNY = new Currency("CNY");
|
||||
RUB_CNY = new CurrencyPair(RUB, CNY);
|
||||
|
||||
exchange = new PlainCurrencyExchange(
|
||||
Set.of(RUB_CNY)
|
||||
);
|
||||
|
||||
executorService = Executors.newFixedThreadPool(10);
|
||||
class ConcurrentPlainCurrencyExchangeTest extends ConcurrentCurrencyExchangeTest {
|
||||
@Override
|
||||
public CurrencyExchange createExchange(Set<CurrencyPair> pairs) {
|
||||
return new PlainCurrencyExchange(pairs);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConcurrentClientCreation() throws InterruptedException {
|
||||
int numClients = 100;
|
||||
var latch = new CountDownLatch(numClients);
|
||||
var futures = new ArrayList<CompletableFuture<Client>>();
|
||||
|
||||
for (int i = 0; i < numClients; i++) {
|
||||
var future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return exchange.createClient("Client " + UUID.randomUUID()).join();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
|
||||
assertEquals(numClients, futures.stream().map(CompletableFuture::join).distinct().count(),
|
||||
"All created clients should be unique");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testConcurrentOrderPlacement() throws InterruptedException {
|
||||
var numSellers = 10;
|
||||
var numBuyers = 10;
|
||||
var ordersPerClient = 20;
|
||||
var latch = new CountDownLatch(numSellers + numBuyers);
|
||||
|
||||
var sellers = new ArrayList<Client>();
|
||||
for (int i = 0; i < numSellers; i++) {
|
||||
var seller = exchange.createClient("Seller " + i).join();
|
||||
sellers.add(seller);
|
||||
}
|
||||
|
||||
var buyers = new ArrayList<Client>();
|
||||
for (int i = 0; i < numBuyers; i++) {
|
||||
var buyer = exchange.createClient("Buyer " + i).join();
|
||||
buyers.add(buyer);
|
||||
}
|
||||
|
||||
var sellerFutures = sellers.stream()
|
||||
.map(seller -> CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (int i = 0; i < ordersPerClient; i++) {
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 10.0, 10.0).join();
|
||||
Thread.sleep(10);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService))
|
||||
.toList();
|
||||
|
||||
var buyerFutures = buyers.stream()
|
||||
.map(buyer -> CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (int i = 0; i < ordersPerClient; i++) {
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 1.2, 10.0).join();
|
||||
Thread.sleep(10);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService))
|
||||
.toList();
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(
|
||||
Stream.concat(sellerFutures.stream(), buyerFutures.stream())
|
||||
.toArray(CompletableFuture[]::new)
|
||||
).join();
|
||||
|
||||
for (var seller : sellers) {
|
||||
assertTrue((long) exchange.getOrders(seller.id()).join().size() <= ordersPerClient);
|
||||
}
|
||||
|
||||
for (var buyer : buyers) {
|
||||
assertTrue((long) exchange.getOrders(buyer.id()).join().size() <= ordersPerClient);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConcurrentOrderMatchingWithDifferentPrices() throws InterruptedException {
|
||||
var numOrders = 50;
|
||||
var latch = new CountDownLatch(2);
|
||||
|
||||
var seller = exchange.createClient("Seller").join();
|
||||
var buyer = exchange.createClient("Buyer").join();
|
||||
|
||||
var sellerFuture = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (int i = 0; i < numOrders; i++) {
|
||||
var price = 1.0 + (i * 0.01);
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, price, 10.0).join();
|
||||
Thread.sleep(5);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
|
||||
var buyerFuture = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
var price = 2.0 - (i * 0.01);
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, price, 10.0).join();
|
||||
Thread.sleep(5);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(sellerFuture, buyerFuture).join();
|
||||
|
||||
var sellerOrders = exchange.getOrders(seller.id()).join();
|
||||
var buyerOrders = exchange.getOrders(buyer.id()).join();
|
||||
|
||||
var remainingSellerOrders = sellerOrders.stream().filter(order -> !order.closed()).count();
|
||||
var remainingBuyerOrders = buyerOrders.stream().filter(order -> !order.closed()).count();
|
||||
|
||||
assertTrue(remainingSellerOrders + remainingBuyerOrders < numOrders * 2,
|
||||
"Some orders should have been matched");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBalanceConsistencyWithConcurrentTradesSamePrices() throws InterruptedException {
|
||||
var numTraders = 100;
|
||||
var numOrders = 1000;
|
||||
var latch = new CountDownLatch(numTraders * 2);
|
||||
|
||||
var sellers = new ArrayList<Client>();
|
||||
var buyers = new ArrayList<Client>();
|
||||
var sellAmount = 100.0;
|
||||
var buyAmount = 100.0;
|
||||
|
||||
for (var i = 0; i < numTraders; i++) {
|
||||
var seller = exchange.createClient("Seller " + i).join();
|
||||
var buyer = exchange.createClient("Buyer " + i).join();
|
||||
|
||||
sellers.add(seller);
|
||||
buyers.add(buyer);
|
||||
}
|
||||
|
||||
var futures = new ArrayList<CompletableFuture<Void>>();
|
||||
|
||||
for (var seller : sellers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, buyAmount, sellAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, sellAmount, buyAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
var totalRubMoney = 0.0;
|
||||
var totalCnyMoney = 0.0;
|
||||
|
||||
for (var seller : sellers) {
|
||||
var orders = exchange.getOrders(seller.id()).join();
|
||||
|
||||
totalCnyMoney += orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney += orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(numOrders * numTraders * sellAmount, totalCnyMoney, 0.001);
|
||||
assertEquals(numOrders * numTraders * buyAmount, totalRubMoney, 0.001);
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var orders = exchange.getOrders(buyer.id()).join();
|
||||
|
||||
totalCnyMoney -= orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney -= orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(0.0, totalRubMoney, 0.001);
|
||||
assertEquals(0.0, totalCnyMoney, 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBalanceConsistencyWithConcurrentTrades() throws InterruptedException {
|
||||
var numTraders = 100;
|
||||
var numOrders = 1000;
|
||||
var latch = new CountDownLatch(numTraders * 3);
|
||||
|
||||
var sellers = new ArrayList<Client>();
|
||||
var buyers = new ArrayList<Client>();
|
||||
var sellAmount = 100.0;
|
||||
var buyAmount = 50.0;
|
||||
|
||||
for (var i = 0; i < numTraders; i++) {
|
||||
var seller = exchange.createClient("Seller " + i).join();
|
||||
var buyer = exchange.createClient("Buyer " + i).join();
|
||||
var buyer2 = exchange.createClient("Buyer " + i).join();
|
||||
|
||||
sellers.add(seller);
|
||||
buyers.add(buyer);
|
||||
buyers.add(buyer2);
|
||||
}
|
||||
|
||||
var futures = new ArrayList<CompletableFuture<Void>>();
|
||||
|
||||
for (var seller : sellers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, buyAmount, sellAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, sellAmount, buyAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
var totalRubMoney = 0.0;
|
||||
var totalCnyMoney = 0.0;
|
||||
|
||||
for (var seller : sellers) {
|
||||
var orders = exchange.getOrders(seller.id()).join();
|
||||
|
||||
totalCnyMoney += orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney += orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(numOrders * numTraders * sellAmount, totalCnyMoney, 0.001);
|
||||
assertEquals(numOrders * numTraders * buyAmount, totalRubMoney, 0.001);
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var orders = exchange.getOrders(buyer.id()).join();
|
||||
|
||||
totalCnyMoney -= orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney -= orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(0.0, totalCnyMoney, 0.001);
|
||||
assertEquals(0.0, totalRubMoney, 0.001);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
executorService.shutdown();
|
||||
try {
|
||||
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
@Override
|
||||
protected void shutdownExchange(CurrencyExchange exchange) {
|
||||
}
|
||||
}
|
||||
@@ -1,336 +1,20 @@
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.lionarius.api.client.Client;
|
||||
import ru.lionarius.api.currency.Currency;
|
||||
import ru.lionarius.api.CurrencyExchange;
|
||||
import ru.lionarius.api.currency.CurrencyPair;
|
||||
import ru.lionarius.api.order.OrderType;
|
||||
import ru.lionarius.impl.queue.QueueCurrencyExchange;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ConcurrentQueueCurrencyExchangeTest {
|
||||
private QueueCurrencyExchange exchange;
|
||||
private CurrencyPair RUB_CNY;
|
||||
private ExecutorService executorService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Currency RUB = new Currency("RUB");
|
||||
Currency CNY = new Currency("CNY");
|
||||
RUB_CNY = new CurrencyPair(RUB, CNY);
|
||||
|
||||
exchange = new QueueCurrencyExchange(
|
||||
Set.of(RUB_CNY)
|
||||
);
|
||||
|
||||
executorService = Executors.newFixedThreadPool(10);
|
||||
class ConcurrentQueueCurrencyExchangeTest extends ConcurrentCurrencyExchangeTest {
|
||||
@Override
|
||||
protected CurrencyExchange createExchange(Set<CurrencyPair> pairs) {
|
||||
return new QueueCurrencyExchange(pairs);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConcurrentClientCreation() throws InterruptedException {
|
||||
int numClients = 100;
|
||||
var latch = new CountDownLatch(numClients);
|
||||
var futures = new ArrayList<CompletableFuture<Client>>();
|
||||
|
||||
for (int i = 0; i < numClients; i++) {
|
||||
var future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return exchange.createClient("Client " + UUID.randomUUID()).join();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
|
||||
assertEquals(numClients, futures.stream().map(CompletableFuture::join).distinct().count(),
|
||||
"All created clients should be unique");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testConcurrentOrderPlacement() throws InterruptedException {
|
||||
var numSellers = 10;
|
||||
var numBuyers = 10;
|
||||
var ordersPerClient = 20;
|
||||
var latch = new CountDownLatch(numSellers + numBuyers);
|
||||
|
||||
var sellers = new ArrayList<Client>();
|
||||
for (int i = 0; i < numSellers; i++) {
|
||||
var seller = exchange.createClient("Seller " + i).join();
|
||||
sellers.add(seller);
|
||||
}
|
||||
|
||||
var buyers = new ArrayList<Client>();
|
||||
for (int i = 0; i < numBuyers; i++) {
|
||||
var buyer = exchange.createClient("Buyer " + i).join();
|
||||
buyers.add(buyer);
|
||||
}
|
||||
|
||||
var sellerFutures = sellers.stream()
|
||||
.map(seller -> CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (int i = 0; i < ordersPerClient; i++) {
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 10.0, 10.0).join();
|
||||
Thread.sleep(10);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService))
|
||||
.toList();
|
||||
|
||||
var buyerFutures = buyers.stream()
|
||||
.map(buyer -> CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (int i = 0; i < ordersPerClient; i++) {
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 1.2, 10.0).join();
|
||||
Thread.sleep(10);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService))
|
||||
.toList();
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(
|
||||
Stream.concat(sellerFutures.stream(), buyerFutures.stream())
|
||||
.toArray(CompletableFuture[]::new)
|
||||
).join();
|
||||
|
||||
for (var seller : sellers) {
|
||||
assertTrue((long) exchange.getOrders(seller.id()).join().size() <= ordersPerClient);
|
||||
}
|
||||
|
||||
for (var buyer : buyers) {
|
||||
assertTrue((long) exchange.getOrders(buyer.id()).join().size() <= ordersPerClient);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConcurrentOrderMatchingWithDifferentPrices() throws InterruptedException {
|
||||
var numOrders = 50;
|
||||
var latch = new CountDownLatch(2);
|
||||
|
||||
var seller = exchange.createClient("Seller").join();
|
||||
var buyer = exchange.createClient("Buyer").join();
|
||||
|
||||
var sellerFuture = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (int i = 0; i < numOrders; i++) {
|
||||
var price = 1.0 + (i * 0.01);
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, price, 10.0).join();
|
||||
Thread.sleep(5);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
|
||||
var buyerFuture = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
var price = 2.0 - (i * 0.01);
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, price, 10.0).join();
|
||||
Thread.sleep(5);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(sellerFuture, buyerFuture).join();
|
||||
|
||||
var sellerOrders = exchange.getOrders(seller.id()).join();
|
||||
var buyerOrders = exchange.getOrders(buyer.id()).join();
|
||||
|
||||
var remainingSellerOrders = sellerOrders.stream().filter(order -> !order.closed()).count();
|
||||
var remainingBuyerOrders = buyerOrders.stream().filter(order -> !order.closed()).count();
|
||||
|
||||
assertTrue(remainingSellerOrders + remainingBuyerOrders < numOrders * 2,
|
||||
"Some orders should have been matched");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBalanceConsistencyWithConcurrentTradesSamePrices() throws InterruptedException {
|
||||
var numTraders = 100;
|
||||
var numOrders = 1000;
|
||||
var latch = new CountDownLatch(numTraders * 2);
|
||||
|
||||
var sellers = new ArrayList<Client>();
|
||||
var buyers = new ArrayList<Client>();
|
||||
var sellAmount = 100.0;
|
||||
var buyAmount = 100.0;
|
||||
|
||||
for (var i = 0; i < numTraders; i++) {
|
||||
var seller = exchange.createClient("Seller " + i).join();
|
||||
var buyer = exchange.createClient("Buyer " + i).join();
|
||||
|
||||
sellers.add(seller);
|
||||
buyers.add(buyer);
|
||||
}
|
||||
|
||||
var futures = new ArrayList<CompletableFuture<Void>>();
|
||||
|
||||
for (var seller : sellers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, buyAmount, sellAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, sellAmount, buyAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
var totalRubMoney = 0.0;
|
||||
var totalCnyMoney = 0.0;
|
||||
|
||||
for (var seller : sellers) {
|
||||
var orders = exchange.getOrders(seller.id()).join();
|
||||
|
||||
totalCnyMoney += orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney += orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(numOrders * numTraders * sellAmount, totalCnyMoney, 0.001);
|
||||
assertEquals(numOrders * numTraders * buyAmount, totalRubMoney, 0.001);
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var orders = exchange.getOrders(buyer.id()).join();
|
||||
|
||||
totalCnyMoney -= orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney -= orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(0.0, totalRubMoney, 0.001);
|
||||
assertEquals(0.0, totalCnyMoney, 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBalanceConsistencyWithConcurrentTrades() throws InterruptedException {
|
||||
var numTraders = 100;
|
||||
var numOrders = 1000;
|
||||
var latch = new CountDownLatch(numTraders * 3);
|
||||
|
||||
var sellers = new ArrayList<Client>();
|
||||
var buyers = new ArrayList<Client>();
|
||||
var sellAmount = 100.0;
|
||||
var buyAmount = 50.0;
|
||||
|
||||
for (var i = 0; i < numTraders; i++) {
|
||||
var seller = exchange.createClient("Seller " + i).join();
|
||||
var buyer = exchange.createClient("Buyer " + i).join();
|
||||
var buyer2 = exchange.createClient("Buyer " + i).join();
|
||||
|
||||
sellers.add(seller);
|
||||
buyers.add(buyer);
|
||||
buyers.add(buyer2);
|
||||
}
|
||||
|
||||
var futures = new ArrayList<CompletableFuture<Void>>();
|
||||
|
||||
for (var seller : sellers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, buyAmount, sellAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
for (var i = 0; i < numOrders; i++) {
|
||||
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, sellAmount, buyAmount).join();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}, executorService);
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
var totalRubMoney = 0.0;
|
||||
var totalCnyMoney = 0.0;
|
||||
|
||||
for (var seller : sellers) {
|
||||
var orders = exchange.getOrders(seller.id()).join();
|
||||
|
||||
totalCnyMoney += orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney += orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(numOrders * numTraders * sellAmount, totalCnyMoney, 0.001);
|
||||
assertEquals(numOrders * numTraders * buyAmount, totalRubMoney, 0.001);
|
||||
|
||||
for (var buyer : buyers) {
|
||||
var orders = exchange.getOrders(buyer.id()).join();
|
||||
|
||||
totalCnyMoney -= orders.stream().mapToDouble(order -> order.originalData().quantity() - order.lastData().quantity()).sum();
|
||||
totalRubMoney -= orders.stream().mapToDouble(order -> order.originalData().price() - order.lastData().price()).sum();
|
||||
}
|
||||
|
||||
assertEquals(0.0, totalCnyMoney, 0.001);
|
||||
assertEquals(0.0, totalRubMoney, 0.001);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
executorService.shutdown();
|
||||
@Override
|
||||
protected void shutdownExchange(CurrencyExchange exchange) {
|
||||
try {
|
||||
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
exchange.shutdown();
|
||||
} catch (InterruptedException e) {
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
((QueueCurrencyExchange) exchange).shutdown();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
229
src/test/java/CurrencyExchangeTest.java
Normal file
229
src/test/java/CurrencyExchangeTest.java
Normal file
@@ -0,0 +1,229 @@
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.lionarius.api.CurrencyExchange;
|
||||
import ru.lionarius.api.currency.Currency;
|
||||
import ru.lionarius.api.currency.CurrencyPair;
|
||||
import ru.lionarius.api.order.OrderData;
|
||||
import ru.lionarius.api.order.OrderType;
|
||||
import ru.lionarius.api.order.message.OrderClosedMessage;
|
||||
import ru.lionarius.api.order.message.OrderFilledMessage;
|
||||
import ru.lionarius.api.order.message.OrderMessageType;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public abstract class CurrencyExchangeTest {
|
||||
protected abstract CurrencyExchange createExchange(Set<CurrencyPair> pairs);
|
||||
protected abstract void shutdownExchange(CurrencyExchange exchange);
|
||||
|
||||
private CurrencyExchange exchange;
|
||||
private CurrencyPair RUB_CNY;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Currency RUB = new Currency("RUB");
|
||||
Currency CNY = new Currency("CNY");
|
||||
RUB_CNY = new CurrencyPair(RUB, CNY);
|
||||
|
||||
exchange = createExchange(Set.of(RUB_CNY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateClient() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
assertNotNull(client);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateClientWithNullName() {
|
||||
assertThrows(ExecutionException.class, () -> exchange.createClient(null).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetBalancesNonexistentClient() {
|
||||
var nonexistentId = UUID.randomUUID();
|
||||
assertThrows(Exception.class, () -> exchange.getOrders(nonexistentId).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPlaceOrder() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
exchange.placeOrder(client.id(), RUB_CNY, OrderType.SELL, 120, 100.0).get();
|
||||
|
||||
exchange.getOrders(client.id()).get().getFirst();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOrderPriceValidation() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
assertThrows(ExecutionException.class, () ->
|
||||
exchange.placeOrder(client.id(), RUB_CNY, OrderType.SELL, -1.0, 100.0).get()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOrderQuantityValidation() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
assertThrows(ExecutionException.class, () ->
|
||||
exchange.placeOrder(client.id(), RUB_CNY, OrderType.SELL, 120.0, -100.0).get()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testMatchingOrders() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 120.0, 100.0).get();
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 120.0, 100.0).get();
|
||||
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
|
||||
assertEquals(3, sellOrderMessages.size());
|
||||
assertSame(sellOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPartialOrderMatching() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 120.0, 100.0).get();
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 60.0, 50.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
|
||||
assertEquals(2, sellOrderMessages.size());
|
||||
assertSame(sellOrderMessages.getLast().type(), OrderMessageType.FILLED);
|
||||
assertEquals(new OrderData(60.0, 50.0), ((OrderFilledMessage) sellOrderMessages.getLast()).newData());
|
||||
assertEquals(3, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPartialOrderMatchingMultiple() throws ExecutionException, InterruptedException {
|
||||
var buyer1 = exchange.createClient("Buyer1").get();
|
||||
var buyer2 = exchange.createClient("Buyer2").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 120.0, 100.0).get();
|
||||
var buyOrderId1 = exchange.placeOrder(buyer1.id(), RUB_CNY, OrderType.BUY, 60.0, 50.0).get();
|
||||
var buyOrderId2 = exchange.placeOrder(buyer2.id(), RUB_CNY, OrderType.BUY, 60.0, 50.0).get();
|
||||
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
var buyOrder1Messages = exchange.getOrderMessages(buyer1.id(), buyOrderId1).get();
|
||||
var buyOrder2Messages = exchange.getOrderMessages(buyer2.id(), buyOrderId2).get();
|
||||
|
||||
assertEquals(4, sellOrderMessages.size());
|
||||
assertSame(sellOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrder1Messages.size());
|
||||
assertSame(buyOrder1Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrder2Messages.size());
|
||||
assertSame(buyOrder2Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testPricePriorityInOrderMatching() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller1 = exchange.createClient("Seller1").get();
|
||||
var seller2 = exchange.createClient("Seller2").get();
|
||||
|
||||
var sellOrderId1 = exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 75, 50.0).get();
|
||||
var sellOrderId2 = exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 60, 50.0).get();
|
||||
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 75, 50.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
var sellOrder1Messages = exchange.getOrderMessages(seller1.id(), sellOrderId1).get();
|
||||
var sellOrder2Messages = exchange.getOrderMessages(seller2.id(), sellOrderId2).get();
|
||||
|
||||
assertEquals(1, sellOrder1Messages.size());
|
||||
assertSame(sellOrder1Messages.getLast().type(), OrderMessageType.CREATED);
|
||||
assertEquals(3, sellOrder2Messages.size());
|
||||
assertSame(sellOrder2Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPricePriorityWithMultipleOrders() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller1 = exchange.createClient("Seller1").get();
|
||||
var seller2 = exchange.createClient("Seller2").get();
|
||||
var seller3 = exchange.createClient("Seller3").get();
|
||||
|
||||
var sellOrderId1 = exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 75.0, 50.0).get();
|
||||
var sellOrderId2 = exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 65.0, 50.0).get();
|
||||
var sellOrderId3 = exchange.placeOrder(seller3.id(), RUB_CNY, OrderType.SELL, 60.0, 50.0).get();
|
||||
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 150.0, 100.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
var sellOrder1Messages = exchange.getOrderMessages(seller1.id(), sellOrderId1).get();
|
||||
var sellOrder2Messages = exchange.getOrderMessages(seller2.id(), sellOrderId2).get();
|
||||
var sellOrder3Messages = exchange.getOrderMessages(seller3.id(), sellOrderId3).get();
|
||||
|
||||
assertEquals(1, sellOrder1Messages.size());
|
||||
assertSame(sellOrder1Messages.getLast().type(), OrderMessageType.CREATED);
|
||||
assertEquals(3, sellOrder2Messages.size());
|
||||
assertSame(sellOrder2Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, sellOrder3Messages.size());
|
||||
assertSame(sellOrder3Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(4, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPriceOrderNotMatching() throws ExecutionException, InterruptedException {
|
||||
var buyer1 = exchange.createClient("Buyer1").get();
|
||||
var buyer2 = exchange.createClient("Buyer2").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var buyOrderId1 = exchange.placeOrder(buyer1.id(), RUB_CNY, OrderType.BUY, 80.0, 50.0).get();
|
||||
var buyOrderId2 = exchange.placeOrder(buyer2.id(), RUB_CNY, OrderType.BUY, 75.0, 50.0).get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 85, 50.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer1.id(), buyOrderId1).get();
|
||||
var buyOrderMessages2 = exchange.getOrderMessages(buyer2.id(), buyOrderId2).get();
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
|
||||
assertEquals(1, sellOrderMessages.size());
|
||||
assertEquals(1, buyOrderMessages.size());
|
||||
assertEquals(1, buyOrderMessages2.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelBuyOrder() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
var orderId = exchange.placeOrder(client.id(), RUB_CNY, OrderType.BUY, 100.0, 200.0).get();
|
||||
|
||||
exchange.cancelOrder(client.id(), orderId).get();
|
||||
|
||||
var orderMessages = exchange.getOrderMessages(client.id(), orderId).get();
|
||||
|
||||
assertEquals(2, orderMessages.size());
|
||||
assertSame(orderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertSame(((OrderClosedMessage) orderMessages.getLast()).reason(), OrderClosedMessage.Reason.CANCELLED);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
shutdownExchange(exchange);
|
||||
}
|
||||
}
|
||||
@@ -1,223 +1,16 @@
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.lionarius.api.CurrencyExchange;
|
||||
import ru.lionarius.api.currency.Currency;
|
||||
import ru.lionarius.api.currency.CurrencyPair;
|
||||
import ru.lionarius.api.order.OrderData;
|
||||
import ru.lionarius.api.order.OrderType;
|
||||
import ru.lionarius.api.order.message.OrderClosedMessage;
|
||||
import ru.lionarius.api.order.message.OrderFilledMessage;
|
||||
import ru.lionarius.api.order.message.OrderMessageType;
|
||||
import ru.lionarius.impl.plain.PlainCurrencyExchange;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PlainCurrencyExchangeTest {
|
||||
private CurrencyExchange exchange;
|
||||
private CurrencyPair RUB_CNY;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Currency RUB = new Currency("RUB");
|
||||
Currency CNY = new Currency("CNY");
|
||||
RUB_CNY = new CurrencyPair(RUB, CNY);
|
||||
|
||||
exchange = new PlainCurrencyExchange(
|
||||
Set.of(RUB_CNY)
|
||||
);
|
||||
class PlainCurrencyExchangeTest extends CurrencyExchangeTest {
|
||||
@Override
|
||||
public CurrencyExchange createExchange(Set<CurrencyPair> pairs) {
|
||||
return new PlainCurrencyExchange(pairs);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateClient() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
assertNotNull(client);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateClientWithNullName() {
|
||||
assertThrows(ExecutionException.class, () -> exchange.createClient(null).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetBalancesNonexistentClient() {
|
||||
var nonexistentId = UUID.randomUUID();
|
||||
assertThrows(Exception.class, () -> exchange.getOrders(nonexistentId).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPlaceOrder() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
exchange.placeOrder(client.id(), RUB_CNY, OrderType.SELL, 120, 100.0).get();
|
||||
|
||||
exchange.getOrders(client.id()).get().getFirst();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOrderPriceValidation() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
assertThrows(ExecutionException.class, () ->
|
||||
exchange.placeOrder(client.id(), RUB_CNY, OrderType.SELL, -1.0, 100.0).get()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOrderQuantityValidation() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
assertThrows(ExecutionException.class, () ->
|
||||
exchange.placeOrder(client.id(), RUB_CNY, OrderType.SELL, 120.0, -100.0).get()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testMatchingOrders() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 120.0, 100.0).get();
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 120.0, 100.0).get();
|
||||
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
|
||||
assertEquals(3, sellOrderMessages.size());
|
||||
assertSame(sellOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPartialOrderMatching() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 120.0, 100.0).get();
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 60.0, 50.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
|
||||
assertEquals(2, sellOrderMessages.size());
|
||||
assertSame(sellOrderMessages.getLast().type(), OrderMessageType.FILLED);
|
||||
assertEquals(new OrderData(60.0, 50.0), ((OrderFilledMessage) sellOrderMessages.getLast()).newData());
|
||||
assertEquals(3, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPartialOrderMatchingMultiple() throws ExecutionException, InterruptedException {
|
||||
var buyer1 = exchange.createClient("Buyer1").get();
|
||||
var buyer2 = exchange.createClient("Buyer2").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 120.0, 100.0).get();
|
||||
var buyOrderId1 = exchange.placeOrder(buyer1.id(), RUB_CNY, OrderType.BUY, 60.0, 50.0).get();
|
||||
var buyOrderId2 = exchange.placeOrder(buyer2.id(), RUB_CNY, OrderType.BUY, 60.0, 50.0).get();
|
||||
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
var buyOrder1Messages = exchange.getOrderMessages(buyer1.id(), buyOrderId1).get();
|
||||
var buyOrder2Messages = exchange.getOrderMessages(buyer2.id(), buyOrderId2).get();
|
||||
|
||||
assertEquals(4, sellOrderMessages.size());
|
||||
assertSame(sellOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrder1Messages.size());
|
||||
assertSame(buyOrder1Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrder2Messages.size());
|
||||
assertSame(buyOrder2Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testPricePriorityInOrderMatching() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller1 = exchange.createClient("Seller1").get();
|
||||
var seller2 = exchange.createClient("Seller2").get();
|
||||
|
||||
var sellOrderId1 = exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 75, 50.0).get();
|
||||
var sellOrderId2 = exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 60, 50.0).get();
|
||||
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 75, 50.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
var sellOrder1Messages = exchange.getOrderMessages(seller1.id(), sellOrderId1).get();
|
||||
var sellOrder2Messages = exchange.getOrderMessages(seller2.id(), sellOrderId2).get();
|
||||
|
||||
assertEquals(1, sellOrder1Messages.size());
|
||||
assertSame(sellOrder1Messages.getLast().type(), OrderMessageType.CREATED);
|
||||
assertEquals(3, sellOrder2Messages.size());
|
||||
assertSame(sellOrder2Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPricePriorityWithMultipleOrders() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller1 = exchange.createClient("Seller1").get();
|
||||
var seller2 = exchange.createClient("Seller2").get();
|
||||
var seller3 = exchange.createClient("Seller3").get();
|
||||
|
||||
var sellOrderId1 = exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 75.0, 50.0).get();
|
||||
var sellOrderId2 = exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 65.0, 50.0).get();
|
||||
var sellOrderId3 = exchange.placeOrder(seller3.id(), RUB_CNY, OrderType.SELL, 60.0, 50.0).get();
|
||||
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 150.0, 100.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
var sellOrder1Messages = exchange.getOrderMessages(seller1.id(), sellOrderId1).get();
|
||||
var sellOrder2Messages = exchange.getOrderMessages(seller2.id(), sellOrderId2).get();
|
||||
var sellOrder3Messages = exchange.getOrderMessages(seller3.id(), sellOrderId3).get();
|
||||
|
||||
assertEquals(1, sellOrder1Messages.size());
|
||||
assertSame(sellOrder1Messages.getLast().type(), OrderMessageType.CREATED);
|
||||
assertEquals(3, sellOrder2Messages.size());
|
||||
assertSame(sellOrder2Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, sellOrder3Messages.size());
|
||||
assertSame(sellOrder3Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(4, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPriceOrderNotMatching() throws ExecutionException, InterruptedException {
|
||||
var buyer1 = exchange.createClient("Buyer1").get();
|
||||
var buyer2 = exchange.createClient("Buyer2").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var buyOrderId1 = exchange.placeOrder(buyer1.id(), RUB_CNY, OrderType.BUY, 80.0, 50.0).get();
|
||||
var buyOrderId2 = exchange.placeOrder(buyer2.id(), RUB_CNY, OrderType.BUY, 75.0, 50.0).get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 85, 50.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer1.id(), buyOrderId1).get();
|
||||
var buyOrderMessages2 = exchange.getOrderMessages(buyer2.id(), buyOrderId2).get();
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
|
||||
assertEquals(1, sellOrderMessages.size());
|
||||
assertEquals(1, buyOrderMessages.size());
|
||||
assertEquals(1, buyOrderMessages2.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelBuyOrder() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
var orderId = exchange.placeOrder(client.id(), RUB_CNY, OrderType.BUY, 100.0, 200.0).get();
|
||||
|
||||
exchange.cancelOrder(client.id(), orderId).get();
|
||||
|
||||
var orderMessages = exchange.getOrderMessages(client.id(), orderId).get();
|
||||
|
||||
assertEquals(2, orderMessages.size());
|
||||
assertSame(orderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertSame(((OrderClosedMessage) orderMessages.getLast()).reason(), OrderClosedMessage.Reason.CANCELLED);
|
||||
@Override
|
||||
protected void shutdownExchange(CurrencyExchange exchange) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,232 +1,20 @@
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.lionarius.api.currency.Currency;
|
||||
import ru.lionarius.api.CurrencyExchange;
|
||||
import ru.lionarius.api.currency.CurrencyPair;
|
||||
import ru.lionarius.api.order.OrderData;
|
||||
import ru.lionarius.api.order.OrderType;
|
||||
import ru.lionarius.api.order.message.OrderClosedMessage;
|
||||
import ru.lionarius.api.order.message.OrderFilledMessage;
|
||||
import ru.lionarius.api.order.message.OrderMessageType;
|
||||
import ru.lionarius.impl.queue.QueueCurrencyExchange;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class QueueCurrencyExchangeTest {
|
||||
private QueueCurrencyExchange exchange;
|
||||
private CurrencyPair RUB_CNY;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Currency RUB = new Currency("RUB");
|
||||
Currency CNY = new Currency("CNY");
|
||||
RUB_CNY = new CurrencyPair(RUB, CNY);
|
||||
|
||||
exchange = new QueueCurrencyExchange(
|
||||
Set.of(RUB_CNY)
|
||||
);
|
||||
class QueueCurrencyExchangeTest extends CurrencyExchangeTest {
|
||||
@Override
|
||||
protected CurrencyExchange createExchange(Set<CurrencyPair> pairs) {
|
||||
return new QueueCurrencyExchange(pairs);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateClient() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
assertNotNull(client);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateClientWithNullName() {
|
||||
assertThrows(ExecutionException.class, () -> exchange.createClient(null).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetBalancesNonexistentClient() {
|
||||
var nonexistentId = UUID.randomUUID();
|
||||
assertThrows(Exception.class, () -> exchange.getOrders(nonexistentId).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPlaceOrder() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
exchange.placeOrder(client.id(), RUB_CNY, OrderType.SELL, 120, 100.0).get();
|
||||
|
||||
exchange.getOrders(client.id()).get().getFirst();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOrderPriceValidation() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
assertThrows(ExecutionException.class, () ->
|
||||
exchange.placeOrder(client.id(), RUB_CNY, OrderType.SELL, -1.0, 100.0).get()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOrderQuantityValidation() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
assertThrows(ExecutionException.class, () ->
|
||||
exchange.placeOrder(client.id(), RUB_CNY, OrderType.SELL, 120.0, -100.0).get()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testMatchingOrders() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 120.0, 100.0).get();
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 120.0, 100.0).get();
|
||||
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
|
||||
assertEquals(3, sellOrderMessages.size());
|
||||
assertSame(sellOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPartialOrderMatching() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 120.0, 100.0).get();
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 60.0, 50.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
|
||||
assertEquals(2, sellOrderMessages.size());
|
||||
assertSame(sellOrderMessages.getLast().type(), OrderMessageType.FILLED);
|
||||
assertEquals(new OrderData(60.0, 50.0), ((OrderFilledMessage) sellOrderMessages.getLast()).newData());
|
||||
assertEquals(3, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPartialOrderMatchingMultiple() throws ExecutionException, InterruptedException {
|
||||
var buyer1 = exchange.createClient("Buyer1").get();
|
||||
var buyer2 = exchange.createClient("Buyer2").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 120.0, 100.0).get();
|
||||
var buyOrderId1 = exchange.placeOrder(buyer1.id(), RUB_CNY, OrderType.BUY, 60.0, 50.0).get();
|
||||
var buyOrderId2 = exchange.placeOrder(buyer2.id(), RUB_CNY, OrderType.BUY, 60.0, 50.0).get();
|
||||
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
var buyOrder1Messages = exchange.getOrderMessages(buyer1.id(), buyOrderId1).get();
|
||||
var buyOrder2Messages = exchange.getOrderMessages(buyer2.id(), buyOrderId2).get();
|
||||
|
||||
assertEquals(4, sellOrderMessages.size());
|
||||
assertSame(sellOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrder1Messages.size());
|
||||
assertSame(buyOrder1Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrder2Messages.size());
|
||||
assertSame(buyOrder2Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testPricePriorityInOrderMatching() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller1 = exchange.createClient("Seller1").get();
|
||||
var seller2 = exchange.createClient("Seller2").get();
|
||||
|
||||
var sellOrderId1 = exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 75, 50.0).get();
|
||||
var sellOrderId2 = exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 60, 50.0).get();
|
||||
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 75, 50.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
var sellOrder1Messages = exchange.getOrderMessages(seller1.id(), sellOrderId1).get();
|
||||
var sellOrder2Messages = exchange.getOrderMessages(seller2.id(), sellOrderId2).get();
|
||||
|
||||
assertEquals(1, sellOrder1Messages.size());
|
||||
assertSame(sellOrder1Messages.getLast().type(), OrderMessageType.CREATED);
|
||||
assertEquals(3, sellOrder2Messages.size());
|
||||
assertSame(sellOrder2Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPricePriorityWithMultipleOrders() throws ExecutionException, InterruptedException {
|
||||
var buyer = exchange.createClient("Buyer").get();
|
||||
var seller1 = exchange.createClient("Seller1").get();
|
||||
var seller2 = exchange.createClient("Seller2").get();
|
||||
var seller3 = exchange.createClient("Seller3").get();
|
||||
|
||||
var sellOrderId1 = exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 75.0, 50.0).get();
|
||||
var sellOrderId2 = exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 65.0, 50.0).get();
|
||||
var sellOrderId3 = exchange.placeOrder(seller3.id(), RUB_CNY, OrderType.SELL, 60.0, 50.0).get();
|
||||
|
||||
var buyOrderId = exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 150.0, 100.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer.id(), buyOrderId).get();
|
||||
var sellOrder1Messages = exchange.getOrderMessages(seller1.id(), sellOrderId1).get();
|
||||
var sellOrder2Messages = exchange.getOrderMessages(seller2.id(), sellOrderId2).get();
|
||||
var sellOrder3Messages = exchange.getOrderMessages(seller3.id(), sellOrderId3).get();
|
||||
|
||||
assertEquals(1, sellOrder1Messages.size());
|
||||
assertSame(sellOrder1Messages.getLast().type(), OrderMessageType.CREATED);
|
||||
assertEquals(3, sellOrder2Messages.size());
|
||||
assertSame(sellOrder2Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(3, sellOrder3Messages.size());
|
||||
assertSame(sellOrder3Messages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertEquals(4, buyOrderMessages.size());
|
||||
assertSame(buyOrderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPriceOrderNotMatching() throws ExecutionException, InterruptedException {
|
||||
var buyer1 = exchange.createClient("Buyer1").get();
|
||||
var buyer2 = exchange.createClient("Buyer2").get();
|
||||
var seller = exchange.createClient("Seller").get();
|
||||
|
||||
var buyOrderId1 = exchange.placeOrder(buyer1.id(), RUB_CNY, OrderType.BUY, 80.0, 50.0).get();
|
||||
var buyOrderId2 = exchange.placeOrder(buyer2.id(), RUB_CNY, OrderType.BUY, 75.0, 50.0).get();
|
||||
|
||||
var sellOrderId = exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 85, 50.0).get();
|
||||
|
||||
var buyOrderMessages = exchange.getOrderMessages(buyer1.id(), buyOrderId1).get();
|
||||
var buyOrderMessages2 = exchange.getOrderMessages(buyer2.id(), buyOrderId2).get();
|
||||
var sellOrderMessages = exchange.getOrderMessages(seller.id(), sellOrderId).get();
|
||||
|
||||
assertEquals(1, sellOrderMessages.size());
|
||||
assertEquals(1, buyOrderMessages.size());
|
||||
assertEquals(1, buyOrderMessages2.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelBuyOrder() throws ExecutionException, InterruptedException {
|
||||
var client = exchange.createClient("Trader").get();
|
||||
|
||||
var orderId = exchange.placeOrder(client.id(), RUB_CNY, OrderType.BUY, 100.0, 200.0).get();
|
||||
|
||||
exchange.cancelOrder(client.id(), orderId).get();
|
||||
|
||||
var orderMessages = exchange.getOrderMessages(client.id(), orderId).get();
|
||||
|
||||
assertEquals(2, orderMessages.size());
|
||||
assertSame(orderMessages.getLast().type(), OrderMessageType.CLOSED);
|
||||
assertSame(((OrderClosedMessage) orderMessages.getLast()).reason(), OrderClosedMessage.Reason.CANCELLED);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
@Override
|
||||
protected void shutdownExchange(CurrencyExchange exchange) {
|
||||
try {
|
||||
exchange.shutdown();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
((QueueCurrencyExchange) exchange).shutdown();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user