movers.py 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. from __future__ import annotations
  2. import collections
  3. import dataclasses
  4. import datetime
  5. import typing
  6. import cache
  7. def main() -> None:
  8. current_prices: typing.Sequence[RawPrice] = cache.get('https://refined-prun.github.io/refined-prices/all.json')
  9. old_prices = get_old_prices()
  10. movers: dict[str, list[Mover]] = collections.defaultdict(list)
  11. for current_price in current_prices:
  12. old_price = old_prices[current_price['FullTicker']]
  13. if (mover := analyze_raw_price(current_price, old_price)) is not None:
  14. movers[mover.ticker].append(mover)
  15. for ticker_movers in movers.values():
  16. ticker_movers.sort(reverse=True)
  17. top_movers = sorted(movers.values(), key=lambda m: m[0].score, reverse=True)
  18. for commodity in top_movers:
  19. print(f'{commodity[0].score:9,.0f}', ' '.join(f'{mover.ticker}.{mover.exchange_code}' for mover in commodity))
  20. def get_old_prices() -> typing.Mapping[str, RawPrice]:
  21. week_ago = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=14)
  22. commits = cache.get(f'https://api.github.com/repos/refined-prun/refined-prices/commits?until={week_ago.isoformat()}&per_page=1')
  23. return {p['FullTicker']: p
  24. for p in cache.get(f'https://raw.githubusercontent.com/refined-prun/refined-prices/{commits[0]["sha"]}/all.json')}
  25. def analyze_raw_price(current_price: RawPrice, old_price: RawPrice) -> Mover | None:
  26. if (traded := current_price['AverageTraded30D']) is None:
  27. return
  28. if (current_vwap7d := current_price['VWAP7D']) is None or (old_vwap7d := old_price['VWAP7D']) is None:
  29. return
  30. diff = abs(current_vwap7d - old_vwap7d)
  31. if diff / min(current_vwap7d, old_vwap7d) > 0.15:
  32. return Mover(current_price['ExchangeCode'], current_price['MaterialTicker'], diff * traded)
  33. class RawPrice(typing.TypedDict):
  34. FullTicker: str
  35. ExchangeCode: str
  36. MaterialTicker: str
  37. VWAP7D: float | None # volume-weighted average price over last 7 days
  38. AverageTraded30D: float | None # averaged daily traded volume over last 30 days
  39. @dataclasses.dataclass(eq=False, frozen=True, slots=True)
  40. class Mover:
  41. exchange_code: str
  42. ticker: str
  43. score: float
  44. def __lt__(self, other: Mover) -> bool:
  45. return self.score < other.score
  46. if __name__ == '__main__':
  47. main()