From 7d15c8461bf3936c850d58348daf9d29241a65c8 Mon Sep 17 00:00:00 2001 From: iMoHax Date: Sun, 17 Aug 2014 14:01:33 +0400 Subject: [PATCH] PathRoute implement --- core/src/main/java/ru/trader/core/Order.java | 11 +- core/src/main/java/ru/trader/graph/Graph.java | 12 +- core/src/main/java/ru/trader/graph/Path.java | 24 +- .../main/java/ru/trader/graph/PathRoute.java | 114 +++++++-- .../java/ru/trader/graph/PathRouteTest.java | 218 ++++++++++++++++++ 5 files changed, 338 insertions(+), 41 deletions(-) create mode 100644 core/src/test/java/ru/trader/graph/PathRouteTest.java diff --git a/core/src/main/java/ru/trader/core/Order.java b/core/src/main/java/ru/trader/core/Order.java index d91054f..9855b69 100644 --- a/core/src/main/java/ru/trader/core/Order.java +++ b/core/src/main/java/ru/trader/core/Order.java @@ -40,10 +40,6 @@ public class Order implements Comparable { return profit; } - public Order getCopy(long count){ - return new Order(sell, buy, count); - } - public long getCount() { return count; } @@ -56,6 +52,10 @@ public class Order implements Comparable { return buy.getVendor().equals(buyer); } + public Vendor getBuyer(){ + return buy.getVendor(); + } + @Override public int compareTo(@NotNull Order order) { Objects.requireNonNull(order, "Not compare with null"); @@ -100,4 +100,7 @@ public class Order implements Comparable { return sb.toString(); } + public void setMax(double balance, long limit) { + setCount((long) Math.min(balance/sell.getPrice(), limit)); + } } diff --git a/core/src/main/java/ru/trader/graph/Graph.java b/core/src/main/java/ru/trader/graph/Graph.java index d9904df..f4b148a 100644 --- a/core/src/main/java/ru/trader/graph/Graph.java +++ b/core/src/main/java/ru/trader/graph/Graph.java @@ -113,7 +113,7 @@ public class Graph> { if (edge != null ){ if (!(withRefill && Math.min(limit, maxDistance) < edge.getLength() && !source.getEntry().canRefill())){ found = true; - Path path = head.connectTo(edge, limit < edge.getLength()); + Path path = head.connectTo(edge.getTarget(), limit < edge.getLength()); path.finish(); LOG.trace("Last edge find, add path {}", path); paths.add(path); @@ -124,10 +124,10 @@ public class Graph> { LOG.trace("Search around"); for (Edge next : source.getEdges()) { if (withRefill && Math.min(limit, maxDistance) < next.getLength() && !source.getEntry().canRefill()) continue; - if (head.contains(next)) continue; + if (head.isConnect(next.getTarget())) continue; // target already added if source consist edge if (next.isConnect(target)) continue; - Path path = head.connectTo(next, limit < next.getLength()); + Path path = head.connectTo(next.getTarget(), limit < next.getLength()); double nextLimit = withRefill ? limit - next.getLength(): stock; // refill if (nextLimit < 0 ) nextLimit = maxDistance - next.getLength(); @@ -151,9 +151,9 @@ public class Graph> { if (deep == source.getLevel()){ for (Edge next : source.getEdges()) { if (withRefill && Math.min(limit, maxDistance) < next.getLength() && !source.getEntry().canRefill()) continue; - if (head.contains(next)) continue; + if (head.isConnect(next.getTarget())) continue; if (next.isConnect(target)) { - Path path = head.connectTo(next, limit < next.getLength()); + Path path = head.connectTo(next.getTarget(), limit < next.getLength()); path.finish(); LOG.trace("Last edge find, path {}", path); return path; @@ -165,7 +165,7 @@ public class Graph> { for (Edge next : source.getEdges()) { if (next.getTarget().getLevel() >= source.getLevel()) continue; if (withRefill && Math.min(limit, maxDistance) < next.getLength() && !source.getEntry().canRefill()) continue; - Path path = head.connectTo(next, limit < next.getLength()); + Path path = head.connectTo(next.getTarget(), limit < next.getLength()); double nextLimit = withRefill ? limit - next.getLength(): stock; // refill if (nextLimit < 0 ) nextLimit = stock - next.getLength(); diff --git a/core/src/main/java/ru/trader/graph/Path.java b/core/src/main/java/ru/trader/graph/Path.java index 10413a1..4ba923e 100644 --- a/core/src/main/java/ru/trader/graph/Path.java +++ b/core/src/main/java/ru/trader/graph/Path.java @@ -18,8 +18,8 @@ public class Path> { this.refill = refill; } - public Path connectTo(Edge edge, boolean refill){ - return new Path<>(this, edge.getTarget(), refill); + public Path connectTo(Vertex vertex, boolean refill){ + return new Path<>(this, vertex, refill); } public void finish(){ @@ -37,6 +37,11 @@ public class Path> { return head == null; } + public Path getRoot(){ + if (isRoot()) return this; + return head.getRoot(); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -63,18 +68,21 @@ public class Path> { return sb.toString(); } - public boolean contains(Edge edge) { - return target.equals(edge.getTarget()) || (!isRoot() && head.contains(edge)); + public boolean isConnect(Vertex vertex) { + return target.equals(vertex) || (!isRoot() && head.isConnect(vertex)); + } + + public boolean isPathFrom(T entry) { + return !isRoot() && head.target.getEntry().equals(entry); } @SafeVarargs public static > Path toPath(T... items){ - T s = items[0]; - Path path = new Path<>(new Vertex<>(s)); + T t = items[0]; + Path path = new Path<>(new Vertex<>(t)); for (int i = 1; i < items.length; i++) { - T t = items[i]; + t = items[i]; path = new Path<>(path, new Vertex<>(t), false); - s = t; } return path; } diff --git a/core/src/main/java/ru/trader/graph/PathRoute.java b/core/src/main/java/ru/trader/graph/PathRoute.java index f3eaf70..c39be2a 100644 --- a/core/src/main/java/ru/trader/graph/PathRoute.java +++ b/core/src/main/java/ru/trader/graph/PathRoute.java @@ -4,25 +4,28 @@ import ru.trader.core.Offer; import ru.trader.core.Order; import ru.trader.core.Vendor; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedList; +import java.util.*; public class PathRoute extends Path { private final ArrayList orders = new ArrayList<>(); + private double profit = 0; + private int transitIndex = 0; + private PathRoute tail; + public PathRoute(Vertex source) { super(source); } - protected PathRoute(Path head, Vertex vertex, boolean refill) { + private PathRoute(PathRoute head, Vertex vertex, boolean refill) { super(head, vertex, refill); + assert head.tail == null; + head.tail = this; } @Override - public Path connectTo(Edge edge, boolean refill) { - return new PathRoute(this.getCopy(), edge.getTarget(), refill); + public Path connectTo(Vertex vertex, boolean refill) { + return new PathRoute(this.getCopy(), vertex, refill); } @Override @@ -51,8 +54,8 @@ public class PathRoute extends Path { } } - public Collection getOrders() { - return orders; + public List getOrders() { + return Collections.unmodifiableList(orders); } @Override @@ -61,7 +64,7 @@ public class PathRoute extends Path { for (Order order : orders) { if (sb.length() > 0) sb.append(", "); sb.append(order.getBuy().getItem()); - sb.append(" (").append(order.getBuy().getVendor()).append(") "); + sb.append(" (").append(order.getBuyer()).append(") "); } String o = sb.toString(); sb = new StringBuilder(); @@ -77,20 +80,85 @@ public class PathRoute extends Path { return sb.toString(); } - public Path getCopy(){ - Path res; - LinkedList> v = new LinkedList<>(); - Path p = this; - while (!p.isRoot()){ - v.add(p); - p = p.getHead(); - } - res = p; - Iterator> it = v.descendingIterator(); - while (it.hasNext()){ - p = it.next(); - res = new PathRoute(res, p.getTarget(), p.isRefill()); + public boolean isEmpty(){ + return orders.isEmpty(); + } + + public PathRoute getCopy(){ + PathRoute path = (PathRoute) getRoot(); + PathRoute res = new PathRoute(path.getTarget()); + while (path.tail != null){ + res = new PathRoute(res, path.tail.getTarget(), path.tail.isRefill()); + path = path.tail; } return res; } + + public double getProfit(){ + return profit; + } + + public double getProfit(Order order){ + if (isPathFrom(order.getBuyer())) return order.getProfit() + profit; + return tail != null ? tail.getProfit(order) : order.getProfit(); + } + + @Override + protected PathRoute getHead() { + return (PathRoute) super.getHead(); + } + + public void resort(double balance, long limit){ + if (isRoot()) return; + for (Order order : orders) { + order.setMax(balance, limit); + } + orders.sort(this::compareOrders); + + updateProfit(); + updateTransitIndex(); + getHead().resort(balance, limit); + } + + private void updateTransitIndex() { + transitIndex = orders.size(); + if (isEmpty()) return; + double transitProfit = tail != null ? tail.getProfit() : 0; + ListIterator itr = orders.listIterator(orders.size()); + while (itr.hasPrevious()){ + Order o = itr.previous(); + if (getProfit(o) > transitProfit){ + return; + } + transitIndex--; + } + } + + private void updateProfit() { + if (isEmpty()){ + profit = tail != null ? tail.getProfit() : 0; + } else { + Order best = orders.get(0); + profit = getProfit(best); + } + } + + private int compareOrders(Order o1, Order o2){ + if (tail == null || o1.isBuyer(o2.getBuyer())){ + //reverse + return o2.compareTo(o1); + } + double profit1 = getProfit(o1); + double profit2 = getProfit(o2); + return Double.compare(profit2, profit1); + } + + + public PathRoute getTail() { + return tail; + } + + public int getTransitIndex() { + return transitIndex; + } } diff --git a/core/src/test/java/ru/trader/graph/PathRouteTest.java b/core/src/test/java/ru/trader/graph/PathRouteTest.java new file mode 100644 index 0000000..a953752 --- /dev/null +++ b/core/src/test/java/ru/trader/graph/PathRouteTest.java @@ -0,0 +1,218 @@ +package ru.trader.graph; + +import org.junit.Assert; +import org.junit.Test; +import ru.trader.TestUtil; +import ru.trader.core.*; + +import java.util.Collection; + +public class PathRouteTest extends Assert { + + private final static Item ITEM1 = new Item("ITEM1"); + private final static Item ITEM2 = new Item("ITEM2"); + private final static Item ITEM3 = new Item("ITEM3"); + private static Vendor v1; + private static Vendor v2; + private static Vendor v3; + private static Vendor v4; + private static Vendor v5; + + private PathRoute initTest1(){ + v1 = new SimpleVendor("v1"); + v2 = new SimpleVendor("v2"); + + 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.BUY, ITEM1, 300)); + v2.add(new Offer(OFFER_TYPE.BUY, ITEM2, 350)); + v2.add(new Offer(OFFER_TYPE.BUY, ITEM3, 400)); + + PathRoute res = new PathRoute(new Vertex<>(v1)); + res = (PathRoute) res.connectTo(new Vertex<>(v2), false); + res.finish(); + res.resort(10000, 5); + return (PathRoute) res.getRoot(); + } + + + @Test + public void testPathRoute1() throws Exception { + PathRoute path = initTest1().getTail(); + Collection orders = path.getOrders(); + + Order order1 = new Order(v1.getSell(ITEM1), v2.getBuy(ITEM1), 5); + Order order2 = new Order(v1.getSell(ITEM2), v2.getBuy(ITEM2), 5); + Order order3 = new Order(v1.getSell(ITEM3), v2.getBuy(ITEM3), 5); + + assertEquals(1000, path.getProfit(), 0.0001); + assertEquals(3, path.getTransitIndex()); + TestUtil.assertCollectionEquals(orders, order1, order2, order3); + } + + private PathRoute initTest2(){ + v1 = new SimpleVendor("v1"); + v2 = new SimpleVendor("v2"); + v3 = new SimpleVendor("v3"); + + v1.add(new Offer(OFFER_TYPE.SELL, ITEM1, 100)); + v1.add(new Offer(OFFER_TYPE.SELL, ITEM3, 300)); + v2.add(new Offer(OFFER_TYPE.SELL, ITEM2, 200)); + v3.add(new Offer(OFFER_TYPE.BUY, ITEM1, 300)); + v3.add(new Offer(OFFER_TYPE.BUY, ITEM2, 350)); + v3.add(new Offer(OFFER_TYPE.BUY, ITEM3, 400)); + + PathRoute res = new PathRoute(new Vertex<>(v1)); + res = (PathRoute) res.connectTo(new Vertex<>(v2), false); + res = (PathRoute) res.connectTo(new Vertex<>(v3), false); + res.finish(); + res.resort(10000, 5); + return (PathRoute) res.getRoot(); + } + + @Test + public void testPathRoute2() throws Exception { + PathRoute path = initTest2().getTail(); + Collection orders = path.getOrders(); + + Order order1 = new Order(v1.getSell(ITEM1), v3.getBuy(ITEM1), 5); + Order order2 = new Order(v2.getSell(ITEM2), v3.getBuy(ITEM2), 5); + Order order3 = new Order(v1.getSell(ITEM3), v3.getBuy(ITEM3), 5); + + assertEquals(1000, path.getProfit(), 0.0001); + assertEquals(1, path.getTransitIndex()); + TestUtil.assertCollectionEquals(orders, order1, order3); + + path = path.getTail(); + orders = path.getOrders(); + + assertEquals(750, path.getProfit(), 0.0001); + assertEquals(1, path.getTransitIndex()); + TestUtil.assertCollectionEquals(orders, order2); + } + + private PathRoute initTest3(){ + 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, 230)); + 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.resort(10000, 5); + return (PathRoute) res.getRoot(); + } + + @Test + public void testPathRoute3() throws Exception { + PathRoute path = initTest3().getTail(); + 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), 5); + Order order3 = new Order(v1.getSell(ITEM3), v4.getBuy(ITEM3), 5); + Order order4 = new Order(v2.getSell(ITEM1), v3.getBuy(ITEM1), 5); + Order order5 = new Order(v2.getSell(ITEM3), v4.getBuy(ITEM3), 5); + Order order7 = new Order(v3.getSell(ITEM3), v4.getBuy(ITEM3), 5); + + assertEquals(800, path.getProfit(), 0.0001); + assertEquals(3, path.getTransitIndex()); + TestUtil.assertCollectionEquals(orders, order2, order1, order3); + + path = path.getTail(); + orders = path.getOrders(); + + assertEquals(650, path.getProfit(), 0.0001); + assertEquals(2, path.getTransitIndex()); + TestUtil.assertCollectionEquals(orders, order5, order4); + + path = path.getTail(); + orders = path.getOrders(); + + assertEquals(300, path.getProfit(), 0.0001); + assertEquals(1, path.getTransitIndex()); + TestUtil.assertCollectionEquals(orders, order7); + } + + private PathRoute initTest4(){ + v1 = new SimpleVendor("v1"); + v2 = new SimpleVendor("v2"); + v3 = new SimpleVendor("v3"); + v4 = new SimpleVendor("v4"); + v5 = new SimpleVendor("v5"); + + v1.add(new Offer(OFFER_TYPE.SELL, ITEM1, 410)); + 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, ITEM2, 270)); + v4.add(new Offer(OFFER_TYPE.SELL, ITEM1, 300)); + + v2.add(new Offer(OFFER_TYPE.BUY, ITEM1, 470)); + v3.add(new Offer(OFFER_TYPE.BUY, ITEM2, 300)); + v4.add(new Offer(OFFER_TYPE.BUY, ITEM3, 370)); + v5.add(new Offer(OFFER_TYPE.BUY, ITEM1, 400)); + + 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 = (PathRoute) res.connectTo(new Vertex<>(v5), false); + res.finish(); + res.resort(10000, 5); + return (PathRoute) res.getRoot(); + } + + @Test + public void testPathRoute4() throws Exception { + PathRoute path = initTest4().getTail(); + Collection orders = path.getOrders(); + + Order order1 = new Order(v1.getSell(ITEM1), v2.getBuy(ITEM1), 5); + Order order2 = new Order(v1.getSell(ITEM1), v5.getBuy(ITEM1), 5); + Order order3 = new Order(v1.getSell(ITEM2), v3.getBuy(ITEM2), 5); + Order order4 = new Order(v1.getSell(ITEM3), v4.getBuy(ITEM3), 5); + Order order5 = new Order(v2.getSell(ITEM2), v3.getBuy(ITEM2), 5); + Order order6 = new Order(v4.getSell(ITEM1), v5.getBuy(ITEM1), 5); + + + assertEquals(1000, path.getProfit(), 0.0001); + assertEquals(3, path.getTransitIndex()); + TestUtil.assertCollectionEquals(orders, order3, order1, order4, order2); + + path = path.getTail(); + orders = path.getOrders(); + + assertEquals(650, path.getProfit(), 0.0001); + assertEquals(1, path.getTransitIndex()); + TestUtil.assertCollectionEquals(orders, order5); + + path = path.getTail(); + orders = path.getOrders(); + + assertEquals(500, path.getProfit(), 0.0001); + assertEquals(0, path.getTransitIndex()); + assertTrue(orders.isEmpty()); + + path = path.getTail(); + orders = path.getOrders(); + + assertEquals(500, path.getProfit(), 0.0001); + assertEquals(1, path.getTransitIndex()); + TestUtil.assertCollectionEquals(orders, order6); + } +}