kuhl-haus-mdp 0.1.6__py3-none-any.whl → 0.1.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,8 @@
1
1
  import json
2
2
  import logging
3
3
  from typing import Any, Optional, Iterator, List
4
+ from datetime import datetime, timezone, timedelta
5
+ from zoneinfo import ZoneInfo
4
6
 
5
7
  import aiohttp
6
8
  import redis.asyncio as aioredis
@@ -8,8 +10,10 @@ from massive.rest import RESTClient
8
10
  from massive.rest.models import (
9
11
  TickerSnapshot,
10
12
  FinancialRatio,
13
+ Agg,
11
14
  )
12
15
 
16
+ from kuhl_haus.mdp.helpers.utils import ticker_snapshot_to_dict
13
17
  from kuhl_haus.mdp.models.market_data_cache_keys import MarketDataCacheKeys
14
18
  from kuhl_haus.mdp.models.market_data_cache_ttl import MarketDataCacheTTL
15
19
 
@@ -34,49 +38,151 @@ class MarketDataCache:
34
38
  await self.redis_client.setex(cache_key, cache_ttl, json.dumps(data))
35
39
  else:
36
40
  await self.redis_client.set(cache_key, json.dumps(data))
37
- self.logger.debug(f"Cached data for {cache_key}")
41
+ self.logger.info(f"Cached data for {cache_key}")
38
42
 
39
43
  async def publish_data(self, data: Any, publish_key: str = None):
40
44
  await self.redis_client.publish(publish_key, json.dumps(data))
41
- self.logger.debug(f"Published data for {publish_key}")
45
+ self.logger.info(f"Published data for {publish_key}")
42
46
 
43
47
  async def get_ticker_snapshot(self, ticker: str) -> TickerSnapshot:
44
- self.logger.debug(f"Getting snapshot for {ticker}")
48
+ self.logger.info(f"Getting snapshot for {ticker}")
45
49
  cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:{ticker}"
46
50
  result = await self.get_cache(cache_key=cache_key)
47
51
  if result:
48
- snapshot = TickerSnapshot.from_dict(**result)
52
+ self.logger.info(f"Returning cached snapshot for {ticker}")
53
+ snapshot = TickerSnapshot(**result)
49
54
  else:
50
55
  snapshot: TickerSnapshot = self.rest_client.get_snapshot_ticker(
51
56
  market_type="stocks",
52
57
  ticker=ticker
53
58
  )
54
- self.logger.debug(f"Snapshot result: {snapshot}")
59
+ self.logger.info(f"Snapshot result: {snapshot}")
60
+ # data = {
61
+ # "day": {
62
+ # "open": snapshot.day.open,
63
+ # "high": snapshot.day.high,
64
+ # "low": snapshot.day.low,
65
+ # "close": snapshot.day.close,
66
+ # "volume": snapshot.day.volume,
67
+ # "vwap": snapshot.day.vwap,
68
+ # "timestamp": snapshot.day.timestamp,
69
+ # "transactions": snapshot.day.transactions,
70
+ # "otc": snapshot.day.otc,
71
+ # },
72
+ # "last_quote": {
73
+ # "ticker": snapshot.last_quote.ticker,
74
+ # "trf_timestamp": snapshot.last_quote.trf_timestamp,
75
+ # "sequence_number": snapshot.last_quote.sequence_number,
76
+ # "sip_timestamp": snapshot.last_quote.sip_timestamp,
77
+ # "participant_timestamp": snapshot.last_quote.participant_timestamp,
78
+ # "ask_price": snapshot.last_quote.ask_price,
79
+ # "ask_size": snapshot.last_quote.ask_size,
80
+ # "ask_exchange": snapshot.last_quote.ask_exchange,
81
+ # "conditions": snapshot.last_quote.conditions,
82
+ # "indicators": snapshot.last_quote.indicators,
83
+ # "bid_price": snapshot.last_quote.bid_price,
84
+ # "bid_size": snapshot.last_quote.bid_size,
85
+ # "bid_exchange": snapshot.last_quote.bid_exchange,
86
+ # "tape": snapshot.last_quote.tape,
87
+ # },
88
+ # "last_trade": {
89
+ # "ticker": snapshot.last_trade.ticker,
90
+ # "trf_timestamp": snapshot.last_trade.trf_timestamp,
91
+ # "sequence_number": snapshot.last_trade.sequence_number,
92
+ # "sip_timestamp": snapshot.last_trade.sip_timestamp,
93
+ # "participant_timestamp": snapshot.last_trade.participant_timestamp,
94
+ # "conditions": snapshot.last_trade.conditions,
95
+ # "correction": snapshot.last_trade.correction,
96
+ # "id": snapshot.last_trade.id,
97
+ # "price": snapshot.last_trade.price,
98
+ # "trf_id": snapshot.last_trade.trf_id,
99
+ # "size": snapshot.last_trade.size,
100
+ # "exchange": snapshot.last_trade.exchange,
101
+ # "tape": snapshot.last_trade.tape,
102
+ # },
103
+ # "min": {
104
+ # "accumulated_volume": snapshot.min.accumulated_volume,
105
+ # "open": snapshot.min.open,
106
+ # "high": snapshot.min.high,
107
+ # "low": snapshot.min.low,
108
+ # "close": snapshot.min.close,
109
+ # "volume": snapshot.min.volume,
110
+ # "vwap": snapshot.min.vwap,
111
+ # "otc": snapshot.min.otc,
112
+ # "timestamp": snapshot.min.timestamp,
113
+ # "transactions": snapshot.min.transactions,
114
+ # },
115
+ # "prev_day": {
116
+ # "open": snapshot.prev_day.open,
117
+ # "high": snapshot.prev_day.high,
118
+ # "low": snapshot.prev_day.low,
119
+ # "close": snapshot.prev_day.close,
120
+ # "volume": snapshot.prev_day.volume,
121
+ # "vwap": snapshot.prev_day.vwap,
122
+ # "timestamp": snapshot.prev_day.timestamp,
123
+ # "transactions": snapshot.prev_day.transactions,
124
+ # "otc": snapshot.prev_day.otc,
125
+ # },
126
+ # "ticker": snapshot.ticker,
127
+ # "todaysChange": snapshot.todays_change,
128
+ # "todaysChangePerc": snapshot.todays_change_percent,
129
+ # "updated": snapshot.updated,
130
+ # }
131
+ data = ticker_snapshot_to_dict(snapshot)
55
132
  await self.cache_data(
56
- data=snapshot,
133
+ data=data,
57
134
  cache_key=cache_key,
58
135
  cache_ttl=MarketDataCacheTTL.EIGHT_HOURS.value
59
136
  )
60
137
  return snapshot
61
138
 
62
139
  async def get_avg_volume(self, ticker: str):
63
- self.logger.debug(f"Getting average volume for {ticker}")
140
+ self.logger.info(f"Getting average volume for {ticker}")
64
141
  cache_key = f"{MarketDataCacheKeys.TICKER_AVG_VOLUME.value}:{ticker}"
65
142
  avg_volume = await self.get_cache(cache_key=cache_key)
66
143
  if avg_volume:
67
- self.logger.debug(f"Returning cached value for {ticker}: {avg_volume}")
144
+ self.logger.info(f"Returning cached value for {ticker}: {avg_volume}")
68
145
  return avg_volume
69
146
 
147
+ # Experimental version - unreliable
70
148
  results: Iterator[FinancialRatio] = self.rest_client.list_financials_ratios(ticker=ticker)
71
149
  ratios: List[FinancialRatio] = []
72
150
  for financial_ratio in results:
73
151
  ratios.append(financial_ratio)
152
+
153
+ # If there is only one financial ratio, use it's average volume.
154
+ # Otherwise, calculate average volume from 30 trading sessions.'
74
155
  if len(ratios) == 1:
75
156
  avg_volume = ratios[0].average_volume
76
157
  else:
77
- raise Exception(f"Unexpected number of financial ratios for {ticker}: {len(ratios)}")
158
+ # Get date string in YYYY-MM-DD format
159
+ end_date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
160
+ # Get date from 30 trading sessions ago in YYYY-MM-DD format
161
+ start_date = (datetime.now(timezone.utc) - timedelta(days=42)).strftime("%Y-%m-%d")
162
+
163
+ result: Iterator[Agg] = self.rest_client.list_aggs(
164
+ ticker=ticker,
165
+ multiplier=1,
166
+ timespan="day",
167
+ from_=start_date,
168
+ to=end_date,
169
+ adjusted=True,
170
+ sort="desc"
171
+ )
172
+ self.logger.info(f"average volume result: {result}")
173
+
174
+ total_volume = 0
175
+ max_periods = 30
176
+ periods_calculated = 0
177
+ for agg in result:
178
+ if periods_calculated < max_periods:
179
+ total_volume += agg.volume
180
+ periods_calculated += 1
181
+ else:
182
+ break
183
+ avg_volume = total_volume / periods_calculated
78
184
 
79
- self.logger.debug(f"average volume {ticker}: {avg_volume}")
185
+ self.logger.info(f"average volume {ticker}: {avg_volume}")
80
186
  await self.cache_data(
81
187
  data=avg_volume,
82
188
  cache_key=cache_key,
@@ -85,11 +191,11 @@ class MarketDataCache:
85
191
  return avg_volume
86
192
 
87
193
  async def get_free_float(self, ticker: str):
88
- self.logger.debug(f"Getting free float for {ticker}")
194
+ self.logger.info(f"Getting free float for {ticker}")
89
195
  cache_key = f"{MarketDataCacheKeys.TICKER_FREE_FLOAT.value}:{ticker}"
90
196
  free_float = await self.get_cache(cache_key=cache_key)
91
197
  if free_float:
92
- self.logger.debug(f"Returning cached value for {ticker}: {free_float}")
198
+ self.logger.info(f"Returning cached value for {ticker}: {free_float}")
93
199
  return free_float
94
200
 
95
201
  # NOTE: This endpoint is experimental and the interface may change.
@@ -123,7 +229,7 @@ class MarketDataCache:
123
229
  self.logger.error(f"Error fetching free float for {ticker}: {e}")
124
230
  raise
125
231
 
126
- self.logger.debug(f"free float {ticker}: {free_float}")
232
+ self.logger.info(f"free float {ticker}: {free_float}")
127
233
  await self.cache_data(
128
234
  data=free_float,
129
235
  cache_key=cache_key,
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import os
3
3
 
4
+ from massive.rest.models import TickerSnapshot
4
5
 
5
6
  logging.basicConfig(
6
7
  level=logging.INFO,
@@ -35,3 +36,101 @@ def get_massive_api_key():
35
36
  raise ValueError("MASSIVE_API_KEY environment variable not set")
36
37
  logger.info("Done.")
37
38
  return api_key
39
+
40
+
41
+ def ticker_snapshot_to_dict(snapshot: TickerSnapshot) -> dict:
42
+ """
43
+ Convert a TickerSnapshot instance into a JSON-serializable dictionary.
44
+
45
+ Args:
46
+ snapshot: TickerSnapshot instance to convert
47
+
48
+ Returns:
49
+ Dictionary with keys matching the from_dict format (camelCase)
50
+ """
51
+ data = {
52
+ "ticker": snapshot.ticker,
53
+ "todays_change": snapshot.todays_change,
54
+ "todays_change_perc": snapshot.todays_change_percent,
55
+ "updated": snapshot.updated,
56
+ }
57
+
58
+ if snapshot.day is not None:
59
+ data["day"] = {
60
+ "open": snapshot.day.open,
61
+ "high": snapshot.day.high,
62
+ "low": snapshot.day.low,
63
+ "close": snapshot.day.close,
64
+ "volume": snapshot.day.volume,
65
+ "vwap": snapshot.day.vwap,
66
+ "timestamp": snapshot.day.timestamp,
67
+ "transactions": snapshot.day.transactions,
68
+ "otc": snapshot.day.otc,
69
+ }
70
+
71
+ if snapshot.last_quote is not None:
72
+ data["last_quote"] = {
73
+ "ticker": snapshot.last_quote.ticker,
74
+ "trf_timestamp": snapshot.last_quote.trf_timestamp,
75
+ "sequence_number": snapshot.last_quote.sequence_number,
76
+ "sip_timestamp": snapshot.last_quote.sip_timestamp,
77
+ "participant_timestamp": snapshot.last_quote.participant_timestamp,
78
+ "ask_price": snapshot.last_quote.ask_price,
79
+ "ask_size": snapshot.last_quote.ask_size,
80
+ "ask_exchange": snapshot.last_quote.ask_exchange,
81
+ "conditions": snapshot.last_quote.conditions,
82
+ "indicators": snapshot.last_quote.indicators,
83
+ "bid_price": snapshot.last_quote.bid_price,
84
+ "bid_size": snapshot.last_quote.bid_size,
85
+ "bid_exchange": snapshot.last_quote.bid_exchange,
86
+ "tape": snapshot.last_quote.tape,
87
+ }
88
+
89
+ if snapshot.last_trade is not None:
90
+ data["last_trade"] = {
91
+ "ticker": snapshot.last_trade.ticker,
92
+ "trf_timestamp": snapshot.last_trade.trf_timestamp,
93
+ "sequence_number": snapshot.last_trade.sequence_number,
94
+ "sip_timestamp": snapshot.last_trade.sip_timestamp,
95
+ "participant_timestamp": snapshot.last_trade.participant_timestamp,
96
+ "conditions": snapshot.last_trade.conditions,
97
+ "correction": snapshot.last_trade.correction,
98
+ "id": snapshot.last_trade.id,
99
+ "price": snapshot.last_trade.price,
100
+ "trf_id": snapshot.last_trade.trf_id,
101
+ "size": snapshot.last_trade.size,
102
+ "exchange": snapshot.last_trade.exchange,
103
+ "tape": snapshot.last_trade.tape,
104
+ }
105
+
106
+ if snapshot.min is not None:
107
+ data["min"] = {
108
+ "accumulated_volume": snapshot.min.accumulated_volume,
109
+ "open": snapshot.min.open,
110
+ "high": snapshot.min.high,
111
+ "low": snapshot.min.low,
112
+ "close": snapshot.min.close,
113
+ "volume": snapshot.min.volume,
114
+ "vwap": snapshot.min.vwap,
115
+ "otc": snapshot.min.otc,
116
+ "timestamp": snapshot.min.timestamp,
117
+ "transactions": snapshot.min.transactions,
118
+ }
119
+
120
+ if snapshot.prev_day is not None:
121
+ data["prev_day"] = {
122
+ "open": snapshot.prev_day.open,
123
+ "high": snapshot.prev_day.high,
124
+ "low": snapshot.prev_day.low,
125
+ "close": snapshot.prev_day.close,
126
+ "volume": snapshot.prev_day.volume,
127
+ "vwap": snapshot.prev_day.vwap,
128
+ "timestamp": snapshot.prev_day.timestamp,
129
+ "transactions": snapshot.prev_day.transactions,
130
+ "otc": snapshot.prev_day.otc,
131
+ }
132
+
133
+ if snapshot.fair_market_value is not None:
134
+ data["fmv"] = snapshot.fair_market_value
135
+
136
+ return data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kuhl-haus-mdp
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Market data processing pipeline for stock market scanner
5
5
  Author-Email: Tom Pounders <git@oldschool.engineer>
6
6
  License: The MIT License (MIT)
@@ -4,13 +4,13 @@ kuhl_haus/mdp/analyzers/analyzer.py,sha256=rIU1lcHwP2IBai0QLt0y-4ySg_ibWsutNU8JU
4
4
  kuhl_haus/mdp/analyzers/massive_data_analyzer.py,sha256=WSb7T8X4u2ue7Du7sf_fqxjgjEbR6ThllSNT1CncIM0,3866
5
5
  kuhl_haus/mdp/analyzers/top_stocks.py,sha256=GvNSa7yWZZ7WUgpaV2t1nVOxWA2R2qpISy16x2RGaQ8,9463
6
6
  kuhl_haus/mdp/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- kuhl_haus/mdp/components/market_data_cache.py,sha256=EmRDlh_GTKyYDvAbbAPQrE8n91JKNFKH8myAM4UTPLM,5835
7
+ kuhl_haus/mdp/components/market_data_cache.py,sha256=qc19TN05efgdpqobcF9rlNL8D8ZFDVYfKN-flTaZLks,11086
8
8
  kuhl_haus/mdp/components/market_data_scanner.py,sha256=45MgprFlq03MvmIRYXENsrc7UlTcBE_hIsPyOvNs1zc,9745
9
9
  kuhl_haus/mdp/components/widget_data_service.py,sha256=ikygD9NRpidcXBEqft5Q11rHy_eUOwKGyOLEezo-Dd4,7439
10
10
  kuhl_haus/mdp/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  kuhl_haus/mdp/helpers/process_manager.py,sha256=Is3Jx8nlBWvywQ1acdsdaSJTAG0olKskpPvrRB4VMDE,9024
12
12
  kuhl_haus/mdp/helpers/queue_name_resolver.py,sha256=l_zfRLxrjR9uwRCV2VDO4vPWLK_lj5KVG2p4Lh8xWiw,770
13
- kuhl_haus/mdp/helpers/utils.py,sha256=9JEpl2yr2LghOLrJUDxi-4dtDK3DZ1wBTZ1uxBJsFbQ,1309
13
+ kuhl_haus/mdp/helpers/utils.py,sha256=i6ILSa884jp7ijBsP_wBT3yHsUa91xSQEFkDhzqe1aQ,5164
14
14
  kuhl_haus/mdp/integ/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  kuhl_haus/mdp/integ/massive_data_listener.py,sha256=fPEYc6zZzHzFFjbP3zFInajKtEGInj8UQKKo3nKQEwQ,5098
16
16
  kuhl_haus/mdp/integ/massive_data_processor.py,sha256=H1WlbGtuSF45n7qLTLleuNlG-OlIXz4llJ7q3XRSS-s,8605
@@ -24,8 +24,8 @@ kuhl_haus/mdp/models/market_data_pubsub_keys.py,sha256=PEIPXK9jBehJB7G4pqoSuQZcf
24
24
  kuhl_haus/mdp/models/market_data_scanner_names.py,sha256=BYn1C0rYgGF1Sq583BkHADKUu-28ytNZQ-XgptuCH-Y,260
25
25
  kuhl_haus/mdp/models/massive_data_queue.py,sha256=MfYBcjVc4Fi61DWIvvhhWLUOiLmRpE9egtW-2KH6FTE,188
26
26
  kuhl_haus/mdp/models/top_stocks_cache_item.py,sha256=4vwwPTMkRRf1ct6iFInJnLSbBadM-tRk-zhqdD_ITE0,7676
27
- kuhl_haus_mdp-0.1.6.dist-info/METADATA,sha256=ecQ-j_82U-8JfxrpBMpJovbruVY2VSitRS-BgzG_cb8,8898
28
- kuhl_haus_mdp-0.1.6.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
29
- kuhl_haus_mdp-0.1.6.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
30
- kuhl_haus_mdp-0.1.6.dist-info/licenses/LICENSE.txt,sha256=DRkJftAJcMqoTkQ_Y6-HtKj3nm4pZah_p8XBZiYnw-c,1079
31
- kuhl_haus_mdp-0.1.6.dist-info/RECORD,,
27
+ kuhl_haus_mdp-0.1.7.dist-info/METADATA,sha256=YbJ2dQ8MBJrdtgCtDtfuWYEtX7FZDCF-gilSTjxDTmw,8898
28
+ kuhl_haus_mdp-0.1.7.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
29
+ kuhl_haus_mdp-0.1.7.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
30
+ kuhl_haus_mdp-0.1.7.dist-info/licenses/LICENSE.txt,sha256=DRkJftAJcMqoTkQ_Y6-HtKj3nm4pZah_p8XBZiYnw-c,1079
31
+ kuhl_haus_mdp-0.1.7.dist-info/RECORD,,