Skip to content

Commit

Permalink
Move findBestMarketToBuy to trading.dart
Browse files Browse the repository at this point in the history
  • Loading branch information
eseidel committed Jul 26, 2023
1 parent d7a62db commit 72b2e60
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 107 deletions.
2 changes: 1 addition & 1 deletion packages/cli/bin/find_mounts.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:cli/behavior/deliver.dart';
import 'package:cli/cache/caches.dart';
import 'package:cli/cli.dart';
import 'package:cli/logger.dart';
import 'package:cli/nav/route.dart';
import 'package:cli/printing.dart';
import 'package:cli/trading.dart';

Future<void> command(FileSystem fs, List<String> args) async {
final marketPrices = MarketPrices.load(fs);
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/bin/list_nearby_buys_for.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:cli/behavior/deliver.dart';
import 'package:cli/cache/caches.dart';
import 'package:cli/cli.dart';
import 'package:cli/logger.dart';
import 'package:cli/nav/route.dart';
import 'package:cli/printing.dart';
import 'package:cli/trading.dart';

Future<void> command(FileSystem fs, List<String> args) async {
final marketPrices = MarketPrices.load(fs);
Expand Down
91 changes: 1 addition & 90 deletions packages/cli/lib/behavior/deliver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,102 +5,13 @@ import 'package:cli/behavior/trader.dart';
import 'package:cli/cache/caches.dart';
import 'package:cli/logger.dart';
import 'package:cli/nav/navigation.dart';
import 'package:cli/nav/route.dart';
import 'package:cli/net/actions.dart';
import 'package:cli/trading.dart';
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
// Go buy and deliver.
// Used for modules.

/// Calculated trip cost of going and buying something.
class CostedTrip {
/// Create a new costed trip.
CostedTrip({required this.route, required this.price});

/// The route to get there.
final RoutePlan route;

/// The historical price for the item at a given market.
final MarketPrice price;
}

/// Compute the cost of going to and buying from a specific MarketPrice record.
CostedTrip? costTrip(
Ship ship,
RoutePlanner planner,
MarketPrice price,
WaypointSymbol start,
WaypointSymbol end,
) {
final route = planner.planRoute(
start: start,
end: end,
fuelCapacity: ship.fuel.capacity,
shipSpeed: ship.engine.speed,
);
if (route == null) {
return null;
}
return CostedTrip(route: route, price: price);
}

/// Find the best market to buy a given item from.
/// expectedCreditsPerSecond is the time value of money (e.g. 7c/s)
/// used for evaluating the trade-off between "closest" vs. "cheapest".
CostedTrip? findBestMarketToBuy(
MarketPrices marketPrices,
RoutePlanner routePlanner,
Ship ship,
TradeSymbol tradeSymbol, {
required int expectedCreditsPerSecond,
}) {
final prices = marketPrices.pricesFor(tradeSymbol).toList();
if (prices.isEmpty) {
return null;
}
final start = ship.waypointSymbol;

// If there are a lot of prices we could cut down the search space by only
// looking at prices at or below median?
// final medianPrice = marketPrices.medianPurchasePrice(tradeSymbol)!;
// Find the closest 10 prices which are median or below.
// final medianOrBelow = prices.where((e) => e.purchasePrice <= medianPrice);

final costed = <CostedTrip>[];
for (final price in prices) {
final end = price.waypointSymbol;
final trip = costTrip(ship, routePlanner, price, start, end);
if (trip != null) {
costed.add(trip);
} else {
logger.warn('No route from $start to $end');
}
}

final sorted = costed.toList()
..sort((a, b) => a.route.duration.compareTo(b.route.duration));

final nearest = sorted.first;
if (sorted.length == 1) {
return nearest;
}

var best = nearest;
// Pick any one further that saves more than expectedCreditsPerSecond
for (final trip in sorted.sublist(1)) {
final priceDiff = trip.price.purchasePrice - nearest.price.purchasePrice;
final savings = -priceDiff;
final extraTime = trip.route.duration - nearest.route.duration;
final savingsPerSecond = savings / extraTime.inSeconds;
if (savingsPerSecond > expectedCreditsPerSecond) {
best = trip;
break;
}
}

return best;
}

/// A job to buy a given item.
@immutable
class BuyJob {
Expand Down
89 changes: 89 additions & 0 deletions packages/cli/lib/trading.dart
Original file line number Diff line number Diff line change
Expand Up @@ -597,3 +597,92 @@ Future<CostedDeal?> findDealFor(
filter: filter,
);
}

/// Calculated trip cost of going and buying something.
class CostedTrip {
/// Create a new costed trip.
CostedTrip({required this.route, required this.price});

/// The route to get there.
final RoutePlan route;

/// The historical price for the item at a given market.
final MarketPrice price;
}

/// Compute the cost of going to and buying from a specific MarketPrice record.
CostedTrip? costTrip(
Ship ship,
RoutePlanner planner,
MarketPrice price,
WaypointSymbol start,
WaypointSymbol end,
) {
final route = planner.planRoute(
start: start,
end: end,
fuelCapacity: ship.fuel.capacity,
shipSpeed: ship.engine.speed,
);
if (route == null) {
return null;
}
return CostedTrip(route: route, price: price);
}

/// Find the best market to buy a given item from.
/// expectedCreditsPerSecond is the time value of money (e.g. 7c/s)
/// used for evaluating the trade-off between "closest" vs. "cheapest".
CostedTrip? findBestMarketToBuy(
MarketPrices marketPrices,
RoutePlanner routePlanner,
Ship ship,
TradeSymbol tradeSymbol, {
required int expectedCreditsPerSecond,
}) {
final prices = marketPrices.pricesFor(tradeSymbol).toList();
if (prices.isEmpty) {
return null;
}
final start = ship.waypointSymbol;

// If there are a lot of prices we could cut down the search space by only
// looking at prices at or below median?
// final medianPrice = marketPrices.medianPurchasePrice(tradeSymbol)!;
// Find the closest 10 prices which are median or below.
// final medianOrBelow = prices.where((e) => e.purchasePrice <= medianPrice);

final costed = <CostedTrip>[];
for (final price in prices) {
final end = price.waypointSymbol;
final trip = costTrip(ship, routePlanner, price, start, end);
if (trip != null) {
costed.add(trip);
} else {
logger.warn('No route from $start to $end');
}
}

final sorted = costed.toList()
..sort((a, b) => a.route.duration.compareTo(b.route.duration));

final nearest = sorted.first;
if (sorted.length == 1) {
return nearest;
}

var best = nearest;
// Pick any one further that saves more than expectedCreditsPerSecond
for (final trip in sorted.sublist(1)) {
final priceDiff = trip.price.purchasePrice - nearest.price.purchasePrice;
final savings = -priceDiff;
final extraTime = trip.route.duration - nearest.route.duration;
final savingsPerSecond = savings / extraTime.inSeconds;
if (savingsPerSecond > expectedCreditsPerSecond) {
best = trip;
break;
}
}

return best;
}
15 changes: 0 additions & 15 deletions packages/cli/test/behavior/deliver_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,4 @@ void main() {
);
expect(waitUntil, isNull);
});

test('findBestMarketToBuy smoke test', () {
final ship = _MockShip();
final routePlanner = _MockRoutePlanner();
final marketPrices = _MockMarketPrices();
when(() => marketPrices.pricesFor(TradeSymbol.ALUMINUM)).thenReturn([]);

findBestMarketToBuy(
marketPrices,
routePlanner,
ship,
TradeSymbol.ALUMINUM,
expectedCreditsPerSecond: 7,
);
});
}
15 changes: 15 additions & 0 deletions packages/cli/test/trading_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -516,4 +516,19 @@ void main() {
expect(costedDeal.expectedUnits, 100);
expect(costedDeal.maxUnitsToBuy, 10);
});
test('findBestMarketToBuy smoke test', () {
final ship = _MockShip();
final routePlanner = _MockRoutePlanner();
final marketPrices = _MockMarketPrices();
when(() => marketPrices.pricesFor(TradeSymbol.ALUMINUM)).thenReturn([]);

final market = findBestMarketToBuy(
marketPrices,
routePlanner,
ship,
TradeSymbol.ALUMINUM,
expectedCreditsPerSecond: 7,
);
expect(market, isNull);
});
}

0 comments on commit 72b2e60

Please sign in to comment.