Archived
0

implement scorer

This commit is contained in:
iMoHax
2015-05-16 17:23:09 +03:00
parent bef5cc2f99
commit 42a958ecb2
6 changed files with 10528 additions and 40 deletions

View File

@@ -3,6 +3,7 @@ package ru.trader.analysis;
import ru.trader.core.*;
import java.util.Collection;
import java.util.NavigableSet;
import java.util.stream.Stream;
public class FilteredMarket {
@@ -37,8 +38,13 @@ public class FilteredMarket {
}
public Stream<Offer> getOffers(OFFER_TYPE offerType, Item item){
return market.getStat(offerType, item).getOffers().stream()
.filter(o -> !filter.isFiltered(o.getVendor(), true));
NavigableSet<Offer> offers = market.getStat(offerType, item).getOffers();
Stream<Offer> res;
if (offerType.getOrder() > 0)
res = offers.stream();
else
res = offers.descendingSet().stream();
return res.filter(o -> !filter.isFiltered(o.getVendor(), true));
}

View File

@@ -1,8 +1,10 @@
package ru.trader.analysis;
import org.jetbrains.annotations.NotNull;
import ru.trader.core.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Scorer {
@@ -11,9 +13,11 @@ public class Scorer {
private final FilteredMarket market;
private final Profile profile;
private int ordersCount = 5;
private final double avgProfit;
private final double avgDistance;
private double avgProfit;
private int ordersCount = 5;
private double distanceRate = 1;
public Scorer(FilteredMarket market, Profile profile) {
this.market = market;
@@ -22,6 +26,7 @@ public class Scorer {
buyOffers = new HashMap<>(100, 0.9f);
market.getItems().parallelStream().forEach(this::fillOffers);
avgProfit = computeAvgProfit();
avgDistance = computeAvgDistance();
}
private void fillOffers(Item item){
@@ -35,22 +40,33 @@ public class Scorer {
}
}
public void setOrdersCount(int ordersCount) {
this.ordersCount = ordersCount;
}
private double computeAvgProfit(){
OptionalDouble avg = sellOffers.values().stream()
.flatMap(this::mapToOrder)
.mapToDouble(Order::getProfit)
.mapToDouble(o -> o.getProfit() / profile.getShip().getCargo())
.average();
return avg.orElse(0);
}
private double computeAvgDistance(){
OptionalDouble res = market.getVendors().mapToDouble(Vendor::getDistance).average();
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 Score getScore(Vendor vendor){
Stream<Order> sellOrders = vendor.getAllSellOffers().stream().flatMap(this::mapToOrder);
Stream<Order> buyOrders = vendor.getAllBuyOffers().stream().flatMap(this::mapToOrder);
return new Score(sellOrders, buyOrders);
return new Score(vendor);
}
private Stream<Order> mapToOrder(Offer offer) {
@@ -69,33 +85,59 @@ public class Scorer {
return Stream.of(order);
}
public class Score {
private double sellProfit;
private double buyProfit;
public class Score implements Comparable<Score> {
private final Vendor vendor;
private final DoubleSummaryStatistics sellStat;
private final DoubleSummaryStatistics buyStat;
private double score;
public Score(Stream<Order> sell, Stream<Order> buy) {
sellProfit = computeProfits(sell);
buyProfit = computeProfits(buy);
public Score(Vendor vendor) {
this.vendor = vendor;
Stream<Order> sell = vendor.getAllSellOffers().stream().flatMap(Scorer.this::mapToOrder);
Stream<Order> buy = vendor.getAllBuyOffers().stream().flatMap(Scorer.this::mapToOrder);
long count = sell.limit(ordersCount).count();
computeScore(count);
sellStat = computeProfits(sell);
buyStat = computeProfits(buy);
computeScore();
}
private double computeProfits(Stream<Order> orders) {
OptionalDouble profit = orders.sorted(Comparator.<Order>reverseOrder())
public double getSellProfit() {
return sellStat.getAverage();
}
public double getBuyProfit() {
return buyStat.getAverage();
}
public double getScore() {
return score;
}
private DoubleSummaryStatistics computeProfits(Stream<Order> orders) {
return orders.sorted(Comparator.<Order>reverseOrder())
.limit(ordersCount)
.mapToDouble(Order::getProfit)
.average();
return profit.orElse(0);
.collect(Collectors.summarizingDouble(o -> o.getProfit() / profile.getShip().getCargo()));
}
private void computeScore(long sellOrdersCount){
score = (sellProfit + buyProfit)/2;
if (sellOrdersCount < ordersCount){
score =- Math.abs(avgProfit-sellProfit) * (ordersCount - sellOrdersCount) / score;
private void computeScore(){
score = (getSellProfit() + getBuyProfit())/2;
score -= distanceRate * avgProfit * (vendor.getDistance() - avgDistance) / avgDistance;
}
@Override
public String toString() {
return "Score{" +
"vendor=" + vendor.getPlace()+"("+vendor+")"+
", sellStat=" + sellStat +
", buyStat=" + buyStat +
", score=" + score +
'}';
}
@Override
public int compareTo(@NotNull Score other) {
return Double.compare(score, other.score);
}
}

View File

@@ -3,13 +3,44 @@ package ru.trader.core;
public class Profile {
private double balance;
private int jumps;
private Ship ship;
private boolean refill;
public Profile(Ship ship) {
this.ship = ship;
refill = true;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public int getJumps() {
return jumps;
}
public void setJumps(int jumps) {
this.jumps = jumps;
}
public Ship getShip() {
return ship;
}
public void setShip(Ship ship) {
this.ship = ship;
}
public boolean withRefill() {
return refill;
}
public void setRefill(boolean refill) {
this.refill = refill;
}
}

View File

@@ -1,17 +1,26 @@
package ru.trader.core;
public class Ship {
private int cargo;
private double engine;
private Engine engine;
private double tank;
private double mass;
public Ship(int cargo, double engine) {
this.cargo = cargo;
this.engine = engine;
public Ship() {
//Default sidewinder
this.mass = 44.9;
this.cargo = 4;
this.tank = 2;
this.engine = new Engine(2, 'E');
}
public static Ship copyOf(Ship other){
return new Ship(other.cargo, other.engine);
Ship copy = new Ship();
copy.mass = other.mass;
copy.cargo = other.cargo;
copy.tank = other.tank;
copy.engine = other.getEngine();
return copy;
}
public int getCargo() {
@@ -22,12 +31,163 @@ public class Ship {
this.cargo = cargo;
}
public double getEngine() {
public Engine getEngine() {
return engine;
}
public void setEngine(double engine) {
public void setEngine(Engine engine) {
this.engine = engine;
}
public double getTank() {
return tank;
}
public void setTank(double tank) {
this.tank = tank;
}
public double getMass() {
return mass;
}
public void setMass(double mass) {
this.mass = mass;
}
public double getLadenMass(){
return mass+tank+cargo;
}
public double getLadenMass(double fuel){
return mass+fuel+cargo;
}
//Laden fuel cost
public double getFuelCost(double distance){
return engine.getFuelCost(distance, getLadenMass());
}
public double getFuelCost(double fuel, double distance){
return engine.getFuelCost(distance, getLadenMass(fuel));
}
//Jump range with full fuel tank
public double getJumpRange(){
return getJumpRange(tank);
}
//Laden jump range
public double getJumpRange(double fuel){
return engine.getJumpRange(fuel, getLadenMass(fuel));
}
public double getFullTankJumpRange(){
double fuel = tank;
double range = 0;
while (fuel > 0){
double distance = engine.getJumpRange(fuel, getLadenMass(fuel));
range += distance;
fuel -= engine.getFuelCost(distance, getLadenMass(fuel));
}
return range;
}
@Override
public String toString() {
return "Ship{" +
"cargo=" + cargo +
", engine=" + engine +
", tank=" + tank +
", mass=" + mass +
", maxDist=" + getJumpRange() +
", fullTankDist=" + getFullTankJumpRange() +
'}';
}
//FSD multiplier by FSD Rating A,B,C ... etc * 0.001
//http://elite-dangerous.wikia.com/wiki/Frame_Shift_Drive
private final static double[] FSD_MULT = {0.012,0.010,0.008,0.010,0.011};
//FSD power multiplier by FSD Class 1,2,3 ... etc
private final static double[] FSD_POWER_MULT = {0,0,2.00,2.15,2.30,2.45,2.60,2.75,2.90};
//FSD Optimal Mass [class][rating]
private final static double[][] FSD_OPT_MASS = {
{},
{},
{90.0, 75.0, 60.0, 54.0, 48.0},
{150.0, 125.0, 100.0, 90.0, 80.0},
{525.0, 438.0, 350.0, 315.0, 280.0},
{1,050.0, 875.0, 700.0, 630.0, 560.0},
{1,800.0, 1,500.0, 1,200.0, 1,080.0, 960.0}
};
//FSD Max fuel per jump [class][rating]
private final static double[][] FSD_MAX_FUEL= {
{},
{},
{0.90, 0.80, 0.60, 0.60, 0.60},
{1.80, 1.50, 1.20, 1.20, 1.20},
{3.00, 2.50, 2.00, 2.00, 2.00},
{5.00, 4.10, 3.30, 3.30, 3.30},
{8.00, 6.60, 5.30, 5.30, 5.30}
};
private class Engine {
private int rating;
private int clazz;
private Engine(int clazz, char rating) {
setRating(rating);
this.clazz = clazz;
}
public char getRating() {
return (char)(rating + 'A');
}
public void setRating(char rating) {
this.rating = rating - 'A';
}
public int getClazz() {
return clazz;
}
public void setClazz(int clazz) {
this.clazz = clazz;
}
public double getOptMass() {
return FSD_OPT_MASS[clazz][rating];
}
public double getMaxFuel() {
return FSD_MAX_FUEL[clazz][rating];
}
public double getMultiplier(){
return FSD_MULT[rating];
}
public double getPowMultiplier(){
return FSD_POWER_MULT[clazz];
}
//https://forums.frontier.co.uk/showthread.php?p=643461#post643461
//Fuel Cost = Coefficient * (Distance * (Mass / Optimised Mass))^Power
public double getFuelCost(double distance, double mass){
return getMultiplier() * Math.pow(distance * (mass / getOptMass()), getPowMultiplier());
}
public double getJumpRange(double fuel, double mass){
return Math.pow(Math.min(fuel, getMaxFuel())/getMultiplier(), 1/getPowMultiplier())*getOptMass()/mass;
}
@Override
public String toString() {
return ""+clazz+getRating()+
" {optMass="+getOptMass()+
", fuelPJ="+getMaxFuel()+"}";
}
}
}

View File

@@ -0,0 +1,116 @@
package ru.trader.analysis;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.core.*;
import ru.trader.store.simple.Store;
import java.io.InputStream;
public class ScorerTest extends Assert {
private final static Logger LOG = LoggerFactory.getLogger(ScorerTest.class);
private Market world;
private FilteredMarket fWorld;
private Place breksta;
private Place bhadaba;
private Place lhs1541;
private Place itza;
@Before
public void setUp() throws Exception {
InputStream is = getClass().getResourceAsStream("/test3.xml");
world = Store.loadFromFile(is);
breksta = world.get("Breksta");
bhadaba = world.get("Bhadaba");
lhs1541 = world.get("LHS 1541");
itza = world.get("Itza");
MarketFilter filter = new MarketFilter();
fWorld = new FilteredMarket(world, filter);
}
@Test
public void testScore() throws Exception {
Vendor grantTerminal = breksta.get("Grant Terminal");
Vendor perezMarket = breksta.get("Perez market");
Vendor kandelRing = bhadaba.get("Kandel Ring");
Vendor robertsHub = bhadaba.get("Roberts Hub");
Vendor cabreraDock = lhs1541.get("Cabrera Dock");
Vendor hallerPort = lhs1541.get("Haller Port");
Vendor luikenPort = itza.get("Luiken Port");
Ship ship = new Ship();
ship.setCargo(100);
Profile profile = new Profile(ship);
LOG.info("Start score test, balance 10000000");
profile.setBalance(10000000);
Scorer scorer = new Scorer(fWorld, profile);
if (LOG.isDebugEnabled()){
fWorld.getVendors().map(scorer::getScore).sorted().forEach(s -> LOG.debug("{}", s));
}
Scorer.Score gtScore = scorer.getScore(grantTerminal);
Scorer.Score pmScore = scorer.getScore(perezMarket);
assertTrue(gtScore.getSellProfit() < pmScore.getSellProfit());
assertTrue(gtScore.getBuyProfit() > pmScore.getBuyProfit());
assertTrue(gtScore.getScore() > pmScore.getScore());
Scorer.Score krScore = scorer.getScore(kandelRing);
Scorer.Score rhScore = scorer.getScore(robertsHub);
assertTrue(krScore.getScore() < pmScore.getScore());
assertTrue(krScore.getScore() < rhScore.getScore());
assertTrue(krScore.getScore() < gtScore.getScore());
Scorer.Score cdScore = scorer.getScore(cabreraDock);
Scorer.Score hpScore = scorer.getScore(hallerPort);
Scorer.Score lpScore = scorer.getScore(luikenPort);
assertTrue(hpScore.getScore() > pmScore.getScore());
assertTrue(hpScore.getScore() > krScore.getScore());
assertTrue(hpScore.getScore() > gtScore.getScore());
assertTrue(hpScore.getScore() > lpScore.getScore());
assertTrue(hpScore.getScore() > cdScore.getScore());
assertTrue(cdScore.getScore() > pmScore.getScore());
assertTrue(cdScore.getScore() > krScore.getScore());
assertTrue(cdScore.getScore() > gtScore.getScore());
assertTrue(cdScore.getScore() > lpScore.getScore());
LOG.info("Start score test, balance 50000");
profile.setBalance(50000);
Scorer scorer2 = new Scorer(fWorld, profile);
if (LOG.isDebugEnabled()){
fWorld.getVendors().map(scorer2::getScore).sorted().forEach(s -> LOG.debug("{}", s));
}
gtScore = scorer2.getScore(grantTerminal);
pmScore = scorer2.getScore(perezMarket);
assertTrue(gtScore.getSellProfit() > pmScore.getSellProfit());
assertTrue(gtScore.getBuyProfit() > pmScore.getBuyProfit());
assertTrue(gtScore.getScore() > pmScore.getScore());
krScore = scorer2.getScore(kandelRing);
rhScore = scorer2.getScore(robertsHub);
cdScore = scorer2.getScore(cabreraDock);
hpScore = scorer2.getScore(hallerPort);
lpScore = scorer2.getScore(luikenPort);
assertTrue(lpScore.getScore() > pmScore.getScore());
assertTrue(lpScore.getScore() > gtScore.getScore());
assertTrue(lpScore.getScore() > krScore.getScore());
assertTrue(lpScore.getScore() > rhScore.getScore());
assertTrue(lpScore.getScore() > cdScore.getScore());
assertTrue(lpScore.getScore() > hpScore.getScore());
}
@After
public void tearDown() throws Exception {
world = null;
fWorld = null;
}
}

File diff suppressed because one or more lines are too long