kuhl-haus-mdp 0.1.7__tar.gz → 0.1.8__tar.gz

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.
Files changed (46) hide show
  1. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/PKG-INFO +1 -1
  2. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/pyproject.toml +1 -1
  3. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/analyzers/top_stocks.py +17 -6
  4. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/components/market_data_cache.py +0 -71
  5. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/helpers/utils.py +2 -1
  6. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/models/market_data_cache_keys.py +4 -4
  7. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/models/top_stocks_cache_item.py +3 -0
  8. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/components/test_market_data_cache.py +29 -27
  9. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/models/test_top_stocks_cache_item.py +9 -9
  10. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/LICENSE.txt +0 -0
  11. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/README.md +0 -0
  12. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/__init__.py +0 -0
  13. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/analyzers/__init__.py +0 -0
  14. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/analyzers/analyzer.py +0 -0
  15. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/analyzers/massive_data_analyzer.py +0 -0
  16. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/components/__init__.py +0 -0
  17. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/components/market_data_scanner.py +0 -0
  18. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/components/widget_data_service.py +0 -0
  19. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/helpers/__init__.py +0 -0
  20. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/helpers/process_manager.py +0 -0
  21. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/helpers/queue_name_resolver.py +0 -0
  22. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/integ/__init__.py +0 -0
  23. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/integ/massive_data_listener.py +0 -0
  24. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/integ/massive_data_processor.py +0 -0
  25. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/integ/massive_data_queues.py +0 -0
  26. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/integ/web_socket_message_serde.py +0 -0
  27. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/models/__init__.py +0 -0
  28. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/models/market_data_analyzer_result.py +0 -0
  29. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/models/market_data_cache_ttl.py +0 -0
  30. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/models/market_data_pubsub_keys.py +0 -0
  31. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/models/market_data_scanner_names.py +0 -0
  32. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/src/kuhl_haus/mdp/models/massive_data_queue.py +0 -0
  33. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/__init__.py +0 -0
  34. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/analyzers/__init__.py +0 -0
  35. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/analyzers/test_massive_data_analyzer.py +0 -0
  36. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/analyzers/test_top_stocks_rehydrate.py +0 -0
  37. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/components/__init__.py +0 -0
  38. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/components/test_market_data_scanner.py +0 -0
  39. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/components/test_widget_data_service.py +0 -0
  40. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/helpers/__init__.py +0 -0
  41. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/helpers/test_process_manager.py +0 -0
  42. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/helpers/test_queue_name_resolver.py +0 -0
  43. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/helpers/test_utils.py +0 -0
  44. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/integ/__init__.py +0 -0
  45. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/integ/test_web_socket_message_serde.py +0 -0
  46. {kuhl_haus_mdp-0.1.7 → kuhl_haus_mdp-0.1.8}/tests/models/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kuhl-haus-mdp
3
- Version: 0.1.7
3
+ Version: 0.1.8
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)
@@ -30,7 +30,7 @@ dependencies = [
30
30
  "uvicorn[standard]",
31
31
  "websockets",
32
32
  ]
33
- version = "0.1.7"
33
+ version = "0.1.8"
34
34
 
35
35
  [project.license]
36
36
  file = "LICENSE.txt"
@@ -126,6 +126,7 @@ class TopStocksAnalyzer(Analyzer):
126
126
  prev_day_close = cached_data["prev_day_close"]
127
127
  prev_day_volume = cached_data["prev_day_volume"]
128
128
  prev_day_vwap = cached_data["prev_day_vwap"]
129
+ free_float = cached_data["free_float"]
129
130
  else:
130
131
  # Get snapshot for previous day's data
131
132
  retry_count = 0
@@ -140,12 +141,10 @@ class TopStocksAnalyzer(Analyzer):
140
141
  prev_day_volume = snapshot.prev_day.volume
141
142
  prev_day_vwap = snapshot.prev_day.vwap
142
143
  break
143
- except BadResponse as e:
144
- self.logger.error(f"Error getting snapshot for {event.symbol}: {repr(e)}", exc_info=e, stack_info=True)
144
+ except Exception:
145
145
  retry_count += 1
146
146
  if retry_count == max_tries and prev_day_close == 0:
147
147
  self.logger.error(f"Failed to get snapshot for {event.symbol} after {max_tries} tries.")
148
- return
149
148
 
150
149
  # Get average volume
151
150
  retry_count = 0
@@ -155,12 +154,23 @@ class TopStocksAnalyzer(Analyzer):
155
154
  try:
156
155
  avg_volume = await self.cache.get_avg_volume(event.symbol)
157
156
  break
158
- except (BadResponse, ZeroDivisionError) as e:
159
- self.logger.error(f"Error getting average volume for {event.symbol}: {repr(e)}", exc_info=e, stack_info=True)
157
+ except Exception:
160
158
  retry_count += 1
161
159
  if retry_count == max_tries and avg_volume == 0:
162
160
  self.logger.error(f"Failed to get average volume for {event.symbol} after {max_tries} tries.")
163
- return
161
+
162
+ # Get free float - this uses an experimental API
163
+ retry_count = 0
164
+ max_tries = 2
165
+ free_float = 0
166
+ while retry_count < max_tries:
167
+ try:
168
+ free_float = await self.cache.get_free_float(event.symbol)
169
+ break
170
+ except Exception:
171
+ retry_count += 1
172
+ if retry_count == max_tries and free_float == 0:
173
+ self.logger.error(f"Failed to get free float for {event.symbol} after {max_tries} tries.")
164
174
 
165
175
  # Calculate relative volume
166
176
  if avg_volume == 0:
@@ -196,6 +206,7 @@ class TopStocksAnalyzer(Analyzer):
196
206
  self.cache_item.symbol_data_cache[event.symbol] = {
197
207
  "symbol": event.symbol,
198
208
  "volume": event.volume,
209
+ "free_float": free_float,
199
210
  "accumulated_volume": event.accumulated_volume,
200
211
  "relative_volume": relative_volume,
201
212
  "official_open_price": event.official_open_price,
@@ -57,77 +57,6 @@ class MarketDataCache:
57
57
  ticker=ticker
58
58
  )
59
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
60
  data = ticker_snapshot_to_dict(snapshot)
132
61
  await self.cache_data(
133
62
  data=data,
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  import os
3
+ from typing import Dict, Any
3
4
 
4
5
  from massive.rest.models import TickerSnapshot
5
6
 
@@ -38,7 +39,7 @@ def get_massive_api_key():
38
39
  return api_key
39
40
 
40
41
 
41
- def ticker_snapshot_to_dict(snapshot: TickerSnapshot) -> dict:
42
+ def ticker_snapshot_to_dict(snapshot: TickerSnapshot) -> Dict[str, Any]:
42
43
  """
43
44
  Convert a TickerSnapshot instance into a JSON-serializable dictionary.
44
45
 
@@ -15,10 +15,10 @@ class MarketDataCacheKeys(Enum):
15
15
  UNKNOWN = 'unknown'
16
16
 
17
17
  # MARKET DATA CACHE
18
- DAILY_AGGREGATES = 'aggregate:daily'
19
- TICKER_SNAPSHOTS = 'snapshots'
20
- TICKER_AVG_VOLUME = 'avg_volume'
21
- TICKER_FREE_FLOAT = 'free_float'
18
+ DAILY_AGGREGATES = 'mdc:aggregate:daily'
19
+ TICKER_SNAPSHOTS = 'mdc:snapshots'
20
+ TICKER_AVG_VOLUME = 'mdc:avg_volume'
21
+ TICKER_FREE_FLOAT = 'mdc:free_float'
22
22
 
23
23
  # MARKET DATA PROCESSOR CACHE
24
24
  TOP_TRADES_SCANNER = f'cache:{MarketDataScannerNames.TOP_TRADES.value}'
@@ -45,6 +45,7 @@ class TopStocksCacheItem:
45
45
  ret.append({
46
46
  "symbol": ticker,
47
47
  "volume": self.symbol_data_cache[ticker]["volume"],
48
+ "free_float": self.symbol_data_cache[ticker]["free_float"],
48
49
  "accumulated_volume": self.symbol_data_cache[ticker]["accumulated_volume"],
49
50
  "relative_volume": self.symbol_data_cache[ticker]["relative_volume"],
50
51
  "official_open_price": self.symbol_data_cache[ticker]["official_open_price"],
@@ -81,6 +82,7 @@ class TopStocksCacheItem:
81
82
  ret.append({
82
83
  "symbol": ticker,
83
84
  "volume": self.symbol_data_cache[ticker]["volume"],
85
+ "free_float": self.symbol_data_cache[ticker]["free_float"],
84
86
  "accumulated_volume": self.symbol_data_cache[ticker]["accumulated_volume"],
85
87
  "relative_volume": self.symbol_data_cache[ticker]["relative_volume"],
86
88
  "official_open_price": self.symbol_data_cache[ticker]["official_open_price"],
@@ -117,6 +119,7 @@ class TopStocksCacheItem:
117
119
  ret.append({
118
120
  "symbol": ticker,
119
121
  "volume": self.symbol_data_cache[ticker]["volume"],
122
+ "free_float": self.symbol_data_cache[ticker]["free_float"],
120
123
  "accumulated_volume": self.symbol_data_cache[ticker]["accumulated_volume"],
121
124
  "relative_volume": self.symbol_data_cache[ticker]["relative_volume"],
122
125
  "official_open_price": self.symbol_data_cache[ticker]["official_open_price"],
@@ -5,6 +5,8 @@ import pytest
5
5
  from kuhl_haus.mdp.components.market_data_cache import MarketDataCache
6
6
  from massive.rest.models import TickerSnapshot
7
7
 
8
+ from kuhl_haus.mdp.models.market_data_cache_keys import MarketDataCacheKeys
9
+
8
10
 
9
11
  @pytest.fixture
10
12
  def mock_massive_api_key():
@@ -93,7 +95,7 @@ async def test_get_ticker_snapshot_with_cache_hit_expect_ticker_snapshot_returne
93
95
  mock_redis_client = AsyncMock()
94
96
  mock_rest_client = MagicMock()
95
97
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
96
- mock_cache_key = "snapshots:TEST"
98
+ mock_cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:TEST"
97
99
  mock_cached_value = mock_data_dict
98
100
  mock_redis_client.get.return_value = json.dumps(mock_cached_value)
99
101
  mock_snapshot.return_value = TickerSnapshot(**mock_cached_value)
@@ -115,7 +117,7 @@ async def test_get_ticker_snapshot_without_cache_hit_expect_ticker_snapshot_retu
115
117
  mock_redis_client = AsyncMock()
116
118
  mock_rest_client = MagicMock()
117
119
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
118
- mock_cache_key = "snapshots:TEST"
120
+ mock_cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:TEST"
119
121
  mock_snapshot_instance = MagicMock(spec=TickerSnapshot)
120
122
  mock_snapshot_instance.ticker = "TEST"
121
123
  mock_snapshot_instance.todays_change = 5.0
@@ -145,7 +147,7 @@ async def test_get_ticker_snapshot_with_invalid_cache_data_expect_exception(mock
145
147
  mock_redis_client = AsyncMock()
146
148
  mock_rest_client = MagicMock()
147
149
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
148
- mock_cache_key = "snapshots:TEST"
150
+ mock_cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:TEST"
149
151
  mock_redis_client.get.return_value = json.dumps({"invalid": "data"})
150
152
  mock_from_dict.side_effect = ValueError("Invalid cache data")
151
153
 
@@ -163,7 +165,7 @@ async def test_get_ticker_snapshot_with_invalid_cache_data_expect_exception():
163
165
  mock_redis_client = AsyncMock()
164
166
  mock_rest_client = MagicMock()
165
167
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
166
- mock_cache_key = "snapshots:TEST"
168
+ mock_cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:TEST"
167
169
  mock_redis_client.get.return_value = json.dumps({"invalid": "data"})
168
170
 
169
171
  # Act & Assert
@@ -180,7 +182,7 @@ async def test_get_avg_volume_with_cache_hit_expect_cached_value_returned():
180
182
  mock_redis_client = AsyncMock()
181
183
  mock_rest_client = MagicMock()
182
184
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
183
- mock_cache_key = "avg_volume:TEST"
185
+ mock_cache_key = f"{MarketDataCacheKeys.TICKER_AVG_VOLUME.value}:TEST"
184
186
  mock_cached_value = 1500000
185
187
  mock_redis_client.get.return_value = json.dumps(mock_cached_value)
186
188
 
@@ -199,7 +201,7 @@ async def test_get_avg_volume_without_cache_hit_expect_avg_volume_returned():
199
201
  mock_redis_client = AsyncMock()
200
202
  mock_rest_client = MagicMock()
201
203
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
202
- mock_cache_key = "avg_volume:TEST"
204
+ mock_cache_key = f"{MarketDataCacheKeys.TICKER_AVG_VOLUME.value}:TEST"
203
205
  mock_avg_volume = 2500000
204
206
 
205
207
  # Create mock FinancialRatio object
@@ -225,7 +227,7 @@ async def test_get_avg_volume_without_cache_hit_expect_avg_volume_returned():
225
227
  # mock_redis_client = AsyncMock()
226
228
  # mock_rest_client = MagicMock()
227
229
  # sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
228
- # mock_cache_key = "avg_volume:TEST"
230
+ # mock_cache_key = f"{MarketDataCacheKeys.TICKER_AVG_VOLUME.value}:TEST"
229
231
  #
230
232
  # mock_redis_client.get.return_value = None
231
233
  # mock_rest_client.list_financials_ratios.return_value = iter([])
@@ -245,7 +247,7 @@ async def test_get_avg_volume_without_cache_hit_expect_avg_volume_returned():
245
247
  # mock_redis_client = AsyncMock()
246
248
  # mock_rest_client = MagicMock()
247
249
  # sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
248
- # mock_cache_key = "avg_volume:TEST"
250
+ # mock_cache_key = f"{MarketDataCacheKeys.TICKER_AVG_VOLUME.value}:TEST"
249
251
  #
250
252
  # # Create multiple mock FinancialRatio objects
251
253
  # mock_financial_ratio_1 = MagicMock()
@@ -271,7 +273,7 @@ async def test_get_avg_volume_caches_with_correct_ttl():
271
273
  mock_redis_client = AsyncMock()
272
274
  mock_rest_client = MagicMock()
273
275
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
274
- mock_cache_key = "avg_volume:TEST"
276
+ mock_cache_key = f"{MarketDataCacheKeys.TICKER_AVG_VOLUME.value}:TEST"
275
277
  mock_avg_volume = 3500000
276
278
 
277
279
  # Create mock FinancialRatio object
@@ -300,7 +302,7 @@ async def test_get_avg_volume_caches_with_correct_ttl():
300
302
  mock_redis_client = AsyncMock()
301
303
  mock_rest_client = MagicMock()
302
304
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
303
- mock_cache_key = "avg_volume:TEST"
305
+ mock_cache_key = f"{MarketDataCacheKeys.TICKER_AVG_VOLUME.value}:TEST"
304
306
  mock_avg_volume = 3500000
305
307
 
306
308
  # Create mock FinancialRatio object
@@ -329,12 +331,12 @@ async def test_get_free_float_with_cache_hit_expect_cached_value_returned():
329
331
  mock_redis_client = AsyncMock()
330
332
  mock_rest_client = MagicMock()
331
333
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
332
- mock_cache_key = "free_float:TSLA"
334
+ mock_cache_key = f"{MarketDataCacheKeys.TICKER_FREE_FLOAT.value}:TEST"
333
335
  mock_cached_value = 2643494955
334
336
  mock_redis_client.get.return_value = json.dumps(mock_cached_value)
335
337
 
336
338
  # Act
337
- result = await sut.get_free_float("TSLA")
339
+ result = await sut.get_free_float("TEST")
338
340
 
339
341
  # Assert
340
342
  mock_redis_client.get.assert_awaited_once_with(mock_cache_key)
@@ -347,7 +349,7 @@ async def test_get_free_float_without_cache_hit_expect_free_float_returned():
347
349
  mock_redis_client = AsyncMock()
348
350
  mock_rest_client = MagicMock()
349
351
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_api_key")
350
- mock_cache_key = "free_float:TSLA"
352
+ mock_cache_key = f"{MarketDataCacheKeys.TICKER_FREE_FLOAT.value}:TEST"
351
353
  mock_free_float = 2643494955
352
354
 
353
355
  # Mock API response
@@ -358,7 +360,7 @@ async def test_get_free_float_without_cache_hit_expect_free_float_returned():
358
360
  "effective_date": "2025-11-14",
359
361
  "free_float": mock_free_float,
360
362
  "free_float_percent": 79.5,
361
- "ticker": "TSLA"
363
+ "ticker": "TEST"
362
364
  }
363
365
  ],
364
366
  "status": "OK"
@@ -382,14 +384,14 @@ async def test_get_free_float_without_cache_hit_expect_free_float_returned():
382
384
  sut.http_session = mock_session
383
385
 
384
386
  # Act
385
- result = await sut.get_free_float("TSLA")
387
+ result = await sut.get_free_float("TEST")
386
388
 
387
389
  # Assert
388
390
  mock_redis_client.get.assert_awaited_once_with(mock_cache_key)
389
391
  mock_session.get.assert_called_once()
390
392
  call_args = mock_session.get.call_args
391
393
  assert call_args[0][0] == "https://api.massive.com/stocks/vX/float"
392
- assert call_args[1]["params"]["ticker"] == "TSLA"
394
+ assert call_args[1]["params"]["ticker"] == "TEST"
393
395
  assert call_args[1]["params"]["apiKey"] == "test_api_key"
394
396
  mock_response.json.assert_awaited_once()
395
397
  mock_redis_client.setex.assert_awaited_once()
@@ -402,7 +404,7 @@ async def test_get_free_float_caches_with_correct_ttl():
402
404
  mock_redis_client = AsyncMock()
403
405
  mock_rest_client = MagicMock()
404
406
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
405
- mock_cache_key = "free_float:TSLA"
407
+ mock_cache_key = f"{MarketDataCacheKeys.TICKER_FREE_FLOAT.value}:TEST"
406
408
  mock_free_float = 2643494955
407
409
 
408
410
  # Mock API response
@@ -413,7 +415,7 @@ async def test_get_free_float_caches_with_correct_ttl():
413
415
  "effective_date": "2025-11-14",
414
416
  "free_float": mock_free_float,
415
417
  "free_float_percent": 79.5,
416
- "ticker": "TSLA"
418
+ "ticker": "TEST"
417
419
  }
418
420
  ],
419
421
  "status": "OK"
@@ -436,7 +438,7 @@ async def test_get_free_float_caches_with_correct_ttl():
436
438
  sut.http_session = mock_session
437
439
 
438
440
  # Act
439
- result = await sut.get_free_float("TSLA")
441
+ result = await sut.get_free_float("TEST")
440
442
 
441
443
  # Assert
442
444
  mock_redis_client.get.assert_awaited_once_with(mock_cache_key)
@@ -482,8 +484,8 @@ async def test_get_free_float_with_empty_results_expect_exception():
482
484
  sut.http_session = mock_session
483
485
 
484
486
  # Act & Assert
485
- with pytest.raises(Exception, match="No free float data returned for TSLA"):
486
- await sut.get_free_float("TSLA")
487
+ with pytest.raises(Exception, match="No free float data returned for TEST"):
488
+ await sut.get_free_float("TEST")
487
489
 
488
490
  mock_redis_client.setex.assert_not_awaited()
489
491
 
@@ -523,8 +525,8 @@ async def test_get_free_float_with_invalid_status_expect_exception():
523
525
  sut.http_session = mock_session
524
526
 
525
527
  # Act & Assert
526
- with pytest.raises(Exception, match="Invalid response from Massive API for TSLA"):
527
- await sut.get_free_float("TSLA")
528
+ with pytest.raises(Exception, match="Invalid response from Massive API for TEST"):
529
+ await sut.get_free_float("TEST")
528
530
 
529
531
  mock_redis_client.setex.assert_not_awaited()
530
532
 
@@ -559,7 +561,7 @@ async def test_get_free_float_with_client_error_expect_exception():
559
561
 
560
562
  # Act & Assert
561
563
  with pytest.raises(aiohttp.ClientError, match="Connection timeout"):
562
- await sut.get_free_float("TSLA")
564
+ await sut.get_free_float("TEST")
563
565
 
564
566
  mock_redis_client.setex.assert_not_awaited()
565
567
 
@@ -592,7 +594,7 @@ async def test_get_free_float_with_http_error_expect_exception():
592
594
 
593
595
  # Act & Assert
594
596
  with pytest.raises(Exception, match="HTTP 500 Error"):
595
- await sut.get_free_float("TSLA")
597
+ await sut.get_free_float("TEST")
596
598
 
597
599
  mock_redis_client.setex.assert_not_awaited()
598
600
 
@@ -674,8 +676,8 @@ async def test_publish_data_expect_publish_called():
674
676
  mock_rest_client = MagicMock()
675
677
  sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
676
678
 
677
- test_data = {"symbol": "TSLA", "price": 250.50, "volume": 1000000}
678
- test_publish_key = "market:updates:TSLA"
679
+ test_data = {"symbol": "TEST", "price": 250.50, "volume": 1000000}
680
+ test_publish_key = "market:updates:TEST"
679
681
 
680
682
  # Act
681
683
  await sut.publish_data(data=test_data, publish_key=test_publish_key)
@@ -35,17 +35,17 @@ class TestTopStocksCacheItem(unittest.TestCase):
35
35
  """Test the top_volume method with a limit."""
36
36
  self.cache_item.top_volume_map = {"AAPL": 1200, "GOOG": 600, "AMZN": 500}
37
37
  self.cache_item.symbol_data_cache = {
38
- "AAPL": {"volume": 1000, "accumulated_volume": 1200, "relative_volume": 1.2, "official_open_price": 150,
38
+ "AAPL": {"volume": 1000, "free_float": 14831485766, "accumulated_volume": 1200, "relative_volume": 1.2, "official_open_price": 150,
39
39
  "vwap": 155, "open": 145, "close": 152, "high": 160, "low": 142, "aggregate_vwap": 156,
40
40
  "average_size": 50, "avg_volume": 1000, "prev_day_close": 148, "prev_day_volume": 900,
41
41
  "prev_day_vwap": 154, "change": 4, "pct_change": 2.7, "change_since_open": 7,
42
42
  "pct_change_since_open": 4.8, "start_timestamp": 100000, "end_timestamp": 110000},
43
- "AMZN": {"volume": 300, "accumulated_volume": 500, "relative_volume": 1.2, "official_open_price": 3300,
43
+ "AMZN": {"volume": 300, "free_float": 9698671061, "accumulated_volume": 500, "relative_volume": 1.2, "official_open_price": 3300,
44
44
  "vwap": 3500, "open": 3250, "close": 3520, "high": 3600, "low": 3200, "aggregate_vwap": 3450,
45
45
  "average_size": 85, "avg_volume": 4000, "prev_day_close": 3200, "prev_day_volume": 3900,
46
46
  "prev_day_vwap": 3400, "change": 200, "pct_change": 10.0, "change_since_open": 270,
47
47
  "pct_change_since_open": 8.3, "start_timestamp": 300000, "end_timestamp": 310000},
48
- "GOOG": {"volume": 500, "accumulated_volume": 600, "relative_volume": 1.0, "official_open_price": 2500,
48
+ "GOOG": {"volume": 500, "free_float": 5029591400, "accumulated_volume": 600, "relative_volume": 1.0, "official_open_price": 2500,
49
49
  "vwap": 2550, "open": 2450, "close": 2520, "high": 2600, "low": 2400, "aggregate_vwap": 2560,
50
50
  "average_size": 65, "avg_volume": 2000, "prev_day_close": 2510, "prev_day_volume": 1900,
51
51
  "prev_day_vwap": 2530, "change": 10, "pct_change": 0.4, "change_since_open": 70,
@@ -60,17 +60,17 @@ class TestTopStocksCacheItem(unittest.TestCase):
60
60
  """Test the top_gappers method with a limit."""
61
61
  self.cache_item.top_gappers_map = {"AAPL": 5.0, "GOOG": -3.0, "AMZN": 10.0}
62
62
  self.cache_item.symbol_data_cache = {
63
- "AAPL": {"volume": 1000, "accumulated_volume": 1200, "relative_volume": 1.5, "official_open_price": 150,
63
+ "AAPL": {"volume": 1000, "free_float": 14831485766, "accumulated_volume": 1200, "relative_volume": 1.5, "official_open_price": 150,
64
64
  "vwap": 155, "open": 145, "close": 152, "high": 160, "low": 142, "aggregate_vwap": 156,
65
65
  "average_size": 50, "avg_volume": 1000, "prev_day_close": 148, "prev_day_volume": 900,
66
66
  "prev_day_vwap": 154, "change": 4, "pct_change": 5.0, "change_since_open": 7,
67
67
  "pct_change_since_open": 2.5, "start_timestamp": 100000, "end_timestamp": 110000},
68
- "AMZN": {"volume": 300, "accumulated_volume": 500, "relative_volume": 1.2, "official_open_price": 3300,
68
+ "AMZN": {"volume": 300, "free_float": 9698671061, "accumulated_volume": 500, "relative_volume": 1.2, "official_open_price": 3300,
69
69
  "vwap": 3500, "open": 3250, "close": 3520, "high": 3600, "low": 3200, "aggregate_vwap": 3450,
70
70
  "average_size": 85, "avg_volume": 4000, "prev_day_close": 3200, "prev_day_volume": 3900,
71
71
  "prev_day_vwap": 3400, "change": 200, "pct_change": 10.0, "change_since_open": 270,
72
72
  "pct_change_since_open": 8.3, "start_timestamp": 300000, "end_timestamp": 310000},
73
- "GOOG": {"volume": 500, "accumulated_volume": 600, "relative_volume": 1.0, "official_open_price": 2500,
73
+ "GOOG": {"volume": 500, "free_float": 5029591400, "accumulated_volume": 600, "relative_volume": 1.0, "official_open_price": 2500,
74
74
  "vwap": 2550, "open": 2450, "close": 2520, "high": 2600, "low": 2400, "aggregate_vwap": 2560,
75
75
  "average_size": 65, "avg_volume": 2000, "prev_day_close": 2510, "prev_day_volume": 1900,
76
76
  "prev_day_vwap": 2530, "change": 10, "pct_change": 0.4, "change_since_open": 70,
@@ -84,17 +84,17 @@ class TestTopStocksCacheItem(unittest.TestCase):
84
84
  """Test the top_gainers method with a limit."""
85
85
  self.cache_item.top_gainers_map = {"AAPL": 2.5, "GOOG": -0.5, "AMZN": 8.3}
86
86
  self.cache_item.symbol_data_cache = {
87
- "AAPL": {"volume": 500, "accumulated_volume": 800, "relative_volume": 1.6, "official_open_price": 140,
87
+ "AAPL": {"volume": 500, "free_float": 14831485766, "accumulated_volume": 800, "relative_volume": 1.6, "official_open_price": 140,
88
88
  "vwap": 145, "open": 130, "close": 150, "high": 155, "low": 128, "aggregate_vwap": 150,
89
89
  "average_size": 45, "avg_volume": 900, "prev_day_close": 142, "prev_day_volume": 850,
90
90
  "prev_day_vwap": 145, "change": 8, "pct_change": 2.5, "change_since_open": 20,
91
91
  "pct_change_since_open": 15.4, "start_timestamp": 150000, "end_timestamp": 160000},
92
- "AMZN": {"volume": 800, "accumulated_volume": 1200, "relative_volume": 1.4, "official_open_price": 3200,
92
+ "AMZN": {"volume": 800, "free_float": 9698671061, "accumulated_volume": 1200, "relative_volume": 1.4, "official_open_price": 3200,
93
93
  "vwap": 3300, "open": 3150, "close": 3400, "high": 3450, "low": 3100, "aggregate_vwap": 3350,
94
94
  "average_size": 70, "avg_volume": 3900, "prev_day_close": 3250, "prev_day_volume": 3800,
95
95
  "prev_day_vwap": 3300, "change": 150, "pct_change": 4.6, "change_since_open": 250,
96
96
  "pct_change_since_open": 8.3, "start_timestamp": 170000, "end_timestamp": 180000},
97
- "GOOG": {"volume": 500, "accumulated_volume": 600, "relative_volume": 1.0, "official_open_price": 2500,
97
+ "GOOG": {"volume": 500, "free_float": 5029591400, "accumulated_volume": 600, "relative_volume": 1.0, "official_open_price": 2500,
98
98
  "vwap": 2550, "open": 2450, "close": 2520, "high": 2600, "low": 2400, "aggregate_vwap": 2560,
99
99
  "average_size": 65, "avg_volume": 2000, "prev_day_close": 2510, "prev_day_volume": 1900,
100
100
  "prev_day_vwap": 2530, "change": 10, "pct_change": 0.4, "change_since_open": 70,
File without changes
File without changes