From 374e1d6c62f63c9ff30ef814cb7e346f7e98c1fd Mon Sep 17 00:00:00 2001 From: iMoHax Date: Fri, 14 Oct 2016 15:57:14 +0300 Subject: [PATCH] Increase search speed, add minimum vendor rating parameter for skip low profitable vendors --- .../controllers/SettingsController.java | 4 + .../resources/lang/locale_en_US.properties | 5 +- .../resources/lang/locale_ru_RU.properties | 5 +- client/src/main/resources/view/settings.fxml | 65 +++---- .../trader/analysis/CrawlerSpecificator.java | 25 ++- .../main/java/ru/trader/analysis/Scorer.java | 162 +++++++++++------- .../java/ru/trader/core/MarketAnalyzer.java | 21 ++- .../src/main/java/ru/trader/core/Profile.java | 13 ++ .../java/ru/trader/analysis/ScorerTest.java | 2 +- 9 files changed, 197 insertions(+), 105 deletions(-) diff --git a/client/src/main/java/ru/trader/controllers/SettingsController.java b/client/src/main/java/ru/trader/controllers/SettingsController.java index 1fd8999..7058136 100644 --- a/client/src/main/java/ru/trader/controllers/SettingsController.java +++ b/client/src/main/java/ru/trader/controllers/SettingsController.java @@ -36,6 +36,8 @@ public class SettingsController { @FXML private NumberField routesCount; @FXML + private NumberField minVendorRating; + @FXML private NumberField fuelPrice; @FXML private ComboBox pathPriority; @@ -93,6 +95,7 @@ public class SettingsController { jumps.setValue(profile.getJumps()); lands.setValue(profile.getLands()); routesCount.setValue(profile.getRoutesCount()); + minVendorRating.setValue(profile.getMinVendorRating()); fuelPrice.setValue(profile.getFuelPrice()); pathPriority.setValue(profile.getPathPriority()); jumpTime.setValue(profile.getJumpTime()); @@ -134,6 +137,7 @@ public class SettingsController { profile.setJumps(jumps.getValue().intValue()); profile.setLands(lands.getValue().intValue()); profile.setRoutesCount(routesCount.getValue().intValue()); + profile.setMinVendorRating(minVendorRating.getValue().doubleValue()); profile.setFuelPrice(fuelPrice.getValue().intValue()); profile.setPathPriority(pathPriority.getValue()); profile.setJumpTime(jumpTime.getValue().intValue()); diff --git a/client/src/main/resources/lang/locale_en_US.properties b/client/src/main/resources/lang/locale_en_US.properties index 9958abe..9d0e1e0 100644 --- a/client/src/main/resources/lang/locale_en_US.properties +++ b/client/src/main/resources/lang/locale_en_US.properties @@ -184,13 +184,14 @@ settings.emdn.on=Active settings.emdn.sub=Server SUB: settings.emdn.updateOnly=Update price only: settings.emdn.auto=Auto update (sec.): +settings.edce.on=Active +settings.edce.interval=Update interval, sec. settings.performance=Performance settings.performance.segmentSize=Segment size (0 - auto): settings.performance.limit=Routes count: -settings.edce.on=Active -settings.edce.interval=Update interval, sec. settings.performance.jumps=Jumps: settings.performance.landings=Landings: +settings.performance.minVendorRating=Minimal station rating (0-10): settings.search=Search options settings.search.fuelPrice=1t fuel price: settings.search.pathType=Path priority: diff --git a/client/src/main/resources/lang/locale_ru_RU.properties b/client/src/main/resources/lang/locale_ru_RU.properties index e7730d8..0c3ca8a 100644 --- a/client/src/main/resources/lang/locale_ru_RU.properties +++ b/client/src/main/resources/lang/locale_ru_RU.properties @@ -183,13 +183,14 @@ settings.emdn.on=\u0412\u043A\u043B\u044E\u0447\u0438\u0442\u044C settings.emdn.sub=\u0421\u0435\u0440\u0432\u0435\u0440 SUB: settings.emdn.updateOnly=\u0422\u043E\u043B\u044C\u043A\u043E \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u0446\u0435\u043D\u044B: settings.emdn.auto=\u041E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u0430\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u0447\u0435\u0440\u0435\u0437 (\u0441\u0435\u043A.): +settings.edce.on=\u0412\u043A\u043B\u044E\u0447\u0438\u0442\u044C +settings.edce.interval=\u0418\u043D\u0442\u0435\u0440\u0432\u0430\u043B, \u0441\u0435\u043A. settings.performance=\u041F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C settings.performance.segmentSize=\u041C\u0430\u0440\u0448\u0440\u0443\u0442\u043E\u0432 \u0432 \u0441\u0435\u0433\u043C\u0435\u043D\u0442\u0435 (0 - \u0430\u0432\u0442\u043E): settings.performance.limit=\u041A\u043E\u043B-\u0432\u043E \u043C\u0430\u0440\u0448\u0440\u0443\u0442\u043E\u0432 \u043D\u0430 \u0432\u044B\u0432\u043E\u0434: -settings.edce.on=\u0412\u043A\u043B\u044E\u0447\u0438\u0442\u044C -settings.edce.interval=\u0418\u043D\u0442\u0435\u0440\u0432\u0430\u043B, \u0441\u0435\u043A. settings.performance.jumps=\u041F\u0440\u044B\u0436\u043A\u043E\u0432: settings.performance.landings=\u041F\u043E\u0441\u0430\u0434\u043E\u043A: +settings.performance.minVendorRating=\u041C\u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0435\u0439\u0442\u0438\u043D\u0433 \u0441\u0442\u0430\u043D\u0446\u0438\u0438 (0-10): settings.search=\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B \u043F\u043E\u0438\u0441\u043A\u0430 settings.search.fuelPrice=\u0421\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C 1\u0442 \u0442\u043E\u043F\u043B\u0438\u0432\u0430: settings.search.pathType=\u0422\u0438\u043F \u043C\u0430\u0440\u0448\u0440\u0443\u0442\u043E\u0432: diff --git a/client/src/main/resources/view/settings.fxml b/client/src/main/resources/view/settings.fxml index 2ad7f63..67160bd 100644 --- a/client/src/main/resources/view/settings.fxml +++ b/client/src/main/resources/view/settings.fxml @@ -2,8 +2,9 @@ - + + @@ -26,38 +27,40 @@ - + diff --git a/core/src/main/java/ru/trader/analysis/CrawlerSpecificator.java b/core/src/main/java/ru/trader/analysis/CrawlerSpecificator.java index ed339c6..fef495d 100644 --- a/core/src/main/java/ru/trader/analysis/CrawlerSpecificator.java +++ b/core/src/main/java/ru/trader/analysis/CrawlerSpecificator.java @@ -1,5 +1,6 @@ package ru.trader.analysis; +import org.jetbrains.annotations.Nullable; import ru.trader.analysis.graph.Edge; import ru.trader.core.Offer; import ru.trader.core.Vendor; @@ -165,6 +166,7 @@ public class CrawlerSpecificator { if (res) return true; for (TimeEntry entry : offers){ Offer offer = entry.obj; + if (offer.getVendor().equals(vendor)) return true; Offer sell = vendor.getSell(offer.getItem()); res = sell != null && sell.getCount() >= offer.getCount(); if (res) return true; @@ -172,12 +174,23 @@ public class CrawlerSpecificator { return false; } - public Collection getVendors(Collection vendors){ - Set v = containsAny.stream().map(e -> e.obj).collect(Collectors.toSet()); - any.stream().map(e -> e.obj).forEach(v::add); - all.stream().map(e -> e.obj).forEach(v::add); - offers.stream().map(e -> e.obj.getVendor()).forEach(v::add); - v.addAll(vendors); + public Set getVendors(Collection vendors){ + Set v = all.stream().map(e -> e.obj).collect(Collectors.toSet()); + if (containsAny.size() > 0){ + vendors.stream().filter(r -> contains(containsAny, r.getVendor())).sorted().limit(5).forEach(r -> v.add(r.getVendor())); + } + if (any.size() > 0){ + vendors.stream().filter(r -> contains(any, r.getVendor())).sorted().limit(5).forEach(r -> v.add(r.getVendor())); + } + + for (TimeEntry entry : offers) { + vendors.stream().filter(r -> { + Offer offer = entry.obj; + if (offer.getVendor().equals(r.getVendor())) return true; + Offer sell = r.getVendor().getSell(offer.getItem()); + return sell != null && sell.getCount() >= offer.getCount(); + }).sorted().limit(5).forEach(r -> v.add(r.getVendor())); + } return v; } diff --git a/core/src/main/java/ru/trader/analysis/Scorer.java b/core/src/main/java/ru/trader/analysis/Scorer.java index 9838860..6c231e6 100644 --- a/core/src/main/java/ru/trader/analysis/Scorer.java +++ b/core/src/main/java/ru/trader/analysis/Scorer.java @@ -27,70 +27,26 @@ 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 minProfit; - private final double maxProfit; - private final double maxScore; private final double avgDistance; 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().forEach(this::fillOffers); - DoubleSummaryStatistics statProfit = computeProfit(); - minProfit = statProfit.getMin() / profile.getShip().getCargo(); - avgProfit = statProfit.getAverage() / profile.getShip().getCargo(); - maxProfit = statProfit.getMax() / profile.getShip().getCargo(); - avgDistance = computeAvgDistance(); - maxScore = getScore(1, statProfit.getMax(), 0, 1, 0, 0); } public Profile getProfile() { return profile; } - private void fillOffers(Item item){ - Optional offer = market.getSell(item).findFirst(); - if (offer.isPresent()){ - sellOffers.put(item, offer.get()); - } - offer = market.getBuy(item).findFirst(); - if (offer.isPresent()){ - buyOffers.put(item, offer.get()); - } - } - - private DoubleSummaryStatistics computeProfit(){ - return sellOffers.values().stream() - .flatMap(this::mapToOrder) - .collect(Collectors.summarizingDouble(Order::getProfit)); - } - private double computeAvgDistance(){ OptionalDouble res = market.getVendors().mapToDouble(Vendor::getDistance).average(); return res.orElse(0); } - public double getAvgProfit() { - return avgProfit; - } - - public double getMaxProfit() { - return maxProfit; - } - - public double getMaxScore() { - return maxScore; - } - public double getAvgDistance() { return avgDistance; } @@ -157,25 +113,113 @@ public class Scorer { return score; } - private Stream mapToOrder(Offer offer) { - Offer sell; - Offer buy; - if (offer.getType() == OFFER_TYPE.SELL){ - sell = offer; - buy = buyOffers.get(offer.getItem()); - } else { - sell = sellOffers.get(offer.getItem()); - buy = offer; - } - if (sell == null || buy == null) return Stream.empty(); - Order order = new Order(sell, buy, profile.getBalance(), profile.getShip().getCargo()); - if (order.getProfit() <= 0) return Stream.empty(); - return Stream.of(order); - } public List getOrders(Vendor seller, Vendor buyer){ FilteredVendor fSeller = market.getFiltered(seller); FilteredVendor fBuyer = market.getFiltered(buyer); return MarketUtils.getOrders(fSeller, fBuyer); } + + public RatingComputer getRatingComputer(final Set vendors){ + return new RatingComputer(vendors); + } + + public class RatingComputer { + private final Map sellOffers; + private final Map buyOffers; + + private final DoubleSummaryStatistics globalStat; + private final double avgDistance; + + private RatingComputer(final Set vendors) { + sellOffers = new HashMap<>(100, 0.9f); + buyOffers = new HashMap<>(100, 0.9f); + market.getItems().forEach(i -> fillOffers(i, vendors)); + globalStat = computeProfit(); + avgDistance = vendors.stream().mapToDouble(Vendor::getDistance).average().orElse(0); + } + + private void fillOffers(Item item, Set vendors){ + Optional offer = market.getSell(item).filter(o -> vendors.contains(o.getVendor())).findFirst(); + if (offer.isPresent()){ + sellOffers.put(item, offer.get()); + } + offer = market.getBuy(item).filter(o -> vendors.contains(o.getVendor())).findFirst(); + if (offer.isPresent()){ + buyOffers.put(item, offer.get()); + } + } + + private Stream mapToOrder(Offer offer) { + Offer sell; + Offer buy; + if (offer.getType() == OFFER_TYPE.SELL){ + sell = offer; + buy = buyOffers.get(offer.getItem()); + } else { + sell = sellOffers.get(offer.getItem()); + buy = offer; + } + if (sell == null || buy == null) return Stream.empty(); + Order order = new Order(sell, buy, profile.getBalance(), profile.getShip().getCargo()); + if (order.getProfit() <= 0) return Stream.empty(); + return Stream.of(order); + } + + private DoubleSummaryStatistics computeProfit(){ + return sellOffers.values().stream() + .flatMap(this::mapToOrder) + .collect(Collectors.summarizingDouble(Order::getProfit)); + } + + private DoubleSummaryStatistics computeProfits(Stream orders) { + return orders.sorted(Comparator.reverseOrder()) + .limit(4) + .filter(o -> o.getProfit() > 0) + .collect(Collectors.summarizingDouble(Order::getProfit)); + } + + public Rating getRating(Vendor vendor){ + Stream sell = vendor.getAllSellOffers().stream().flatMap(this::mapToOrder); + Stream buy = vendor.getAllBuyOffers().stream().flatMap(this::mapToOrder); + + DoubleSummaryStatistics sellStat = computeProfits(sell); + DoubleSummaryStatistics buyStat = computeProfits(buy); + + double sellRate = 0.5 * sellStat.getMax() / globalStat.getMax() + 2.5 * sellStat.getAverage() / globalStat.getAverage(); + double buyRate = 0.5 * buyStat.getMax() / globalStat.getMax() + 2 * buyStat.getAverage() / globalStat.getAverage(); + double distRate = 0.5 * (vendor.getDistance() > 0 ? (vendor.getDistance() < avgDistance ? 1-vendor.getDistance()/avgDistance : -1+avgDistance/vendor.getDistance()) : 0.0); + + LOG.trace("Computed rate for {} = {}", vendor.getFullName(), sellRate + buyRate + distRate); + LOG.trace("global - max: {} avg: {} min: {}", globalStat.getMax(), globalStat.getAverage(), globalStat.getMin()); + LOG.trace("sell - max: {} avg: {} min: {} rate: {}", sellStat.getMax(), sellStat.getAverage(), sellStat.getMin(), sellRate); + LOG.trace("buy - max: {} avg: {} min: {} rate: {}", buyStat.getMax(), buyStat.getAverage(), buyStat.getMin(), buyRate); + LOG.trace("distance: {} avg: {} rate: {}", vendor.getDistance(), avgDistance, distRate); + + return new Rating(vendor, sellRate + buyRate + distRate); + } + } + + public class Rating implements Comparable { + private final Vendor vendor; + private final double rate; + + public Rating(Vendor vendor, double rate) { + this.vendor = vendor; + this.rate = rate; + } + + public Vendor getVendor() { + return vendor; + } + + public double getRate() { + return rate; + } + + @Override + public int compareTo(Rating o) { + return Double.compare(rate, o.rate); + } + } } diff --git a/core/src/main/java/ru/trader/core/MarketAnalyzer.java b/core/src/main/java/ru/trader/core/MarketAnalyzer.java index 6371243..587081e 100644 --- a/core/src/main/java/ru/trader/core/MarketAnalyzer.java +++ b/core/src/main/java/ru/trader/core/MarketAnalyzer.java @@ -296,13 +296,26 @@ public class MarketAnalyzer { private Collection getVendors(CrawlerSpecificator specificator, boolean withTransit){ List transits = withTransit ? market.get().map(Place::asTransit).collect(Collectors.toList()) : new ArrayList<>(); - Collection vendors; + Set vendors; if (!specificator.isFullScan() || specificator.getMinHop() >= profile.getLands()){ - vendors = market.getMarkets().filter(specificator::contains).collect(Collectors.toList()); + vendors = market.getMarkets().filter(specificator::contains).collect(Collectors.toSet()); } else { - vendors = market.getMarkets().collect(Collectors.toList()); + vendors = market.getMarkets().collect(Collectors.toSet()); } - vendors = specificator.getVendors(vendors); + Scorer.RatingComputer ratings = searcher.getScorer().getRatingComputer(vendors); + Collection removes = new ArrayList<>(500); + vendors.removeIf(v -> { + Scorer.Rating rating = ratings.getRating(v); + if (rating.getRate() <= profile.getMinVendorRating()){ + if (specificator.contains(v)){ + removes.add(rating); + } + return true; + } + return false; + }); + + vendors.addAll(specificator.getVendors(removes)); vendors.addAll(transits); return vendors; } diff --git a/core/src/main/java/ru/trader/core/Profile.java b/core/src/main/java/ru/trader/core/Profile.java index eef84b1..d94d715 100644 --- a/core/src/main/java/ru/trader/core/Profile.java +++ b/core/src/main/java/ru/trader/core/Profile.java @@ -15,6 +15,7 @@ public class Profile { private int lands; private boolean refill; private int routesCount; + private double minVendorRating; //Scorer multipliers private double distanceTime; private double jumpTime; @@ -31,6 +32,7 @@ public class Profile { jumps = 3; lands = 4; routesCount = 30; + minVendorRating = 7; distanceTime = 0.3; fuelPrice = 100; landingTime = 80; @@ -49,6 +51,7 @@ public class Profile { this.lands = profile.lands; this.refill = profile.refill; this.routesCount = profile.routesCount; + this.minVendorRating = profile.minVendorRating; this.distanceTime = profile.distanceTime; this.jumpTime = profile.jumpTime; this.landingTime = profile.landingTime; @@ -142,6 +145,14 @@ public class Profile { this.routesCount = routesCount; } + public double getMinVendorRating() { + return minVendorRating; + } + + public void setMinVendorRating(double minVendorRating) { + this.minVendorRating = minVendorRating; + } + public double getDistanceTime() { return distanceTime; } @@ -230,6 +241,7 @@ public class Profile { profile.setLands(Integer.valueOf(values.getProperty("profile.lands", "4"))); profile.setPathPriority(PATH_PRIORITY.valueOf(values.getProperty("profile.search.priority", "FAST"))); profile.setRoutesCount(Integer.valueOf(values.getProperty("profile.search.routes", "30"))); + profile.setMinVendorRating(Double.valueOf(values.getProperty("profile.search.minRating", "7"))); profile.setFuelPrice(Double.valueOf(values.getProperty("profile.search.fuel.price", "100"))); profile.setDistanceTime(Double.valueOf(values.getProperty("profile.search.times.distance", "0.3"))); profile.setLandingTime(Double.valueOf(values.getProperty("profile.search.times.landing", "80"))); @@ -250,6 +262,7 @@ public class Profile { values.setProperty("profile.lands", String.valueOf(lands)); values.setProperty("profile.search.priority", String.valueOf(pathPriority)); values.setProperty("profile.search.routes", String.valueOf(routesCount)); + values.setProperty("profile.search.minRating", String.valueOf(minVendorRating)); values.setProperty("profile.search.fuel.price", String.valueOf(fuelPrice)); values.setProperty("profile.search.times.distance", String.valueOf(distanceTime)); values.setProperty("profile.search.times.landing", String.valueOf(landingTime)); diff --git a/core/src/test/java/ru/trader/analysis/ScorerTest.java b/core/src/test/java/ru/trader/analysis/ScorerTest.java index 7534bd7..708d566 100644 --- a/core/src/test/java/ru/trader/analysis/ScorerTest.java +++ b/core/src/test/java/ru/trader/analysis/ScorerTest.java @@ -36,7 +36,7 @@ public class ScorerTest extends Assert { profile.setBalance(1000000); Scorer scorer = new Scorer(fWorld, profile); - double avgProfit = scorer.getAvgProfit() * profile.getShip().getCargo(); + double avgProfit = 750 * profile.getShip().getCargo(); double score = scorer.getScore(scorer.getAvgDistance(), 0, 1, 1, 0, 4); double score1 = scorer.getScore(scorer.getAvgDistance(), avgProfit/2, 1, 1, 0, 4);