2 次代碼提交 fe2001933e ... cfbb99cf8d

作者 SHA1 備註 提交日期
  raylu cfbb99cf8d include existing inventory and bids 2 周之前
  raylu c3e4a2be90 buy 2 周之前
共有 3 個文件被更改,包括 102 次插入18 次删除
  1. 81 0
      buy.py
  2. 8 1
      market.py
  3. 13 17
      supply.py

+ 81 - 0
buy.py

@@ -0,0 +1,81 @@
+from __future__ import annotations
+
+import collections
+import dataclasses
+import typing
+
+import cache
+from config import config
+import supply
+
+if typing.TYPE_CHECKING:
+	import market
+
+def main() -> None:
+	raw_prices: typing.Mapping[str, market.RawPrice] = {p['MaterialTicker']: p
+			for p in cache.get('https://refined-prun.github.io/refined-prices/all.json') if p['ExchangeCode'] == 'IC1'}
+
+	# what we need to buy
+	planets = [supply.Planet(fio_burn) for fio_burn in cache.get('https://rest.fnar.net/fioweb/burn/user/' + config.username,
+			headers={'Authorization': config.fio_api_key})]
+	buy: dict[str, int] = collections.defaultdict(int)
+	for planet in planets:
+		for mat, amount in planet.buy_for_target(7).items():
+			buy[mat] += amount
+
+	# what we have
+	warehouses: typing.Sequence[market.Warehouse] = cache.get('https://rest.fnar.net/sites/warehouses/' + config.username,
+			headers={'Authorization': config.fio_api_key})
+	for warehouse in warehouses:
+		if warehouse['LocationNaturalId'] == 'HRT':
+			storage: market.Storage = cache.get(f'https://rest.fnar.net/storage/{config.username}/{warehouse["StoreId"]}',
+				headers={'Authorization': config.fio_api_key})
+			assert storage['Type'] == 'WAREHOUSE_STORE'
+			warehouse = {item['MaterialTicker']: item['MaterialAmount'] for item in storage['StorageItems']}
+			break
+	else:
+		raise Exception("couldn't find HRT warehouse")
+
+	# what we already are bidding for
+	orders: typing.Sequence[market.ExchangeOrder] = cache.get('https://rest.fnar.net/cxos/' + config.username,
+			headers={'Authorization': config.fio_api_key})
+	bids = collections.defaultdict(int)
+	for order in orders:
+		if order['OrderType'] == 'SELLING' or order['Status'] == 'FILLED' or order['ExchangeCode'] != 'IC1':
+			continue
+		bids[order['MaterialTicker']] += order['Amount']
+
+	# what's left to buy
+	materials: list[Material] = []
+	for mat, amount in buy.items():
+		remaining = amount - bids[mat] - warehouse.get(mat, 0)
+		if remaining <= 0:
+			continue
+		price = raw_prices[mat]
+		if price['Bid'] is None or price['Ask'] is None:
+			print(mat, 'has no bid/ask')
+			continue
+		spread = price['Ask'] - price['Bid']
+		materials.append(Material(mat, amount=amount, bids=bids[mat], warehouse=warehouse.get(mat, 0),
+				spread=spread, savings=spread * remaining))
+	materials.sort(reverse=True)
+
+	print('mat   want  bids  have   buy  savings')
+	for m in materials:
+		print(f'{m.ticker:4} {m.amount:>5} {m.bids:>5} {m.warehouse:>5} {m.amount - m.bids - m.warehouse:>5} {m.savings:8.0f}')
+
+@dataclasses.dataclass(eq=False, slots=True)
+class Material:
+	ticker: str
+	amount: int
+	bids: int
+	warehouse: int
+	spread: float
+	savings: float
+
+	def __lt__(self, o: Material) -> bool:
+		return self.savings< o.savings
+
+
+if __name__ == '__main__':
+	main()

+ 8 - 1
market.py

@@ -128,6 +128,7 @@ class ExchangeOrder(typing.TypedDict):
 	ExchangeCode: str
 	OrderType: typing.Literal['SELLING'] | typing.Literal['BUYING']
 	Status: typing.Literal['FILLED'] | typing.Literal['PARTIALLY_FILLED'] | typing.Literal['FILLED']
+	Amount: int
 	Limit: float
 
 class ExchangeSummary(typing.TypedDict):
@@ -141,9 +142,15 @@ class Warehouse(typing.TypedDict):
 	LocationNaturalId: str
 
 class Storage(typing.TypedDict):
-	StorageItems: typing.Sequence
+	Name: str
+	StorageItems: typing.Sequence[StorageItem]
 	WeightLoad: float
 	VolumeLoad: float
+	Type: typing.Literal['STORE'] | typing.Literal['WAREHOUSE_STORE'] | typing.Literal['FTL_FUEL_STORE'] | typing.Literal['STL_FUEL_STORE'] |  typing.Literal['SHIP_STORE']
+
+class StorageItem(typing.TypedDict):
+	MaterialTicker: str
+	MaterialAmount: int
 
 class RawPrice(typing.TypedDict):
 	FullTicker: str

+ 13 - 17
supply.py

@@ -26,7 +26,7 @@ def main() -> None:
 
 	planets = [Planet(fio_burn) for fio_burn in get_fio_burn(args.planets)]
 	if args.include_ship:
-		stores: typing.Sequence[Storage] = cache.get('https://rest.fnar.net/storage/' + config.username,
+		stores: typing.Sequence[market.Storage] = cache.get('https://rest.fnar.net/storage/' + config.username,
 				headers={'Authorization': config.fio_api_key})
 		for ship in args.include_ship:
 			ship_name, planet_name = ship.casefold().split('=')
@@ -64,7 +64,8 @@ def main() -> None:
 		buys: dict[str, dict[str, int]] = {}
 		iteration_weight = iteration_volume = 0
 		for planet in planets:
-			buy, weight_used, volume_used = planet.buy_for_target(materials, target_days)
+			buy = planet.buy_for_target(target_days)
+			weight_used, volume_used = shipping_used(materials, buy)
 			iteration_weight += weight_used
 			iteration_volume += volume_used
 			if iteration_weight > args.weight or iteration_volume > args.volume:
@@ -141,6 +142,13 @@ def get_fio_burn(planet_names: typing.Sequence[str]) -> typing.Iterator[FIOBurn]
 		else:
 			raise ValueError(name + ' not found')
 
+def shipping_used(materials: dict[str, Material], buy: dict[str, int]) -> tuple[float, float]:
+	weight = volume = 0
+	for ticker, amount in buy.items():
+		weight += amount * materials[ticker]['Weight']
+		volume += amount * materials[ticker]['Volume']
+	return weight, volume
+
 def cyan(text: str) -> str:
 	return '\033[36m' + text + '\033[0m'
 
@@ -150,7 +158,7 @@ class FIOBurn(typing.TypedDict):
 	Error: typing.Any
 	OrderConsumption: list[Amount]
 	WorkforceConsumption: list[Amount]
-	Inventory: list[Inventory]
+	Inventory: list[market.StorageItem]
 	OrderProduction: list[Amount]
 
 @dataclasses.dataclass(init=False, eq=False, slots=True)
@@ -173,8 +181,7 @@ class Planet:
 			c['net_consumption'] = net
 			self.net_consumption.append(c)
 
-	def buy_for_target(self, materials: dict[str, Material], target_days: float) -> tuple[dict[str, int], float, float]:
-		weight_used = volume_used = 0
+	def buy_for_target(self, target_days: float) -> dict[str, int]:
 		buy: dict[str, int] = {}
 		for consumption in self.net_consumption:
 			ticker = consumption['MaterialTicker']
@@ -183,24 +190,13 @@ class Planet:
 			days = avail / daily_consumption
 			if days < target_days:
 				buy[ticker] = math.ceil((target_days - days) * daily_consumption)
-				weight_used += buy[ticker] * materials[ticker]['Weight']
-				volume_used += buy[ticker] * materials[ticker]['Volume']
-		return buy, weight_used, volume_used
+		return buy
 
 class Amount(typing.TypedDict):
 	MaterialTicker: str
 	DailyAmount: float
 	net_consumption: float
 
-class Inventory(typing.TypedDict):
-	MaterialTicker: str
-	MaterialAmount: int
-
-class Storage(typing.TypedDict):
-	Name: str
-	StorageItems: list[Inventory]
-	Type: typing.Literal['STORE'] | typing.Literal['WAREHOUSE_STORE'] | typing.Literal['FTL_FUEL_STORE'] | typing.Literal['STL_FUEL_STORE'] |  typing.Literal['SHIP_STORE']
-
 class Material(typing.TypedDict):
 	Ticker: str
 	Weight: float