Skip to content

Commit

Permalink
feat: teach the trader that prices can move
Browse files Browse the repository at this point in the history
Previously we assuemed the buy/sell price was fixed
which is wrong (and really wrong for low trade volumes)
This does not yet fix the low trade
volume problem, but it does make
it possible to fix it.
  • Loading branch information
eseidel committed Jul 30, 2023
1 parent ebbc56c commit 5130b6d
Show file tree
Hide file tree
Showing 8 changed files with 437 additions and 211 deletions.
2 changes: 1 addition & 1 deletion packages/cli/lib/behavior/central_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@ class CentralCommand {
good.tradeSymbolObject,
);
if (unitsNeeded > 0) {
yield SellOpp(
yield SellOpp.fromContract(
marketSymbol: good.destination,
tradeSymbol: good.tradeSymbolObject,
contractId: contract.id,
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/lib/behavior/trader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ Future<DateTime?> _handleAtSourceWithDeal(
final good = currentMarket.marketTradeGood(dealTradeSymbol)!;

final maxPerUnitPrice = costedDeal.maxPurchaseUnitPrice;
final nextExpectedPrice = costedDeal.predictNextPurchasePrice;

// Could this get confused by having other cargo in our hold?
final units = unitsToPurchase(good, ship, costedDeal.maxUnitsToBuy);
Expand Down Expand Up @@ -153,7 +154,7 @@ Future<DateTime?> _handleAtSourceWithDeal(
ship,
'Purchased ${transaction.quantity} ${transaction.tradeSymbol} '
'@ ${transaction.perUnitPrice} (expected '
'${costedDeal.deal.purchasePrice}) = '
'${creditsString(nextExpectedPrice)}) = '
'${creditsString(transaction.creditChange)}',
);
}
Expand Down
42 changes: 42 additions & 0 deletions packages/cli/lib/cache/market_prices.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,48 @@ class MarketPrice {
/// The timestamp of the price record.
final DateTime timestamp;

/// Predict the price of buying the Nth unit of this good.
/// Unit is a 0-based index of the unit being purchased.
int predictPurchasePriceForUnit(int unit) {
// TODO(eseidel): movementPerBatch is wrong.
// For large trade volumes, it's probably 0, for small trade volumes
// it can be very large.
const movementPerBatch = 1;
final batchCount = unit ~/ tradeVolume;
final movement = movementPerBatch * batchCount;
return purchasePrice + movement;
}

/// Predict the price of buying the Nth unit of this good.
/// Unit is a 0-based index of the unit being purchased.
int predictSellPriceForUnit(int unit) {
// TODO(eseidel): movementPerBatch is wrong.
// For large trade volumes, it's probably 0, for small trade volumes
// it can be very large.
const movementPerBatch = -1;
final batchCount = unit ~/ tradeVolume;
final movement = movementPerBatch * batchCount;
return sellPrice + movement;
}

/// Predict the total price of buying [units] of this good.
int totalPurchasePriceFor(int units) {
var totalPrice = 0;
for (var i = 0; i < units; i++) {
totalPrice += predictPurchasePriceForUnit(i);
}
return totalPrice;
}

/// Predict the total price of buying [units] of this good.
int totalSellPriceFor(int units) {
var totalPrice = 0;
for (var i = 0; i < units; i++) {
totalPrice += predictSellPriceForUnit(i);
}
return totalPrice;
}

@override
bool operator ==(Object other) =>
identical(this, other) ||
Expand Down
56 changes: 26 additions & 30 deletions packages/cli/lib/market_scan.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,48 @@ import 'package:meta/meta.dart';
@immutable
class BuyOpp {
/// Create a new BuyOpp.
const BuyOpp({
required this.marketSymbol,
required this.tradeSymbol,
required this.price,
});
const BuyOpp(this.marketPrice);

/// State of the market where this buy opportunity was found.
final MarketPrice marketPrice;

/// The symbol of the market where the good can be purchased.
final WaypointSymbol marketSymbol;
WaypointSymbol get marketSymbol => marketPrice.waypointSymbol;

/// The symbol of the good offered for purchase.
final TradeSymbol tradeSymbol;
TradeSymbol get tradeSymbol => marketPrice.tradeSymbol;

/// The price of the good.
final int price;
int get price => marketPrice.purchasePrice;
}

/// A potential sale opportunity. Only public for testing.
@immutable
class SellOpp {
/// Create a new SellOpp.
const SellOpp({
/// Create a new SellOpp from a MarketPrice.
SellOpp.fromMarketPrice(MarketPrice this.marketPrice)
: marketSymbol = marketPrice.waypointSymbol,
tradeSymbol = marketPrice.tradeSymbol,
price = marketPrice.sellPrice,
contractId = null,
maxUnits = null;

/// Create a new SellOpp from a contract.
const SellOpp.fromContract({
required this.marketSymbol,
required this.tradeSymbol,
required this.price,
this.contractId,
this.maxUnits,
});
required this.contractId,
required this.maxUnits,
}) : marketPrice = null;

/// State of the market where this sell opportunity was found.
final MarketPrice? marketPrice;

/// The symbol of the market where the good can be sold.
final WaypointSymbol marketSymbol;

/// The symbol of the good.
/// The symbol of the good offered for sold.
final TradeSymbol tradeSymbol;

/// The price of the good.
Expand Down Expand Up @@ -85,22 +95,8 @@ class _MarketScanBuilder {

/// Record potential deals from the given historical market price.
void visitMarketPrice(MarketPrice marketPrice) {
final marketSymbol = marketPrice.waypointSymbol;
final tradeSymbol = marketPrice.tradeSymbol;
_addBuyOpp(
BuyOpp(
marketSymbol: marketSymbol,
tradeSymbol: tradeSymbol,
price: marketPrice.purchasePrice,
),
);
_addSellOpp(
SellOpp(
marketSymbol: marketSymbol,
tradeSymbol: tradeSymbol,
price: marketPrice.sellPrice,
),
);
_addBuyOpp(BuyOpp(marketPrice));
_addSellOpp(SellOpp.fromMarketPrice(marketPrice));
}
}

Expand Down
Loading

0 comments on commit 5130b6d

Please sign in to comment.