|
|
@@ -15,6 +15,7 @@ def main() -> None:
|
|
|
|
|
|
movers: dict[str, list[Mover]] = collections.defaultdict(list)
|
|
|
shortages: dict[str, list[Mover]] = collections.defaultdict(list)
|
|
|
+ traded_map: dict[str, int] = {}
|
|
|
for current_price in current_prices:
|
|
|
if current_price['ExchangeCode'].endswith('2'):
|
|
|
continue
|
|
|
@@ -25,6 +26,9 @@ def main() -> None:
|
|
|
if mover.days_supply_lost > 7 and mover.days_supply_remaining < 14:
|
|
|
shortages[mover.ticker].append(mover)
|
|
|
|
|
|
+ assert (traded := current_price['AverageTraded30D']) is not None
|
|
|
+ traded_map[current_price['FullTicker']] = int(traded)
|
|
|
+
|
|
|
for ticker_movers in movers.values():
|
|
|
ticker_movers.sort(reverse=True)
|
|
|
for ticker_shortages in shortages.values():
|
|
|
@@ -41,6 +45,30 @@ def main() -> None:
|
|
|
print(f'{-commodity[0].days_supply_lost:6.1f}d', f'{commodity[0].supply_consumption_rate:7.1f}/d',
|
|
|
' '.join(f'{mover.ticker}.{mover.exchange_code}' for mover in commodity))
|
|
|
|
|
|
+ print('\nthin supply:')
|
|
|
+ for ticker, ticker_shortages in shortages.items():
|
|
|
+ for commodity in ticker_shortages:
|
|
|
+ exchange_ticker = f'{commodity.ticker}.{commodity.exchange_code}'
|
|
|
+ asks: list[Order] = cache.get(f'https://rest.fnar.net/exchange/{exchange_ticker}')['SellingOrders']
|
|
|
+ if len(asks) == 0:
|
|
|
+ print(f'{exchange_ticker:3}: no asks')
|
|
|
+ continue
|
|
|
+ asks.sort(key=lambda o: o['ItemCost'], reverse=True)
|
|
|
+ current_ask = asks[-1]['ItemCost']
|
|
|
+ traded = traded_map[exchange_ticker]
|
|
|
+ remaining = traded // 2
|
|
|
+ expected_price = float('-infinity')
|
|
|
+ while remaining > 0:
|
|
|
+ if len(asks) == 0:
|
|
|
+ expected_price = float('infinity')
|
|
|
+ break
|
|
|
+ if asks[-1]['ItemCount'] > remaining:
|
|
|
+ expected_price = asks[-1]['ItemCost']
|
|
|
+ break
|
|
|
+ remaining -= asks.pop()['ItemCount']
|
|
|
+ if (expected_price - current_ask) / current_ask > 0.15:
|
|
|
+ print(f'{exchange_ticker:>7}: {traded:7,} {current_ask:10,.2f} → {expected_price:10,.2f}')
|
|
|
+
|
|
|
def get_old_prices() -> typing.Mapping[str, RawPrice]:
|
|
|
week_ago = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=OLD_PRICE_DAYS)
|
|
|
commits = cache.get(f'https://api.github.com/repos/refined-prun/refined-prices/commits?until={week_ago.isoformat()}&per_page=1')
|
|
|
@@ -68,6 +96,10 @@ class RawPrice(typing.TypedDict):
|
|
|
AverageTraded30D: float | None # averaged daily traded volume over last 30 days
|
|
|
Supply: int
|
|
|
|
|
|
+class Order(typing.TypedDict):
|
|
|
+ ItemCount: int
|
|
|
+ ItemCost: float
|
|
|
+
|
|
|
@dataclasses.dataclass(eq=False, frozen=True, slots=True)
|
|
|
class Mover:
|
|
|
exchange_code: str
|