1
0
This commit is contained in:
2024-11-17 23:05:53 +03:00
parent 9ab033bd58
commit 045a5a9f64
2 changed files with 94 additions and 96 deletions

View File

@@ -191,22 +191,22 @@ class ConcurrentCurrencyExchangeTest {
@Test
void testConcurrentOrderPlacement() throws InterruptedException {
var numSellers = 5;
var numBuyers = 5;
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();
exchange.deposit(seller.id(), CNY, 1000.0).join();
exchange.deposit(seller.id(), CNY, 1000000.0).join();
sellers.add(seller);
}
var buyers = new ArrayList<Client>();
for (int i = 0; i < numBuyers; i++) {
var buyer = exchange.createClient("Buyer " + i).join();
exchange.deposit(buyer.id(), RUB, 1200.0).join();
exchange.deposit(buyer.id(), RUB, 1200000.0).join();
buyers.add(buyer);
}
@@ -214,7 +214,7 @@ class ConcurrentCurrencyExchangeTest {
.map(seller -> CompletableFuture.runAsync(() -> {
try {
for (int i = 0; i < ordersPerClient; i++) {
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 1.2, 10.0).join();
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 10.0, 10.0).join();
Thread.sleep(10);
}
} catch (InterruptedException e) {
@@ -350,20 +350,22 @@ class ConcurrentCurrencyExchangeTest {
}
@Test
void testBalanceConsistencyWithConcurrentTradesAndWithdrawals() throws InterruptedException {
var numTraders = 5;
void testBalanceConsistencyWithConcurrentTrades() throws InterruptedException {
var numTraders = 10;
var numOrders = 1000;
var latch = new CountDownLatch(numTraders * 2);
var sellers = new ArrayList<Client>();
var buyers = new ArrayList<Client>();
var initialAmount = 1000.0;
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();
exchange.deposit(seller.id(), CNY, initialAmount).join();
exchange.deposit(buyer.id(), RUB, initialAmount).join();
exchange.deposit(seller.id(), CNY, sellAmount * numOrders).join();
exchange.deposit(buyer.id(), RUB, buyAmount * numOrders).join();
sellers.add(seller);
buyers.add(buyer);
@@ -374,17 +376,10 @@ class ConcurrentCurrencyExchangeTest {
for (var seller : sellers) {
var future = CompletableFuture.runAsync(() -> {
try {
for (int i = 0; i < 10; i++) {
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, 1.2, 10.0).join();
try {
exchange.withdraw(seller.id(), CNY, 1.0).join();
} catch (CompletionException ignored) {
for (var i = 0; i < numOrders; i++)
{
exchange.placeOrder(seller.id(), RUB_CNY, OrderType.SELL, buyAmount, sellAmount).join();
}
Thread.sleep(10);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
@@ -395,16 +390,10 @@ class ConcurrentCurrencyExchangeTest {
for (var buyer : buyers) {
var future = CompletableFuture.runAsync(() -> {
try {
for (int i = 0; i < 10; i++) {
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 1.2, 10.0).join();
try {
exchange.withdraw(buyer.id(), RUB, 1.0).join();
} catch (CompletionException ignored) {
for (var i = 0; i < numOrders; i++)
{
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, sellAmount, buyAmount).join();
}
Thread.sleep(10);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
@@ -414,12 +403,22 @@ class ConcurrentCurrencyExchangeTest {
latch.await(30, TimeUnit.SECONDS);
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
var totalRubMoney = 0.0;
var totalCnyMoney = 0.0;
for (var client : Stream.concat(sellers.stream(), buyers.stream()).toList()) {
var balances = exchange.getBalances(client.id()).join();
assertTrue(balances.get(RUB) >= 0, "RUB balance should not be negative");
assertTrue(balances.get(CNY) >= 0, "CNY balance should not be negative");
for (var seller : sellers) {
var balances = exchange.getBalances(seller.id()).join();
totalCnyMoney += balances.get(CNY);
totalRubMoney += balances.get(RUB);
}
for (var buyer : buyers) {
var balances = exchange.getBalances(buyer.id()).join();
totalCnyMoney += balances.get(CNY);
totalRubMoney += balances.get(RUB);
}
assertEquals(totalRubMoney, sellAmount * numOrders * numTraders, 0.001);
assertEquals(totalCnyMoney, sellAmount * numOrders * numTraders, 0.001);
}
@AfterEach

View File

@@ -31,7 +31,6 @@ class CurrencyExchangeTest {
);
}
// Client Tests
@Test
void testCreateClient() throws ExecutionException, InterruptedException {
var client = exchange.createClient("Trader").get();
@@ -52,7 +51,7 @@ class CurrencyExchangeTest {
assertThrows(Exception.class, () -> exchange.getBalances(nonexistentId).get());
}
// Deposit Tests
@Test
void testDeposit() throws ExecutionException, InterruptedException {
var client = exchange.createClient("Trader").get();
@@ -77,7 +76,7 @@ class CurrencyExchangeTest {
assertThrows(ExecutionException.class, () -> exchange.deposit(client.id(), null, 100.0).get());
}
// Withdraw Tests
@Test
void testWithdraw() throws ExecutionException, InterruptedException {
var client = exchange.createClient("Trader").get();
@@ -104,16 +103,16 @@ class CurrencyExchangeTest {
exchange.deposit(client.id(), RUB, 500.0).get();
var balancesAfterDeposit = exchange.getBalances(client.id()).get();
assertEquals(500.0, balancesAfterDeposit.get(RUB)); // Trader has 500 RUB
assertEquals(500.0, balancesAfterDeposit.get(RUB));
exchange.withdraw(client.id(), RUB, 200.0).get();
var balancesAfterWithdraw = exchange.getBalances(client.id()).get();
assertEquals(300.0, balancesAfterWithdraw.get(RUB)); // Trader has 300 RUB
assertEquals(300.0, balancesAfterWithdraw.get(RUB));
assertThrows(ExecutionException.class, () -> exchange.withdraw(client.id(), RUB, 400.0).get()); // Trader has only 300 RUB left
assertThrows(ExecutionException.class, () -> exchange.withdraw(client.id(), RUB, 400.0).get());
}
// Order Tests
@Test
void testPlaceOrder() throws ExecutionException, InterruptedException {
var client = exchange.createClient("Trader").get();
@@ -156,7 +155,7 @@ class CurrencyExchangeTest {
);
}
// Order Matching Tests
@Test
void testMatchingOrders() throws ExecutionException, InterruptedException {
var buyer = exchange.createClient("Buyer").get();
@@ -171,10 +170,10 @@ class CurrencyExchangeTest {
Map<Currency, Double> buyerBalances = exchange.getBalances(buyer.id()).get();
Map<Currency, Double> sellerBalances = exchange.getBalances(seller.id()).get();
assertEquals(100.0, buyerBalances.get(CNY)); // Buyer bought 100 CNY
assertEquals(0.0, buyerBalances.get(RUB)); // Buyer sold 120 RUB so he has no RUB left
assertEquals(120.0, sellerBalances.get(RUB)); // Seller bought 120 RUB
assertEquals(0.0, sellerBalances.get(CNY)); // Seller sold 100 CNY so he has no CNY left
assertEquals(100.0, buyerBalances.get(CNY));
assertEquals(0.0, buyerBalances.get(RUB));
assertEquals(120.0, sellerBalances.get(RUB));
assertEquals(0.0, sellerBalances.get(CNY));
}
@Test
@@ -191,13 +190,13 @@ class CurrencyExchangeTest {
var buyerBalances = exchange.getBalances(buyer.id()).get();
var sellerBalances = exchange.getBalances(seller.id()).get();
assertEquals(50.0, buyerBalances.get(CNY)); // Buyer bought 50 CNY
assertEquals(60.0, sellerBalances.get(RUB)); // Seller bought 60 RUB
assertEquals(50.0, buyerBalances.get(CNY));
assertEquals(60.0, sellerBalances.get(RUB));
var sellerOrders = exchange.getActiveOrders(seller.id()).get();
var remainingOrder = sellerOrders.iterator().next();
// Seller has 60/50 RUB/CNY order left
assertEquals(RUB_CNY, remainingOrder.pair());
assertEquals(50.0, remainingOrder.quantity());
assertEquals(60.0, remainingOrder.price());
@@ -221,12 +220,12 @@ class CurrencyExchangeTest {
var buyer2Balances = exchange.getBalances(buyer2.id()).get();
var sellerBalances = exchange.getBalances(seller.id()).get();
assertEquals(50.0, buyer1Balances.get(CNY)); // Buyer1 bought 50 CNY
assertEquals(50.0, buyer2Balances.get(CNY)); // Buyer2 bought 50 CNY
assertEquals(120.0, sellerBalances.get(RUB)); // Seller bought 120 RUB
assertEquals(50.0, buyer1Balances.get(CNY));
assertEquals(50.0, buyer2Balances.get(CNY));
assertEquals(120.0, sellerBalances.get(RUB));
var sellerOrders = exchange.getActiveOrders(seller.id()).get();
assertFalse(sellerOrders.iterator().hasNext()); // Seller has no orders left
assertFalse(sellerOrders.iterator().hasNext());
}
@Test
@@ -243,10 +242,10 @@ class CurrencyExchangeTest {
var buyerBalances = exchange.getBalances(buyer.id()).get();
var sellerBalances = exchange.getBalances(seller.id()).get();
assertEquals(100.0, buyerBalances.get(CNY)); // Buyer bought 100 CNY
assertEquals(180.0, buyerBalances.get(RUB)); // Buyer spends 120 RUB
assertEquals(120.0, sellerBalances.get(RUB)); // Seller bought 120 RUB
assertEquals(100.0, sellerBalances.get(CNY)); // Seller retains remaining CNY
assertEquals(100.0, buyerBalances.get(CNY));
assertEquals(180.0, buyerBalances.get(RUB));
assertEquals(120.0, sellerBalances.get(RUB));
assertEquals(100.0, sellerBalances.get(CNY));
}
@Test
@@ -268,18 +267,18 @@ class CurrencyExchangeTest {
var buyer2Balances = exchange.getBalances(buyer2.id()).get();
var sellerBalances = exchange.getBalances(seller.id()).get();
assertEquals(30.0, buyer1Balances.get(CNY)); // Buyer1 bought 30 CNY
assertEquals(24.0, buyer1Balances.get(RUB)); // Buyer1 sold 36 CNY
assertEquals(30.0, buyer1Balances.get(CNY));
assertEquals(24.0, buyer1Balances.get(RUB));
assertEquals(30.0, buyer2Balances.get(CNY)); // Buyer2 bought 30 CNY
assertEquals(24.0, buyer2Balances.get(RUB)); // Buyer2 sold 36 CNY
assertEquals(30.0, buyer2Balances.get(CNY));
assertEquals(24.0, buyer2Balances.get(RUB));
var sellerOrders = exchange.getActiveOrders(seller.id()).get();
var sellerOrder = sellerOrders.iterator().next();
assertEquals(40.0, sellerOrder.quantity()); // 40 CNY reserved
assertEquals(48.0, sellerOrder.price()); // 1.2 RUB per CNY
assertEquals(72.0, sellerBalances.get(RUB)); // 72 RUB earned
assertEquals(40.0, sellerOrder.quantity());
assertEquals(48.0, sellerOrder.price());
assertEquals(72.0, sellerBalances.get(RUB));
}
@Test
@@ -314,8 +313,8 @@ class CurrencyExchangeTest {
exchange.deposit(seller1.id(), CNY, 50.0).get();
exchange.deposit(seller2.id(), CNY, 50.0).get();
exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 75, 50.0).get(); // Higher price
exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 60, 50.0).get(); // Lower price
exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 75, 50.0).get();
exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 60, 50.0).get();
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 75, 50.0).get();
@@ -323,14 +322,14 @@ class CurrencyExchangeTest {
var seller1Balances = exchange.getBalances(seller1.id()).get();
var seller2Balances = exchange.getBalances(seller2.id()).get();
assertEquals(50.0, buyerBalances.get(CNY)); // Buyer gets 50 CNY from the lower-priced seller
assertEquals(140.0, buyerBalances.get(RUB)); // Buyer spends 60 RUB
assertEquals(50.0, buyerBalances.get(CNY));
assertEquals(140.0, buyerBalances.get(RUB));
assertEquals(60.0, seller2Balances.get(RUB)); // Seller2 sells at 60 price
assertEquals(0.0, seller2Balances.get(CNY)); // Seller2 has no CNY left
assertEquals(60.0, seller2Balances.get(RUB));
assertEquals(0.0, seller2Balances.get(CNY));
assertNull(seller1Balances.get(RUB)); // Seller1 remains untouched
assertEquals(0.0, seller1Balances.get(CNY)); // Seller1 still has their CNY reserved
assertNull(seller1Balances.get(RUB));
assertEquals(0.0, seller1Balances.get(CNY));
}
@Test
@@ -345,9 +344,9 @@ class CurrencyExchangeTest {
exchange.deposit(seller2.id(), CNY, 50.0).get();
exchange.deposit(seller3.id(), CNY, 50.0).get();
exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 75.0, 50.0).get(); // Highest price
exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 65.0, 50.0).get(); // Medium price
exchange.placeOrder(seller3.id(), RUB_CNY, OrderType.SELL, 60.0, 50.0).get(); // Lowest price
exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 75.0, 50.0).get();
exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 65.0, 50.0).get();
exchange.placeOrder(seller3.id(), RUB_CNY, OrderType.SELL, 60.0, 50.0).get();
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 150.0, 100.0).get();
@@ -356,17 +355,17 @@ class CurrencyExchangeTest {
var seller2Balances = exchange.getBalances(seller2.id()).get();
var seller3Balances = exchange.getBalances(seller3.id()).get();
assertEquals(100.0, buyerBalances.get(CNY)); // Buyer receives 100 CNY (50 from seller3 and 50 from seller2)
assertEquals(175.0, buyerBalances.get(RUB)); // Buyer spends 125 RUB (60 + 65)
assertEquals(100.0, buyerBalances.get(CNY));
assertEquals(175.0, buyerBalances.get(RUB));
assertEquals(60.0, seller3Balances.get(RUB)); // Seller3 sells at 60 price
assertEquals(0.0, seller3Balances.get(CNY)); // Seller3's CNY is reduced to 0
assertEquals(60.0, seller3Balances.get(RUB));
assertEquals(0.0, seller3Balances.get(CNY));
assertEquals(65.0, seller2Balances.get(RUB)); // Seller2 sells at 65 price
assertEquals(0.0, seller2Balances.get(CNY)); // Seller2's CNY is reduced to 0
assertEquals(65.0, seller2Balances.get(RUB));
assertEquals(0.0, seller2Balances.get(CNY));
assertNull(seller1Balances.get(RUB)); // Seller1 remains untouched
assertEquals(0.0, seller1Balances.get(CNY)); // Seller1 still has their CNY reserved
assertNull(seller1Balances.get(RUB));
assertEquals(0.0, seller1Balances.get(CNY));
}
@Test
@@ -379,8 +378,8 @@ class CurrencyExchangeTest {
exchange.deposit(seller1.id(), CNY, 50.0).get();
exchange.deposit(seller2.id(), CNY, 50.0).get();
exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 60, 50.0).get(); // Placed first
exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 60, 50.0).get(); // Placed second
exchange.placeOrder(seller1.id(), RUB_CNY, OrderType.SELL, 60, 50.0).get();
exchange.placeOrder(seller2.id(), RUB_CNY, OrderType.SELL, 60, 50.0).get();
exchange.placeOrder(buyer.id(), RUB_CNY, OrderType.BUY, 60, 50.0).get();
@@ -388,13 +387,13 @@ class CurrencyExchangeTest {
var seller1Balances = exchange.getBalances(seller1.id()).get();
var seller2Balances = exchange.getBalances(seller2.id()).get();
assertEquals(50.0, buyerBalances.get(CNY)); // Buyer gets 50 CNY
assertEquals(140.0, buyerBalances.get(RUB)); // Buyer spends 60 RUB
assertEquals(50.0, buyerBalances.get(CNY));
assertEquals(140.0, buyerBalances.get(RUB));
assertEquals(60.0, seller1Balances.get(RUB)); // Seller1 sells first
assertEquals(0.0, seller1Balances.get(CNY)); // Seller1's CNY is reduced to 0
assertEquals(60.0, seller1Balances.get(RUB));
assertEquals(0.0, seller1Balances.get(CNY));
assertNull(seller2Balances.get(RUB)); // Seller2 remains untouched
assertNull(seller2Balances.get(RUB));
}
@Test
@@ -416,13 +415,13 @@ class CurrencyExchangeTest {
var buyer2Balances = exchange.getBalances(buyer2.id()).get();
var sellerBalances = exchange.getBalances(seller.id()).get();
assertNull(sellerBalances.get(RUB)); // Seller did not sell anything
assertEquals(0.0, sellerBalances.get(CNY)); // Seller has no CNY left
assertNull(sellerBalances.get(RUB));
assertEquals(0.0, sellerBalances.get(CNY));
assertNull(buyer1Balances.get(CNY)); // Buyer1 did not buy anything
assertNull(buyer1Balances.get(CNY));
assertEquals(120.0, buyer1Balances.get(RUB));
assertNull(buyer2Balances.get(CNY)); // Buyer2 did not buy anything
assertNull(buyer2Balances.get(CNY));
assertEquals(125.0, buyer2Balances.get(RUB));
}