1
0

fix tests

This commit is contained in:
2024-11-18 20:23:05 +03:00
parent 23ac0ab6bb
commit 7d285efbe4
6 changed files with 595 additions and 1082 deletions

View 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);
}
}

View File

@@ -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) {
}
}

View File

@@ -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) {
}
}
}

View 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);
}
}

View File

@@ -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) {
}
}

View File

@@ -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) {
}
}
}