diff --git a/core/src/main/java/ru/trader/analysis/MarketUtils.java b/core/src/main/java/ru/trader/analysis/MarketUtils.java new file mode 100644 index 0000000..44825b0 --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/MarketUtils.java @@ -0,0 +1,57 @@ +package ru.trader.analysis; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.core.Offer; +import ru.trader.core.Order; +import ru.trader.core.Vendor; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class MarketUtils { + private final static Logger LOG = LoggerFactory.getLogger(MarketUtils.class); + + public static List getStack(List orders, double balance, long cargo){ + LOG.trace("Fill stack orders {}, balance {}", orders, balance); + orders.forEach(o -> o.setMax(balance, cargo)); + LOG.trace("Simple sort"); + orders.sort(Comparator.reverseOrder()); + LOG.trace("New order of orders {}", orders); + List stack = new ArrayList<>(); + long count = cargo; + double remain = balance; + for (Order order : orders) { + order = new Order(order.getSell(), order.getBuy(), remain, count); + LOG.trace("Next best order {}", order); + if (order.getProfit() > 0) { + stack.add(order); + remain -= order.getCount() * order.getSell().getPrice(); + count -= order.getCount(); + LOG.trace("Remain cargo {}, remain balance {}", count, remain); + } else { + LOG.trace("Low profit, stopped"); + remain = 0; + } + if (count <= 0 || remain <= 0) { + break; + } + } + LOG.trace("Stack: {}", stack); + return stack; + } + + public static List getOrders(Vendor seller, Vendor buyer){ + LOG.trace("Get orders from {}, to {}", seller, buyer); + List orders = new ArrayList<>(); + for (Offer sell : seller.getAllSellOffers()) { + Offer buy = buyer.getBuy(sell.getItem()); + if (buy != null) { + Order order = new Order(sell, buy, 1); + orders.add(order); + } + } + return orders; + } +} diff --git a/core/src/main/java/ru/trader/analysis/Route.java b/core/src/main/java/ru/trader/analysis/Route.java new file mode 100644 index 0000000..306f688 --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/Route.java @@ -0,0 +1,120 @@ +package ru.trader.analysis; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.core.Vendor; + +import java.util.*; + +public class Route { + private final static Logger LOG = LoggerFactory.getLogger(Route.class); + + private final List entries; + private double profit = 0; + private double balance = 0; + private double distance = 0; + private int lands = 0; + + public Route(RouteEntry root) { + entries = new ArrayList<>(); + entries.add(root); + } + + public Route(List edges) { + //TODO: move to RouteCrawler + entries = new ArrayList<>(edges.size()+1); + for (int i = 0; i < edges.size(); i++) { + VendorsGraph.VendorsEdge edge = edges.get(i); + if (i==0) entries.add(new RouteEntry(edge.getSource().getEntry(), false, 0)); + entries.add(new RouteEntry(edge.getTarget().getEntry(), edge.isRefill(), edge.getFuel())); + } + } + + public List getEntries() { + return entries; + } + + void setBalance(double balance){ + this.balance = balance; + } + + public double getBalance() { + return balance; + } + + public double getProfit() { + return profit; + } + + public double getDistance() { + return distance; + } + + public int getLands() { + return lands; + } + + public void add(RouteEntry entry){ + LOG.trace("Add entry {} to route {}", entry, this); + entries.add(entry); + updateStats(); + } + + public void addAll(Collection entries){ + LOG.trace("Add {} entries {} to route {}", entries, this); + entries.addAll(entries); + updateStats(); + } + + public Collection getVendors() { + return getVendors(0); + } + + public Collection getVendors(int index){ + if (index < 0 || index >= entries.size()) return Collections.emptyList(); + Collection vendors = new HashSet<>(); + for (int i = index; i < entries.size(); i++) { + RouteEntry entry = entries.get(i); + vendors.add(entry.getVendor()); + } + return vendors; + } + + public boolean contains(Collection vendors){ + return vendors.isEmpty() + || vendors.size() <= entries.size() + && vendors.stream().allMatch(v -> entries.stream().anyMatch(e -> v.equals(e.getVendor()))); + } + + public void join(Route route){ + LOG.trace("Join route {}", route); + RouteEntry end = entries.get(entries.size()-1); + if (route.entries.get(0).is(end.getVendor())){ + entries.remove(entries.size()-1); + } else { + LOG.trace("Is not connected route, set refill"); + end.setRefill(true); + } + entries.addAll(route.entries); + updateStats(); + } + + void updateStats(){ + LOG.trace("Update stats, old: profit={}, distance={}, lands={}", profit, distance, lands); + profit = 0; distance = 0; lands = 0; + if (entries.isEmpty()) return; + RouteEntry entry = entries.get(0); + for (int i = 1; i < entries.size(); i++) { + RouteEntry next = entries.get(i); + distance += entry.getVendor().getDistance(next.getVendor()); + profit += entry.getProfit(); + if (entry.isLand()){ + lands++; + } + entry = next; + } + LOG.trace("new stats profit={}, distance={}, lands={}", profit, distance, lands); + } + + +} diff --git a/core/src/main/java/ru/trader/analysis/RouteEntry.java b/core/src/main/java/ru/trader/analysis/RouteEntry.java new file mode 100644 index 0000000..2abad47 --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/RouteEntry.java @@ -0,0 +1,77 @@ +package ru.trader.analysis; + +import ru.trader.core.Order; +import ru.trader.core.Vendor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class RouteEntry { + private final Vendor vendor; + private final double fuel; + private final List orders; + private boolean land; + private boolean refill; + + + public RouteEntry(Vendor vendor, boolean refill, double fuel) { + orders = new ArrayList<>(); + this.vendor = vendor; + this.refill = refill; + this.fuel = fuel; + } + + public Vendor getVendor() { + return vendor; + } + + public boolean is(Vendor vendor){ + return vendor.equals(this.vendor); + } + + public boolean isRefill() { + return refill; + } + + void setRefill(boolean refill) { + this.refill = refill; + } + + public double getFuel() { + return fuel; + } + + public void add(Order order){ + orders.add(order); + } + + public void addAll(Collection orders){ + this.orders.addAll(orders); + } + + public List getOrders() { + return orders; + } + + public void clearOrders(){ + orders.clear(); + } + + public double getProfit(){ + return orders.stream().mapToDouble(Order::getProfit).sum(); + } + + public boolean isLand(){ + return land || refill || !orders.isEmpty(); + } + + public void setLand(boolean land) { + this.land = land; + } + + public boolean isTransit(){ + return !isLand(); + } + +} diff --git a/core/src/main/java/ru/trader/analysis/RouteFiller.java b/core/src/main/java/ru/trader/analysis/RouteFiller.java new file mode 100644 index 0000000..392b840 --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/RouteFiller.java @@ -0,0 +1,353 @@ +package ru.trader.analysis; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.core.Offer; +import ru.trader.core.Order; +import ru.trader.core.Vendor; + +import java.util.*; + +public class RouteFiller { + private final static Logger LOG = LoggerFactory.getLogger(Route.class); + private final static OrderStack TRANSIT = null; + + private final double balance; + private final long cargo; + private final Scorer scorer; + private Route route; + private List oList; + + public RouteFiller(Scorer scorer) { + this.scorer = scorer; + this.balance = scorer.getProfile().getBalance(); + this.cargo = scorer.getProfile().getShip().getCargo(); + } + + public void fill(Route route){ + this.route = route; + route.setBalance(balance); + fillOrders(); + updateEntries(); + route.updateStats(); + } + + private void fillOrders(){ + fillOrdersList(); + sort(); + } + + private void fillOrdersList(){ + List entries = route.getEntries(); + oList = new ArrayList<>(entries.size()); + for (int i = 0; i < entries.size(); i++) { + RouteEntry rEntry = entries.get(i); + OEntry entry = new OEntry(rEntry.getVendor(), rEntry.isRefill(), rEntry.getFuel()); + oList.add(entry); + Vendor seller = entries.get(i).getVendor(); + LOG.trace("Fill orders for {}", seller); + final int nextIndex = i+1; + Collection vendors = route.getVendors(nextIndex); + for (Offer sell : seller.getAllSellOffers()) { + for (Vendor buyer : vendors) { + Offer buy = buyer.getBuy(sell.getItem()); + if (buy != null) { + Order order = new Order(sell, buy, 1); + if (order.getProfit() <= 0) { + LOG.trace("{} - is no profit, skip", order); + } else { + entry.add(order); + } + } + } + } + } + } + + private void sort() { + LOG.trace("Start forward sort"); + for (int i = 0; i < oList.size(); i++) { + OEntry entry = oList.get(i); + entry.setBalance(getBalance(entry.vendor, i - 1)); + entry.fillToMax(i+1); + } + LOG.trace("Start backward sort"); + for (int i = oList.size() - 1; i >= 0; i--) { + OEntry entry = oList.get(i); + OrderStack best = entry.getBest(); + final int nextIndex = i+1; + entry.setLands(getLands(best, nextIndex) + (best != TRANSIT || entry.refill ? 1 : 0)); + BackwardComparator comparator = new BackwardComparator(nextIndex); + entry.sort(comparator); + best = entry.getBest(); + entry.setFullScore(comparator.getScore(best)); + entry.setLands(getLands(best, nextIndex) + (best != TRANSIT || entry.refill ? 1 : 0)); + } + } + + private int getIndex(Vendor vendor, int startIndex){ + for (int i = startIndex; i < oList.size(); i++) { + if (oList.get(i).vendor.equals(vendor)) return i; + } + return -1; + } + + private double getBalance(Vendor vendor, int startIndex){ + double res = 0; + double score = 0; + double fuel = 0; + int refills = 0; + int jumps = 0; + for (int i = startIndex; i >= 0; i--) { + OEntry entry = oList.get(i); + fuel += entry.fuel; + if (entry.fuel > 0) jumps++; + OrderStack best = entry.getStack(vendor); + if (best == null) continue; + double profit = best.getProfit(); + if (res < profit){ + double newScore = scorer.getScore(entry.vendor, profit, jumps, refills, fuel); + if (newScore > score) { + score = newScore; + res = profit; + LOG.trace("Orders {} is best to {}, score {}, profit {}", best, vendor, score, res); + } + } + if (entry.refill){ + refills++; + } + } + LOG.trace("Max score on {} = {}, profit {}", vendor, score, res); + return balance + res; + } + + + + private double getScore(OrderStack order, int startIndex){ + double fuel = 0; + int refills = 0; + int jumps = 0; + Vendor buyer = order.getBuyer(); + for (int i = startIndex; i < oList.size(); i++) { + OEntry entry = oList.get(i); + fuel += entry.fuel; + if (entry.fuel > 0) jumps++; + if (entry.vendor.equals(buyer)){ + return scorer.getScore(order.getBuyer(), order.getProfit(), jumps, refills + entry.lands, fuel); + } + if (entry.refill){ + refills++; + } + } + return 0; + } + + + private double getFullScore(OrderStack order, int startIndex){ + if (startIndex >= oList.size()) return 0; + if (order == TRANSIT) return oList.get(startIndex).score; + int index = getIndex(order.getBuyer(), startIndex); + if (index > 0) { + return oList.get(index).score + getScore(order, index); + } + return 0; + } + + private int getLands(OrderStack order, int startIndex) { + if (startIndex >= oList.size()) return 1; + if (order == TRANSIT) return oList.get(startIndex).lands; + int index = getIndex(order.getBuyer(), startIndex); + if (index > 0) { + return oList.get(index).lands; + } + throw new IllegalStateException(String.format("Not found buyer for order %s", order)); + } + + private void updateEntries(){ + List entries = route.getEntries(); + OrderStack best = TRANSIT; + for (int i = 0; i < entries.size(); i++) { + RouteEntry entry = entries.get(i); + entry.clearOrders(); + entry.setLand(false); + OEntry o = oList.get(i); + if (best == TRANSIT || o.vendor.equals(best.getBuyer())){ + if (best != TRANSIT) + entry.setLand(true); + best = o.getBest(); + if (best != TRANSIT) + entry.addAll(best.bestOrders); + } + } + } + + private class ForwardComparator implements Comparator { + private final HashMap cache = new HashMap<>(10); + private final int index; + + private ForwardComparator(int index) { + this.index = index; + } + + @Override + public int compare(OrderStack o1, OrderStack o2) { + if (o1 != TRANSIT && o2 != TRANSIT){ + double score1 = getScore(o1); + double score2 = getScore(o2); + return Double.compare(score2, score1); + } + return o1 == TRANSIT ? o2 == TRANSIT ? 0 : Double.compare(o2.getProfit(), 0) : Double.compare(0, o1.getProfit()); + } + + private double getScore(OrderStack stack){ + Double score = cache.get(stack); + if (score == null){ + score = RouteFiller.this.getScore(stack, index); + cache.put(stack, score); + } + return score; + } + } + + + private class BackwardComparator implements Comparator { + private final HashMap cache = new HashMap<>(10); + private final int index; + + private BackwardComparator(int index) { + this.index = index; + } + + @Override + public int compare(OrderStack o1, OrderStack o2) { + double score1 = getScore(o1); + double score2 = getScore(o2); + return Double.compare(score2, score1); + } + + public double getScore(OrderStack stack){ + Double score = cache.get(stack); + if (score == null){ + score = RouteFiller.this.getFullScore(stack, index); + cache.put(stack, score); + } + return score; + } + } + + private class OEntry { + private final List orders; + + private final Vendor vendor; + private final boolean refill; + private final double fuel; + private double balance; + private double score; + private int lands; + + + private OEntry(Vendor vendor, boolean refill, double fuel){ + this.vendor = vendor; + orders = new ArrayList<>(); + orders.add(TRANSIT); + this.refill = refill; + this.fuel = fuel; + } + + public void add(Order order){ + LOG.trace("Add order {}", order); + OrderStack stack = getStack(order.getBuyer()); + if (stack == null){ + stack = new OrderStack(); + orders.add(stack); + } + stack.add(order); + } + + public OrderStack getStack(Vendor vendor){ + OrderStack stack = null; + for (OrderStack s : orders) { + if (s == TRANSIT) continue; + if (s.is(vendor)){ + stack = s; + break; + } + } + return stack; + } + + public void setBalance(double balance){ + this.balance = balance; + } + + public void fillToMax(int nextIndex){ + orders.forEach(o -> { + if (o != TRANSIT) + o.fillBest(balance); + }); + sort(new ForwardComparator(nextIndex)); + } + + public double getProfit(){ + OrderStack best = orders.get(0); + return best.getProfit(); + } + + public void setFullScore(double score) { + LOG.trace("New full score {}", score); + this.score = score; + } + + public void setLands(int lands) { + LOG.trace("New lands count {}", lands); + this.lands = lands; + } + + public OrderStack getBest(){ + return orders.get(0); + } + + public void sort(Comparator comparator) { + LOG.trace("Sort"); + orders.sort(comparator); + LOG.trace("New order of orders {}", orders); + } + } + + private class OrderStack { + private final List orders; + private final List bestOrders; + + private OrderStack() { + orders = new ArrayList<>(); + bestOrders = new ArrayList<>(); + } + + public void add(Order order){ + orders.add(order); + } + + public Vendor getBuyer(){ + return orders.isEmpty() ? null : orders.get(0).getBuyer(); + } + + public boolean is(Vendor buyer){ + return orders.isEmpty() || orders.get(0).isBuyer(buyer); + } + + public void fillBest(double balance){ + bestOrders.clear(); + bestOrders.addAll(MarketUtils.getStack(orders, balance, cargo)); + } + + public double getProfit(){ + return bestOrders.stream().mapToDouble(Order::getProfit).sum(); + } + + + @Override + public String toString() { + return "{" + bestOrders + "}"; + } + } +} diff --git a/core/src/main/java/ru/trader/analysis/Scorer.java b/core/src/main/java/ru/trader/analysis/Scorer.java index 336b717..7fddca3 100644 --- a/core/src/main/java/ru/trader/analysis/Scorer.java +++ b/core/src/main/java/ru/trader/analysis/Scorer.java @@ -1,6 +1,8 @@ package ru.trader.analysis; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ru.trader.core.*; import java.util.*; @@ -8,25 +10,31 @@ import java.util.stream.Collectors; import java.util.stream.Stream; public class Scorer { + private final static Logger LOG = LoggerFactory.getLogger(Scorer.class); + private final Map sellOffers; private final Map buyOffers; private final FilteredMarket market; private final Profile profile; private final double avgProfit; + private final double maxScore; private final double avgDistance; - private int ordersCount = 5; - private double distanceRate = 1; - public Scorer(FilteredMarket market, Profile profile) { this.market = market; this.profile = profile; sellOffers = new HashMap<>(100, 0.9f); buyOffers = new HashMap<>(100, 0.9f); market.getItems().parallelStream().forEach(this::fillOffers); - avgProfit = computeAvgProfit(); + DoubleSummaryStatistics statProfit = computeProfit(); + avgProfit = statProfit.getAverage()/profile.getShip().getCargo(); avgDistance = computeAvgDistance(); + maxScore = getScore(0, statProfit.getMax()*2, 0,0,0); + } + + public Profile getProfile() { + return profile; } private void fillOffers(Item item){ @@ -40,12 +48,10 @@ public class Scorer { } } - private double computeAvgProfit(){ - OptionalDouble avg = sellOffers.values().stream() - .flatMap(this::mapToOrder) - .mapToDouble(o -> o.getProfit() / profile.getShip().getCargo()) - .average(); - return avg.orElse(0); + private DoubleSummaryStatistics computeProfit(){ + return sellOffers.values().stream() + .flatMap(this::mapToOrder) + .collect(Collectors.summarizingDouble(Order::getProfit)); } private double computeAvgDistance(){ @@ -53,18 +59,35 @@ public class Scorer { return res.orElse(0); } - public void setOrdersCount(int ordersCount) { - this.ordersCount = ordersCount; - } - - public void setDistanceRate(double distanceRate) { - this.distanceRate = distanceRate; - } - public double getAvgProfit() { return avgProfit; } + public double getMaxScore() { + return maxScore; + } + + public double getFuel(double distance){ + return profile.getShip().getFuelCost(distance); + } + + public double getScore(Vendor vendor, double profit, int jumps, int lands, double fuel) { + return getScore(vendor.getDistance(), profit, jumps, lands, fuel); + } + + public double getScore(double distance, double profit, int jumps, int lands, double fuel){ + LOG.trace("Compute score distance={}, profit={}, jumps={}, lands={}, fuel={}", distance, profit, jumps, lands, fuel); + double score = profit/profile.getShip().getCargo(); + if (avgDistance > 0) { + score -= profile.getDistanceMult() * getAvgProfit() * (distance - avgDistance) / avgDistance; + } + score -= profile.getLandMult() * lands * getAvgProfit(); + score -= profile.getFuelPrice() * fuel; + score -= profile.getJumpMult() * jumps * getAvgProfit(); + LOG.trace("score={}", score); + return score; + } + public Score getScore(Vendor vendor){ return new Score(vendor); } @@ -116,13 +139,13 @@ public class Scorer { private DoubleSummaryStatistics computeProfits(Stream orders) { return orders.sorted(Comparator.reverseOrder()) - .limit(ordersCount) - .collect(Collectors.summarizingDouble(o -> o.getProfit() / profile.getShip().getCargo())); + .limit(profile.getScoreOrdersCount()) + .collect(Collectors.summarizingDouble(Order::getProfit)); } private void computeScore(){ score = (getSellProfit() + getBuyProfit())/2; - score -= distanceRate * avgProfit * (vendor.getDistance() - avgDistance) / avgDistance; + score = Scorer.this.getScore(vendor, score, 0, 0, 0); } @Override diff --git a/core/src/main/java/ru/trader/core/AbstractOffer.java b/core/src/main/java/ru/trader/core/AbstractOffer.java index 2d6bca3..c092d93 100644 --- a/core/src/main/java/ru/trader/core/AbstractOffer.java +++ b/core/src/main/java/ru/trader/core/AbstractOffer.java @@ -41,5 +41,12 @@ public abstract class AbstractOffer implements Offer { } } - + @Override + public String toString() { + return "{" + getVendor() + + ","+ getType() + + "," + getCount()+ + "," + getItem() + + "," +getPrice()+"}"; + } } diff --git a/core/src/main/java/ru/trader/core/Ship.java b/core/src/main/java/ru/trader/core/Ship.java index e2247e8..74dcc6a 100644 --- a/core/src/main/java/ru/trader/core/Ship.java +++ b/core/src/main/java/ru/trader/core/Ship.java @@ -35,6 +35,10 @@ public class Ship { return engine; } + public void setEngine(int clazz, char rating) { + this.engine = new Engine(clazz, rating); + } + public void setEngine(Engine engine) { this.engine = engine; } diff --git a/core/src/test/java/ru/trader/analysis/RouteFillerTest.java b/core/src/test/java/ru/trader/analysis/RouteFillerTest.java new file mode 100644 index 0000000..d229384 --- /dev/null +++ b/core/src/test/java/ru/trader/analysis/RouteFillerTest.java @@ -0,0 +1,472 @@ +package ru.trader.analysis; + +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.TestUtil; +import ru.trader.core.*; +import ru.trader.store.simple.SimpleMarket; + +import java.util.List; + +public class RouteFillerTest extends Assert { + private final static Logger LOG = LoggerFactory.getLogger(RouteFillerTest.class); + + private Market market; + private Item ITEM1; + private Item ITEM2; + private Item ITEM3; + private Item ITEM4; + private Vendor v1; + private Vendor v2; + private Vendor v3; + private Vendor v4; + private Vendor v5; + + private RouteFiller getFillerInstance(double balance, int cargo, double landsMult, Market market){ + Ship ship = new Ship(); + ship.setCargo(cargo); + Profile profile = new Profile(ship); + profile.setBalance(balance); + profile.setLandMult(landsMult); + Scorer scorer = new Scorer(new FilteredMarket(market, new MarketFilter()), profile); + return new RouteFiller(scorer); + } + + private Route initTest1(){ + LOG.info("Init test 1"); + market = new SimpleMarket(); + ITEM1 = market.addItem("ITEM1", null); + ITEM2 = market.addItem("ITEM2", null); + ITEM3 = market.addItem("ITEM3", null); + v1 = market.addPlace("p1",0,0,0).addVendor("v1"); + v2 = market.addPlace("p2",0,0,0).addVendor("v2"); + v1.addOffer(OFFER_TYPE.SELL, ITEM1, 100, -1); + v1.addOffer(OFFER_TYPE.SELL, ITEM2, 200, -1); + v1.addOffer(OFFER_TYPE.SELL, ITEM3, 300, -1); + v2.addOffer(OFFER_TYPE.BUY, ITEM1, 300, -1); + v2.addOffer(OFFER_TYPE.BUY, ITEM2, 350, -1); + v2.addOffer(OFFER_TYPE.BUY, ITEM3, 400, -1); + + Route route = new Route(new RouteEntry(v1, false, 0)); + route.add(new RouteEntry(v2, false, 0)); + + return route; + } + + + @Test + public void testRoute1() throws Exception { + LOG.info("Start route test 1"); + Route route = initTest1(); + RouteFiller filler = getFillerInstance(10000, 5, 0, market); + filler.fill(route); + + assertEquals(10000, route.getBalance(), 0.0001); + assertEquals(1000, route.getProfit(), 0.0001); + assertEquals(1, route.getLands()); + + List entries = route.getEntries(); + + RouteEntry entry = entries.get(0); + Order order1 = new Order(v1.getSell(ITEM1), v2.getBuy(ITEM1), 5); + assertEquals(1000, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order1); + } + + private Route initTest2(){ + LOG.info("Init test 2"); + market = new SimpleMarket(); + ITEM1 = market.addItem("ITEM1", null); + ITEM2 = market.addItem("ITEM2", null); + ITEM3 = market.addItem("ITEM3", null); + v1 = market.addPlace("p1",0,0,0).addVendor("v1"); + v2 = market.addPlace("p2",0,0,0).addVendor("v2"); + v3 = market.addPlace("p3",0,0,0).addVendor("v3"); + v1.addOffer(OFFER_TYPE.SELL, ITEM1, 100, -1); + v1.addOffer(OFFER_TYPE.SELL, ITEM3, 300, -1); + v2.addOffer(OFFER_TYPE.SELL, ITEM2, 200, -1); + v3.addOffer(OFFER_TYPE.BUY, ITEM1, 300, -1); + v3.addOffer(OFFER_TYPE.BUY, ITEM2, 350, -1); + v3.addOffer(OFFER_TYPE.BUY, ITEM3, 400, -1); + + Route route = new Route(new RouteEntry(v1, false, 0)); + route.add(new RouteEntry(v2, false, 0)); + route.add(new RouteEntry(v3, false, 0)); + + return route; + } + + @Test + public void testPathRoute2() throws Exception { + LOG.info("Start route test 2"); + Route route = initTest2(); + RouteFiller filler = getFillerInstance(10000, 5, 0, market); + filler.fill(route); + + assertEquals(1000, route.getProfit(), 0.0001); + assertEquals(1, route.getLands()); + + List entries = route.getEntries(); + Order order1 = new Order(v1.getSell(ITEM1), v3.getBuy(ITEM1), 5); + + RouteEntry entry = entries.get(0); + assertEquals(1000, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order1); + + entry = entries.get(1); + assertEquals(0, entry.getProfit(), 0.0001); + assertTrue(entry.getOrders().isEmpty()); + } + + private Route initTest3(){ + LOG.info("Init test 3"); + market = new SimpleMarket(); + ITEM1 = market.addItem("ITEM1", null); + ITEM2 = market.addItem("ITEM2", null); + ITEM3 = market.addItem("ITEM3", null); + v1 = market.addPlace("p1",0,0,0).addVendor("v1"); + v2 = market.addPlace("p2",0,0,0).addVendor("v2"); + v3 = market.addPlace("p3",0,0,0).addVendor("v3"); + v4 = market.addPlace("p4",0,0,0).addVendor("v4"); + v1.addOffer(OFFER_TYPE.SELL, ITEM1, 100, -1); + v1.addOffer(OFFER_TYPE.SELL, ITEM2, 200, -1); + v1.addOffer(OFFER_TYPE.SELL, ITEM3, 300, -1); + v2.addOffer(OFFER_TYPE.SELL, ITEM1, 150, -1); + v2.addOffer(OFFER_TYPE.SELL, ITEM3, 320, -1); + v3.addOffer(OFFER_TYPE.SELL, ITEM3, 390, -1); + v2.addOffer(OFFER_TYPE.BUY, ITEM2, 225, -1); + v3.addOffer(OFFER_TYPE.BUY, ITEM1, 200, -1); + v4.addOffer(OFFER_TYPE.BUY, ITEM3, 450, -1); + + Route route = new Route(new RouteEntry(v1, false, 0)); + route.add(new RouteEntry(v2, false, 0)); + route.add(new RouteEntry(v3, false, 0)); + route.add(new RouteEntry(v4, false, 0)); + + return route; + } + + @Test + public void testPathRoute3() throws Exception { + LOG.info("Start route test 3"); + Route route = initTest3(); + RouteFiller filler = getFillerInstance(10000, 5, 0, market); + filler.fill(route); + + assertEquals(800, route.getProfit(), 0.0001); + assertEquals(2, route.getLands()); + + List entries = route.getEntries(); + Order order1 = new Order(v1.getSell(ITEM1), v3.getBuy(ITEM1), 5); + Order order7 = new Order(v3.getSell(ITEM3), v4.getBuy(ITEM3), 5); + + RouteEntry entry = entries.get(0); + assertEquals(500, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order1); + + entry = entries.get(1); + assertEquals(0, entry.getProfit(), 0.0001); + assertTrue(entry.getOrders().isEmpty()); + + entry = entries.get(2); + assertEquals(300, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order7); + } + + @Test + public void testPathRoute3byLands() throws Exception { + LOG.info("Start route test 3 by lands"); + Route route = initTest3(); + RouteFiller filler = getFillerInstance(10000, 5, 1, market); + filler.fill(route); + + assertEquals(750, route.getProfit(), 0.0001); + assertEquals(1, route.getLands()); + + List entries = route.getEntries(); + Order order3 = new Order(v1.getSell(ITEM3), v4.getBuy(ITEM3), 5); + + RouteEntry entry = entries.get(0); + assertEquals(750, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order3); + + entry = entries.get(1); + assertEquals(0, entry.getProfit(), 0.0001); + assertTrue(entry.getOrders().isEmpty()); + + entry = entries.get(2); + assertEquals(0, entry.getProfit(), 0.0001); + assertTrue(entry.getOrders().isEmpty()); + } + + private Route initTest4(){ + LOG.info("Init test 4"); + market = new SimpleMarket(); + ITEM1 = market.addItem("ITEM1", null); + ITEM2 = market.addItem("ITEM2", null); + ITEM3 = market.addItem("ITEM3", null); + v1 = market.addPlace("p1",0,0,0).addVendor("v1"); + v2 = market.addPlace("p2",0,0,0).addVendor("v2"); + v3 = market.addPlace("p3",0,0,0).addVendor("v3"); + v4 = market.addPlace("p4",0,0,0).addVendor("v4"); + v5 = market.addPlace("p5",0,0,0).addVendor("v5"); + v1.addOffer(OFFER_TYPE.SELL, ITEM1, 410, -1); + v1.addOffer(OFFER_TYPE.SELL, ITEM2, 200, -1); + v1.addOffer(OFFER_TYPE.SELL, ITEM3, 300, -1); + v2.addOffer(OFFER_TYPE.SELL, ITEM2, 270, -1); + v4.addOffer(OFFER_TYPE.SELL, ITEM1, 300, -1); + v2.addOffer(OFFER_TYPE.BUY, ITEM1, 470, -1); + v3.addOffer(OFFER_TYPE.BUY, ITEM2, 300, -1); + v4.addOffer(OFFER_TYPE.BUY, ITEM3, 370, -1); + v5.addOffer(OFFER_TYPE.BUY, ITEM1, 400, -1); + + Route route = new Route(new RouteEntry(v1, false, 0)); + route.add(new RouteEntry(v2, false, 0)); + route.add(new RouteEntry(v3, false, 0)); + route.add(new RouteEntry(v4, false, 0)); + route.add(new RouteEntry(v5, false, 0)); + + return route; + } + + @Test + public void testPathRoute4() throws Exception { + LOG.info("Start route test 4"); + Route route = initTest4(); + RouteFiller filler = getFillerInstance(10000, 5, 0, market); + filler.fill(route); + + assertEquals(1000, route.getProfit(), 0.0001); + assertEquals(3, route.getLands()); + + List entries = route.getEntries(); + Order order3 = new Order(v1.getSell(ITEM2), v3.getBuy(ITEM2), 5); + Order order6 = new Order(v4.getSell(ITEM1), v5.getBuy(ITEM1), 5); + + RouteEntry entry = entries.get(0); + assertEquals(500, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order3); + + entry = entries.get(1); + assertEquals(0, entry.getProfit(), 0.0001); + assertTrue(entry.getOrders().isEmpty()); + + entry = entries.get(2); + assertEquals(0, entry.getProfit(), 0.0001); + assertTrue(entry.getOrders().isEmpty()); + + entry = entries.get(3); + assertEquals(500, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order6); + } + + private Route initTest5(){ + LOG.info("Init test 5"); + market = new SimpleMarket(); + ITEM1 = market.addItem("ITEM1", null); + ITEM2 = market.addItem("ITEM2", null); + ITEM3 = market.addItem("ITEM3", null); + ITEM4 = market.addItem("ITEM4", null); + v1 = market.addPlace("p1",0,0,0).addVendor("v1"); + v2 = market.addPlace("p2",0,0,0).addVendor("v2"); + v3 = market.addPlace("p3",0,0,0).addVendor("v3"); + v4 = market.addPlace("p4",0,0,0).addVendor("v4"); + v1.addOffer(OFFER_TYPE.SELL, ITEM1, 100, 5); + v1.addOffer(OFFER_TYPE.SELL, ITEM2, 200, 5); + v1.addOffer(OFFER_TYPE.SELL, ITEM3, 300, 5); + v1.addOffer(OFFER_TYPE.SELL, ITEM4, 40, -1); + v2.addOffer(OFFER_TYPE.SELL, ITEM1, 150, 5); + v2.addOffer(OFFER_TYPE.SELL, ITEM3, 320, 5); + v3.addOffer(OFFER_TYPE.SELL, ITEM3, 390, 5); + v2.addOffer(OFFER_TYPE.BUY, ITEM2, 300, -1); + v2.addOffer(OFFER_TYPE.BUY, ITEM4, 50, -1); + v3.addOffer(OFFER_TYPE.BUY, ITEM1, 200, -1); + v4.addOffer(OFFER_TYPE.BUY, ITEM3, 450, -1); + + Route route = new Route(new RouteEntry(v1, false, 0)); + route.add(new RouteEntry(v2, false, 0)); + route.add(new RouteEntry(v3, false, 0)); + route.add(new RouteEntry(v4, false, 0)); + + return route; + } + + + @Test + public void testPathRoute5() throws Exception { + LOG.info("Start route test 5"); + Route route = initTest5(); + RouteFiller filler = getFillerInstance(500, 5, 0, market); + filler.fill(route); + + assertEquals(620, route.getProfit(), 0.0001); + assertEquals(2, route.getLands()); + + List entries = route.getEntries(); + Order order1 = new Order(v1.getSell(ITEM1), v3.getBuy(ITEM1), 5); + Order order7 = new Order(v3.getSell(ITEM3), v4.getBuy(ITEM3), 2); + + RouteEntry entry = entries.get(0); + assertEquals(500, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order1); + + entry = entries.get(1); + assertEquals(0, entry.getProfit(), 0.0001); + assertTrue(entry.getOrders().isEmpty()); + + entry = entries.get(2); + assertEquals(120, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order7); + } + + @Test + public void testPathRoute5B() throws Exception { + LOG.info("Start route test 5B"); + Route route = initTest5(); + RouteFiller filler = getFillerInstance(700, 7, 0, market); + filler.fill(route); + + assertEquals(750, route.getProfit(), 0.0001); + assertEquals(3, route.getLands()); + + List entries = route.getEntries(); + Order order2 = new Order(v1.getSell(ITEM2), v2.getBuy(ITEM2), 3); + Order order3 = new Order(v1.getSell(ITEM4), v2.getBuy(ITEM4), 2); + Order order4 = new Order(v2.getSell(ITEM1), v3.getBuy(ITEM1), 5); + Order order7 = new Order(v3.getSell(ITEM3), v4.getBuy(ITEM3), 3); + + RouteEntry entry = entries.get(0); + assertEquals(320, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order2, order3); + + entry = entries.get(1); + assertEquals(250, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order4); + + entry = entries.get(2); + assertEquals(180, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order7); + } + + private Route initTest6A(){ + LOG.info("Init test 5"); + market = new SimpleMarket(); + ITEM1 = market.addItem("ITEM1", null); + ITEM2 = market.addItem("ITEM2", null); + ITEM3 = market.addItem("ITEM3", null); + ITEM4 = market.addItem("ITEM4", null); + v1 = market.addPlace("p1",0,0,0).addVendor("v1"); + v2 = market.addPlace("p2",0,1,0).addVendor("v2"); + v1.addOffer(OFFER_TYPE.SELL, ITEM1, 100, -1); + v1.addOffer(OFFER_TYPE.SELL, ITEM2, 200, -1); + v1.addOffer(OFFER_TYPE.SELL, ITEM3, 300, -1); + v2.addOffer(OFFER_TYPE.SELL, ITEM1, 150, -1); + v2.addOffer(OFFER_TYPE.SELL, ITEM3, 320, -1); + + v2.addOffer(OFFER_TYPE.BUY, ITEM2, 225, -1); + + Route route = new Route(new RouteEntry(v1, false, 0)); + route.add(new RouteEntry(v2, false, 0)); + + + return route; + } + + private Route initTest6B(){ + LOG.info("Init test 6B"); + v3 = market.addPlace("p3",0,1,1).addVendor("v3"); + v4 = market.addPlace("p4",1,1,1).addVendor("v4"); + + v3.addOffer(OFFER_TYPE.SELL, ITEM3, 390, -1); + + v3.addOffer(OFFER_TYPE.BUY, ITEM1, 200, -1); + v4.addOffer(OFFER_TYPE.BUY, ITEM3, 450, -1); + + Route route = new Route(new RouteEntry(v2, false, 0)); + route.add(new RouteEntry(v3, false, 0)); + route.add(new RouteEntry(v4, false, 0)); + + return route; + } + + + @Test + public void testJoinRoute() throws Exception { + LOG.info("Start join route test"); + Route route = initTest6A(); + Route routeB = initTest6B(); + RouteFiller filler = getFillerInstance(500, 5, 0, market); + filler.fill(route); + filler.fill(routeB); + + route.join(routeB); + filler.fill(route); + + assertEquals(620, route.getProfit(), 0.0001); + assertEquals(2, route.getLands()); + assertEquals(3, route.getDistance(), 0.0001); + + List entries = route.getEntries(); + 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); + + RouteEntry entry = entries.get(0); + assertEquals(500, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order1); + + entry = entries.get(1); + assertEquals(0, entry.getProfit(), 0.0001); + assertTrue(entry.getOrders().isEmpty()); + + entry = entries.get(2); + assertEquals(120, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order7); + + } + + @Test + public void testJoinRouteNoSort() throws Exception { + LOG.info("Start join route test"); + Route route = initTest6A(); + Route routeB = initTest6B(); + RouteFiller filler = getFillerInstance(500, 5, 0, market); + filler.fill(route); + filler = getFillerInstance(550, 5, 0, market); + filler.fill(routeB); + + route.join(routeB); + + assertEquals(260, route.getProfit(), 0.0001); + assertEquals(3, route.getLands()); + assertEquals(3, route.getDistance(), 0.0001); + + List entries = route.getEntries(); + 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), 1); + + + RouteEntry entry = entries.get(0); + assertEquals(50, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order2); + + entry = entries.get(1); + assertEquals(150, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order4); + + entry = entries.get(2); + assertEquals(60, entry.getProfit(), 0.0001); + TestUtil.assertCollectionEquals(entry.getOrders(), order7); + + } + +} diff --git a/core/src/test/java/ru/trader/analysis/RouteTest.java b/core/src/test/java/ru/trader/analysis/RouteTest.java new file mode 100644 index 0000000..c17bfce --- /dev/null +++ b/core/src/test/java/ru/trader/analysis/RouteTest.java @@ -0,0 +1,66 @@ +package ru.trader.analysis; + +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.TestUtil; +import ru.trader.core.Vendor; +import ru.trader.store.simple.SimpleVendor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +public class RouteTest extends Assert { + private final static Logger LOG = LoggerFactory.getLogger(RouteTest.class); + + private Vendor v1; + private Vendor v2; + private Vendor v3; + private Vendor v4; + + @Test + public void testVendors() throws Exception { + LOG.info("Start test get vendors"); + v1 = new SimpleVendor("v1",0,0,0); + v2 = new SimpleVendor("v2",0,0,0); + v3 = new SimpleVendor("v3",0,0,0); + v4 = new SimpleVendor("v4",0,0,0); + + Route path = new Route(new RouteEntry(v1, false, 0)); + path.add(new RouteEntry(v2, false, 0)); + path.add(new RouteEntry(v3, false, 0)); + TestUtil.assertCollectionContainAll(path.getVendors(), v1, v2, v3); + } + + @Test + public void testContains() throws Exception { + LOG.info("Start test get entries"); + v1 = new SimpleVendor("v1",0,0,0); + v2 = new SimpleVendor("v2",0,0,0); + v3 = new SimpleVendor("v3",0,0,0); + v4 = new SimpleVendor("v4",0,0,0); + + Route path = new Route(new RouteEntry(v1, false, 0)); + path.add(new RouteEntry(v2, false, 0)); + path.add(new RouteEntry(v3, false, 0)); + Collection vendors = new ArrayList<>(); + Collections.addAll(vendors, v1, v2, v3); + assertTrue(path.contains(vendors)); + vendors.clear(); + Collections.addAll(vendors, v2); + assertTrue(path.contains(vendors)); + vendors.clear(); + Collections.addAll(vendors, v4); + assertFalse(path.contains(vendors)); + vendors.clear(); + Collections.addAll(vendors, v3, v2, v4, v1); + assertFalse(path.contains(vendors)); + vendors.clear(); + Collections.addAll(vendors, v1, v2, v3, v4); + assertFalse(path.contains(vendors)); + + } + +}