buy.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. from __future__ import annotations
  2. import collections
  3. import concurrent.futures
  4. import dataclasses
  5. import sys
  6. import typing
  7. import cache
  8. from config import config
  9. import supply
  10. if typing.TYPE_CHECKING:
  11. import market
  12. def main() -> None:
  13. days = int(sys.argv[1])
  14. with concurrent.futures.ThreadPoolExecutor() as executor:
  15. futures = [
  16. executor.submit(get_raw_prices),
  17. executor.submit(get_total_buy, days), # what we need to buy
  18. executor.submit(supply.warehouse_inventory), # what we have
  19. executor.submit(get_planet_exports_and_ship_storage),
  20. executor.submit(get_bids), # what we already are bidding for
  21. ]
  22. raw_prices, buy, warehouse, exports, (bids, orders) = (f.result() for f in futures)
  23. executor.shutdown()
  24. # what's left to buy
  25. materials: list[Material] = []
  26. for mat, amount in buy.items():
  27. remaining = max(amount - bids[mat] - warehouse.get(mat, 0) - exports.get(mat, 0), 0)
  28. price = raw_prices[mat]
  29. if price['Bid'] is None or price['Ask'] is None:
  30. print(mat, 'has no bid/ask')
  31. continue
  32. spread = price['Ask'] - price['Bid']
  33. materials.append(Material(mat, amount=amount, bids=bids[mat], have=warehouse.get(mat, 0) + exports.get(mat, 0),
  34. spread=spread, savings=spread * remaining))
  35. materials.sort(reverse=True)
  36. print('mat want bids have buy savings')
  37. for m in materials:
  38. buy = max(m.amount - m.bids - m.have, 0)
  39. if m.bids == 0 and buy > 0:
  40. bids = f'\033[91m{m.bids:5}\033[0m'
  41. else:
  42. bids = str(m.bids).rjust(5)
  43. print(f'{m.ticker:4} {m.amount:>5} {bids} {m.have:>5} {buy:>5} {m.savings:8.0f}')
  44. # deposits of current bids
  45. orders.sort(key=lambda order: order['Limit'] * order['Amount'], reverse=True)
  46. print('\ncurrent bid deposits:')
  47. for order in orders:
  48. print(f"{order['MaterialTicker']:4} {order['Limit'] * order['Amount']:7,.0f}")
  49. def get_raw_prices() -> typing.Mapping[str, market.RawPrice]:
  50. return {p['MaterialTicker']: p
  51. for p in cache.get('https://refined-prun.github.io/refined-prices/all.json') if p['ExchangeCode'] == 'IC1'}
  52. def get_total_buy(days: int) -> typing.Mapping[str, int]:
  53. planets = [supply.Planet(fio_burn) for fio_burn in cache.get('https://rest.fnar.net/fioweb/burn/user/' + config.username,
  54. headers={'Authorization': config.fio_api_key})]
  55. buy: dict[str, int] = collections.defaultdict(int)
  56. for planet in planets:
  57. for mat, amount in planet.supply_for_days(days).items():
  58. buy[mat] += amount
  59. return buy
  60. def get_planet_exports_and_ship_storage() -> typing.Mapping[str, int]:
  61. '''materials in base storage that aren't being consumed and materials in ship storage'''
  62. avail = collections.defaultdict(int)
  63. fio_burn: typing.Sequence[supply.FIOBurn] = cache.get('https://rest.fnar.net/fioweb/burn/user/' + config.username,
  64. headers={'Authorization': config.fio_api_key})
  65. for burn in fio_burn:
  66. planet = supply.Planet(burn)
  67. for mat in planet.exporting:
  68. avail[mat] += planet.inventory.get(mat, 0)
  69. stores: typing.Sequence[market.Storage] = cache.get('https://rest.fnar.net/storage/' + config.username,
  70. headers={'Authorization': config.fio_api_key})
  71. for store in stores:
  72. if store['Type'] != 'SHIP_STORE':
  73. continue
  74. for item in store['StorageItems']:
  75. avail[item['MaterialTicker']] += item['MaterialAmount']
  76. return avail
  77. def get_bids() -> tuple[typing.Mapping[str, int], list[market.ExchangeOrder]]:
  78. orders: typing.Sequence[market.ExchangeOrder] = cache.get('https://rest.fnar.net/cxos/' + config.username,
  79. headers={'Authorization': config.fio_api_key})
  80. orders = [order for order in orders
  81. if order['OrderType'] == 'BUYING' and order['Status'] != 'FILLED' and order['ExchangeCode'] == 'IC1']
  82. bids = collections.defaultdict(int)
  83. for order in orders:
  84. bids[order['MaterialTicker']] += order['Amount']
  85. return bids, orders
  86. @dataclasses.dataclass(eq=False, slots=True)
  87. class Material:
  88. ticker: str
  89. amount: int
  90. bids: int
  91. have: int
  92. spread: float
  93. savings: float
  94. def __lt__(self, o: Material) -> bool:
  95. return self.savings< o.savings
  96. if __name__ == '__main__':
  97. main()