movers.py 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. from __future__ import annotations
  2. import collections
  3. import concurrent.futures
  4. import dataclasses
  5. import typing
  6. import cache
  7. def main() -> None:
  8. raw_prices: list[RawPrice] = cache.get('https://refined-prun.github.io/refined-prices/all.json')
  9. movers: dict[str, list[Mover]] = collections.defaultdict(list)
  10. with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
  11. futures: list[concurrent.futures.Future[Mover | None]] = []
  12. for price in raw_prices:
  13. futures.append(executor.submit(analyze_raw_price, price))
  14. for future in futures:
  15. if (mover := future.result()) is not None:
  16. movers[mover.ticker].append(mover)
  17. executor.shutdown()
  18. for ticker_movers in movers.values():
  19. ticker_movers.sort(reverse=True)
  20. top_movers = sorted(movers.values(), key=lambda m: m[0].score, reverse=True)
  21. for commodity in top_movers:
  22. print(f'{commodity[0].score:7.1f}', ' '.join(f'{mover.ticker}.{mover.exchange_code}' for mover in commodity))
  23. def analyze_raw_price(price: RawPrice) -> Mover | None:
  24. if (traded := price['AverageTraded7D']) is None:
  25. return
  26. if (vwap7d := price['VWAP7D']) is None or (vwap30d := price['VWAP30D']) is None:
  27. return
  28. if (bid := price['Bid']) is None or (ask := price['Ask']) is None:
  29. return
  30. scores = [
  31. (bid - vwap30d) / bid,
  32. (vwap30d - ask) / vwap30d,
  33. abs(vwap7d - vwap30d) / max(vwap7d, vwap30d),
  34. ]
  35. score = max(scores) * traded
  36. if score > 100:
  37. return Mover(price['ExchangeCode'], price['MaterialTicker'], score)
  38. class RawPrice(typing.TypedDict):
  39. ExchangeCode: str
  40. MaterialTicker: str
  41. VWAP7D: float | None # volume-weighted average price over last 7 days
  42. AverageTraded7D: float | None # averaged daily traded volume over last 7 days
  43. VWAP30D: float | None
  44. Bid: float | None
  45. Ask: float | None
  46. HighYesterday: float | None
  47. LowYesterday: float | None
  48. @dataclasses.dataclass(eq=False, frozen=True, slots=True)
  49. class Mover:
  50. exchange_code: str
  51. ticker: str
  52. score: float
  53. def __lt__(self, other: Mover) -> bool:
  54. return self.score < other.score
  55. if __name__ == '__main__':
  56. main()