diff --git a/core/src/main/java/ru/trader/core/Order.java b/core/src/main/java/ru/trader/core/Order.java index 9855b69..848acfc 100644 --- a/core/src/main/java/ru/trader/core/Order.java +++ b/core/src/main/java/ru/trader/core/Order.java @@ -96,6 +96,7 @@ public class Order implements Comparable { sb.append("sell=").append(sell); sb.append(", buy=").append(buy); sb.append(", profit=").append(profit); + sb.append(", count=").append(count); sb.append('}'); return sb.toString(); } diff --git a/core/src/main/java/ru/trader/graph/PathRoute.java b/core/src/main/java/ru/trader/graph/PathRoute.java index b1a4b0f..7e0836b 100644 --- a/core/src/main/java/ru/trader/graph/PathRoute.java +++ b/core/src/main/java/ru/trader/graph/PathRoute.java @@ -13,7 +13,9 @@ public class PathRoute extends Path { private final ArrayList orders = new ArrayList<>(); private final boolean expand; + private final int index; private double profit = 0; + private double balance = 0; private PathRoute tail; private int ordersCount = 0; public final static Order TRANSIT = null; @@ -21,11 +23,13 @@ public class PathRoute extends Path { public PathRoute(Vertex source, boolean expand) { super(source); this.expand = expand; + index = 0; } public PathRoute(Vertex source) { super(source); expand = false; + index = 0; } private PathRoute(PathRoute head, Vertex vertex, boolean refill) { @@ -35,6 +39,7 @@ public class PathRoute extends Path { expand = head.expand; //transit orders.add(ordersCount++, TRANSIT); + index = head.index+1; } @Override @@ -56,7 +61,10 @@ public class PathRoute extends Path { private void addOrder(Order order){ LOG.trace("Add order {} to path {}", order, this); orders.add(ordersCount++, order); - if (!expand) return; + if (expand) expand(order); + } + + private void expand(Order order){ LOG.trace("Expand orders"); if (hasNext()){ PathRoute next = getNext(); @@ -134,16 +142,77 @@ public class PathRoute extends Path { return tail != null; } - public void resort(double balance, long limit){ - if (isRoot()) return; + public void sort(double balance, long limit){ + // start on root only + if (isRoot()){ + this.balance = balance; + if (hasNext()) + getNext().forwardSort(limit); + } else { + getPrevious().sort(balance, limit); + } + } + + private void forwardSort(long limit){ + updateBalance(); + boolean needSort = false; for (Order order : orders) { if (order == TRANSIT) continue; - order.setMax(balance, limit); + if (order.getCount() < limit){ + needSort = true; + order.setMax(balance, limit); + } } - orders.sort(this::compareOrders); + if (needSort){ + LOG.trace("Simple sort"); + orders.sort(this::simpleCompareOrders); + LOG.trace("New order of orders {}", orders); + } + if (hasNext()){ + getNext().forwardSort(limit); + } else { + LOG.trace("Start back sort"); + Order best = orders.get(0); + profit = best == TRANSIT ? 0 : best.getProfit(); + LOG.trace("Max profit from {} = {}",getPrevious().get(), profit); + getPrevious().backwardSort(); + } + } + private void backwardSort(){ + if (isRoot()) return; + orders.sort(this::compareOrders); + LOG.trace("New order of orders {}", orders); updateProfit(); - getPrevious().resort(balance, limit); + getPrevious().backwardSort(); + } + + private void updateBalance() { + PathRoute p = getPrevious(); + balance = p.balance; + if (!p.isRoot()) { + Vendor buyer = p.get(); + while (!p.isRoot()){ + for (Order order : p.orders) { + if (order == TRANSIT) continue; + if (order.isBuyer(buyer) && balance < p.balance + order.getProfit()){ + balance = p.balance + order.getProfit(); + LOG.trace("Order {} is best to {}, new balance {}", order, buyer, balance); + } + + } + p = p.getPrevious(); + } + } + LOG.trace("Max balance on {} = {}", getPrevious().get(), balance); + } + + + private void updateProfit() { + Order best = orders.get(0); + if (best == TRANSIT) profit = getTransitProfit(); + else profit = getProfit(best); + LOG.trace("Max profit from {} = {}", getPrevious().get(), profit); } private double getTransitProfit(){ @@ -154,17 +223,21 @@ public class PathRoute extends Path { return profit; } + public double getBalance() { + return balance; + } + public double getProfit(Order order){ if (order == TRANSIT) return getTransitProfit(); if (isPathFrom(order.getBuyer())) return order.getProfit() + profit; return hasNext() ? getNext().getProfit(order) : order.getProfit(); } - private void updateProfit() { - Order best = orders.get(0); - if (best == TRANSIT) profit = getTransitProfit(); - else profit = getProfit(best); - + private int simpleCompareOrders(Order o1, Order o2){ + if (o1 != TRANSIT && o2 != TRANSIT){ + return o2.compareTo(o1); + } + return o1 == TRANSIT ? o2 == TRANSIT ? 0 : 1 : -1; } private int compareOrders(Order o1, Order o2){ @@ -177,10 +250,22 @@ public class PathRoute extends Path { return Double.compare(profit2, profit1); } - + public PathRoute getPath(int index){ + if (this.index == index) return this; + if (this.index > index){ + return isRoot() ? null : getPrevious().getPath(index); + } else { + return hasNext() ? getNext().getPath(index) : null; + } + } @Override - public String toString() { + public PathRoute getRoot() { + return (PathRoute) super.getRoot(); + } + + @Override + public String toString() { StringBuilder sb = new StringBuilder(); int step = !hasNext() || tail.ordersCount == 0 ? 1 : tail.ordersCount; for (int i = 0; i < ordersCount; i += step) { diff --git a/core/src/test/java/ru/trader/graph/PathRouteTest.java b/core/src/test/java/ru/trader/graph/PathRouteTest.java index a1eef82..3ede6d6 100644 --- a/core/src/test/java/ru/trader/graph/PathRouteTest.java +++ b/core/src/test/java/ru/trader/graph/PathRouteTest.java @@ -36,7 +36,7 @@ public class PathRouteTest extends Assert { PathRoute res = new PathRoute(new Vertex<>(v1)); res = (PathRoute) res.connectTo(new Vertex<>(v2), false); res.finish(); - res.resort(10000, 5); + res.sort(10000, 5); return (PathRoute) res.getRoot(); } @@ -51,6 +51,7 @@ public class PathRouteTest extends Assert { Order order2 = new Order(v1.getSell(ITEM2), v2.getBuy(ITEM2), 5); Order order3 = new Order(v1.getSell(ITEM3), v2.getBuy(ITEM3), 5); + assertEquals(10000, path.getBalance(), 0.0001); assertEquals(1000, path.getProfit(), 0.0001); TestUtil.assertCollectionEquals(orders, order1, order2, order3, PathRoute.TRANSIT); } @@ -72,7 +73,7 @@ public class PathRouteTest extends Assert { res = (PathRoute) res.connectTo(new Vertex<>(v2), false); res = (PathRoute) res.connectTo(new Vertex<>(v3), false); res.finish(); - res.resort(10000, 5); + res.sort(10000, 5); return (PathRoute) res.getRoot(); } @@ -86,12 +87,14 @@ public class PathRouteTest extends Assert { Order order2 = new Order(v2.getSell(ITEM2), v3.getBuy(ITEM2), 5); Order order3 = new Order(v1.getSell(ITEM3), v3.getBuy(ITEM3), 5); + assertEquals(10000, path.getBalance(), 0.0001); assertEquals(1000, path.getProfit(), 0.0001); TestUtil.assertCollectionEquals(orders, order1, PathRoute.TRANSIT, order3); path = path.getNext(); orders = path.getOrders(); + assertEquals(10000, path.getBalance(), 0.0001); assertEquals(750, path.getProfit(), 0.0001); TestUtil.assertCollectionEquals(orders, order2, PathRoute.TRANSIT); } @@ -110,7 +113,7 @@ public class PathRouteTest extends Assert { v2.add(new Offer(OFFER_TYPE.SELL, ITEM3, 320)); v3.add(new Offer(OFFER_TYPE.SELL, ITEM3, 390)); - v2.add(new Offer(OFFER_TYPE.BUY, ITEM2, 230)); + v2.add(new Offer(OFFER_TYPE.BUY, ITEM2, 225)); v3.add(new Offer(OFFER_TYPE.BUY, ITEM1, 200)); v4.add(new Offer(OFFER_TYPE.BUY, ITEM3, 450)); @@ -119,7 +122,7 @@ public class PathRouteTest extends Assert { res = (PathRoute) res.connectTo(new Vertex<>(v3), false); res = (PathRoute) res.connectTo(new Vertex<>(v4), false); res.finish(); - res.resort(10000, 5); + res.sort(10000, 5); return (PathRoute) res.getRoot(); } @@ -136,18 +139,21 @@ public class PathRouteTest extends Assert { Order order5 = new Order(v2.getSell(ITEM3), v4.getBuy(ITEM3), 5); Order order7 = new Order(v3.getSell(ITEM3), v4.getBuy(ITEM3), 5); + assertEquals(10000, path.getBalance(), 0.0001); assertEquals(800, path.getProfit(), 0.0001); - TestUtil.assertCollectionEquals(orders, order2, order1, order3, PathRoute.TRANSIT); + TestUtil.assertCollectionEquals(orders, order1, order2, order3, PathRoute.TRANSIT); path = path.getNext(); orders = path.getOrders(); + assertEquals(10125, path.getBalance(), 0.0001); assertEquals(650, path.getProfit(), 0.0001); TestUtil.assertCollectionEquals(orders, order5, order4, PathRoute.TRANSIT); path = path.getNext(); orders = path.getOrders(); + assertEquals(10500, path.getBalance(), 0.0001); assertEquals(300, path.getProfit(), 0.0001); TestUtil.assertCollectionEquals(orders, order7, PathRoute.TRANSIT); } @@ -177,7 +183,7 @@ public class PathRouteTest extends Assert { res = (PathRoute) res.connectTo(new Vertex<>(v4), false); res = (PathRoute) res.connectTo(new Vertex<>(v5), false); res.finish(); - res.resort(10000, 5); + res.sort(10000, 5); return (PathRoute) res.getRoot(); } @@ -195,25 +201,91 @@ public class PathRouteTest extends Assert { Order order6 = new Order(v4.getSell(ITEM1), v5.getBuy(ITEM1), 5); + assertEquals(10000, path.getBalance(), 0.0001); assertEquals(1000, path.getProfit(), 0.0001); TestUtil.assertCollectionEquals(orders, order3, order1, order4, PathRoute.TRANSIT, order2); path = path.getNext(); orders = path.getOrders(); + assertEquals(10300, path.getBalance(), 0.0001); assertEquals(650, path.getProfit(), 0.0001); TestUtil.assertCollectionEquals(orders, order5, PathRoute.TRANSIT); path = path.getNext(); orders = path.getOrders(); + assertEquals(10500, path.getBalance(), 0.0001); assertEquals(500, path.getProfit(), 0.0001); TestUtil.assertCollectionEquals(orders, PathRoute.TRANSIT); path = path.getNext(); orders = path.getOrders(); + assertEquals(10500, path.getBalance(), 0.0001); assertEquals(500, path.getProfit(), 0.0001); TestUtil.assertCollectionEquals(orders, order6, PathRoute.TRANSIT); } + + private PathRoute initTest5(){ + LOG.info("Init test 5"); + v1 = new SimpleVendor("v1"); + v2 = new SimpleVendor("v2"); + v3 = new SimpleVendor("v3"); + v4 = new SimpleVendor("v4"); + + v1.add(new Offer(OFFER_TYPE.SELL, ITEM1, 100)); + v1.add(new Offer(OFFER_TYPE.SELL, ITEM2, 200)); + v1.add(new Offer(OFFER_TYPE.SELL, ITEM3, 300)); + v2.add(new Offer(OFFER_TYPE.SELL, ITEM1, 150)); + v2.add(new Offer(OFFER_TYPE.SELL, ITEM3, 320)); + v3.add(new Offer(OFFER_TYPE.SELL, ITEM3, 390)); + + v2.add(new Offer(OFFER_TYPE.BUY, ITEM2, 225)); + v3.add(new Offer(OFFER_TYPE.BUY, ITEM1, 200)); + v4.add(new Offer(OFFER_TYPE.BUY, ITEM3, 450)); + + PathRoute res = new PathRoute(new Vertex<>(v1)); + res = (PathRoute) res.connectTo(new Vertex<>(v2), false); + res = (PathRoute) res.connectTo(new Vertex<>(v3), false); + res = (PathRoute) res.connectTo(new Vertex<>(v4), false); + res.finish(); + res.sort(500, 5); + return (PathRoute) res.getRoot(); + } + + @Test + public void testPathRoute5() throws Exception { + LOG.info("Start path route test 5"); + PathRoute path = initTest5().getNext(); + Collection orders = path.getOrders(); + + Order order1 = new Order(v1.getSell(ITEM1), v3.getBuy(ITEM1), 5); + Order order2 = new Order(v1.getSell(ITEM2), v2.getBuy(ITEM2), 2); + Order order3 = new Order(v1.getSell(ITEM3), v4.getBuy(ITEM3), 1); + Order order4 = new Order(v2.getSell(ITEM1), v3.getBuy(ITEM1), 3); + Order order5 = new Order(v2.getSell(ITEM3), v4.getBuy(ITEM3), 1); + Order order7 = new Order(v3.getSell(ITEM3), v4.getBuy(ITEM3), 2); + + assertEquals(500, path.getBalance(), 0.0001); + assertEquals(620, path.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(orders, order1, order2, PathRoute.TRANSIT, order3); + + path = path.getNext(); + orders = path.getOrders(); + + assertEquals(550, path.getBalance(), 0.0001); + assertEquals(270, path.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(orders, order4, order5, PathRoute.TRANSIT); + + path = path.getNext(); + orders = path.getOrders(); + + assertEquals(1000, path.getBalance(), 0.0001); + assertEquals(120, path.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(orders, order7, PathRoute.TRANSIT); + + } + + } diff --git a/core/src/test/resources/log4j.properties b/core/src/test/resources/log4j.properties index c9817f0..fe6a143 100644 --- a/core/src/test/resources/log4j.properties +++ b/core/src/test/resources/log4j.properties @@ -4,3 +4,4 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%p: %d{dd.MM.yyyy HH:mm:ss} (%F:%L) - %m%n +