kuhl-haus-mdp 0.1.9__tar.gz → 0.1.11__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.
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/PKG-INFO +1 -1
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/pyproject.toml +1 -1
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/analyzers/massive_data_analyzer.py +8 -8
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/analyzers/top_stocks.py +16 -15
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/market_data_cache.py +27 -3
- kuhl_haus_mdp-0.1.11/src/kuhl_haus/mdp/models/constants.py +24 -0
- kuhl_haus_mdp-0.1.11/src/kuhl_haus/mdp/models/market_data_cache_ttl.py +29 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/analyzers/test_massive_data_analyzer.py +18 -18
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/components/test_market_data_cache.py +108 -6
- kuhl_haus_mdp-0.1.9/src/kuhl_haus/mdp/models/market_data_cache_ttl.py +0 -20
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/LICENSE.txt +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/README.md +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/analyzers/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/analyzers/analyzer.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/market_data_scanner.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/widget_data_service.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/helpers/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/helpers/process_manager.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/helpers/queue_name_resolver.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/helpers/utils.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/massive_data_listener.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/massive_data_processor.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/massive_data_queues.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/web_socket_message_serde.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_analyzer_result.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_cache_keys.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_pubsub_keys.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_scanner_names.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/massive_data_queue.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/top_stocks_cache_item.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/analyzers/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/analyzers/test_top_stocks_rehydrate.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/components/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/components/test_market_data_scanner.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/components/test_widget_data_service.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/helpers/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/helpers/test_process_manager.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/helpers/test_queue_name_resolver.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/helpers/test_utils.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/integ/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/integ/test_web_socket_message_serde.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/models/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/tests/models/test_top_stocks_cache_item.py +0 -0
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/analyzers/massive_data_analyzer.py
RENAMED
|
@@ -50,7 +50,7 @@ class MassiveDataAnalyzer:
|
|
|
50
50
|
return [MarketDataAnalyzerResult(
|
|
51
51
|
data=data,
|
|
52
52
|
cache_key=f"{MarketDataCacheKeys.HALTS.value}:{symbol}",
|
|
53
|
-
cache_ttl=MarketDataCacheTTL.
|
|
53
|
+
cache_ttl=MarketDataCacheTTL.HALTS.value,
|
|
54
54
|
publish_key=f"{MarketDataCacheKeys.HALTS.value}:{symbol}",
|
|
55
55
|
)]
|
|
56
56
|
|
|
@@ -58,8 +58,8 @@ class MassiveDataAnalyzer:
|
|
|
58
58
|
def handle_equity_agg_event(data: dict, symbol: str) -> Optional[List[MarketDataAnalyzerResult]]:
|
|
59
59
|
return [MarketDataAnalyzerResult(
|
|
60
60
|
data=data,
|
|
61
|
-
cache_key=f"{MarketDataCacheKeys.AGGREGATE.value}:{symbol}",
|
|
62
|
-
cache_ttl=MarketDataCacheTTL.
|
|
61
|
+
# cache_key=f"{MarketDataCacheKeys.AGGREGATE.value}:{symbol}",
|
|
62
|
+
# cache_ttl=MarketDataCacheTTL.AGGREGATE.value,
|
|
63
63
|
publish_key=f"{MarketDataCacheKeys.AGGREGATE.value}:{symbol}",
|
|
64
64
|
)]
|
|
65
65
|
|
|
@@ -67,8 +67,8 @@ class MassiveDataAnalyzer:
|
|
|
67
67
|
def handle_equity_trade_event(data: dict, symbol: str) -> Optional[List[MarketDataAnalyzerResult]]:
|
|
68
68
|
return [MarketDataAnalyzerResult(
|
|
69
69
|
data=data,
|
|
70
|
-
cache_key=f"{MarketDataCacheKeys.TRADES.value}:{symbol}",
|
|
71
|
-
cache_ttl=MarketDataCacheTTL.
|
|
70
|
+
# cache_key=f"{MarketDataCacheKeys.TRADES.value}:{symbol}",
|
|
71
|
+
# cache_ttl=MarketDataCacheTTL.TRADES.value,
|
|
72
72
|
publish_key=f"{MarketDataCacheKeys.TRADES.value}:{symbol}",
|
|
73
73
|
)]
|
|
74
74
|
|
|
@@ -76,8 +76,8 @@ class MassiveDataAnalyzer:
|
|
|
76
76
|
def handle_equity_quote_event(data: dict, symbol: str) -> Optional[List[MarketDataAnalyzerResult]]:
|
|
77
77
|
return [MarketDataAnalyzerResult(
|
|
78
78
|
data=data,
|
|
79
|
-
cache_key=f"{MarketDataCacheKeys.QUOTES.value}:{symbol}",
|
|
80
|
-
cache_ttl=MarketDataCacheTTL.
|
|
79
|
+
# cache_key=f"{MarketDataCacheKeys.QUOTES.value}:{symbol}",
|
|
80
|
+
# cache_ttl=MarketDataCacheTTL.QUOTES.value,
|
|
81
81
|
publish_key=f"{MarketDataCacheKeys.QUOTES.value}:{symbol}",
|
|
82
82
|
)]
|
|
83
83
|
|
|
@@ -88,6 +88,6 @@ class MassiveDataAnalyzer:
|
|
|
88
88
|
return [MarketDataAnalyzerResult(
|
|
89
89
|
data=data,
|
|
90
90
|
cache_key=cache_key,
|
|
91
|
-
cache_ttl=MarketDataCacheTTL.
|
|
91
|
+
cache_ttl=MarketDataCacheTTL.UNKNOWN.value,
|
|
92
92
|
publish_key=f"{MarketDataCacheKeys.UNKNOWN.value}",
|
|
93
93
|
)]
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
-
from datetime import datetime, timezone
|
|
4
|
-
from typing import Optional, List
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Optional, List
|
|
5
5
|
from zoneinfo import ZoneInfo
|
|
6
6
|
|
|
7
|
-
from massive.exceptions import BadResponse
|
|
8
|
-
from massive.rest import RESTClient
|
|
9
|
-
from massive.rest.models import (
|
|
10
|
-
TickerSnapshot,
|
|
11
|
-
Agg,
|
|
12
|
-
)
|
|
13
7
|
from massive.websocket.models import (
|
|
14
8
|
EquityAgg,
|
|
15
9
|
EventType
|
|
@@ -19,6 +13,7 @@ from kuhl_haus.mdp.analyzers.analyzer import Analyzer
|
|
|
19
13
|
from kuhl_haus.mdp.components.market_data_cache import MarketDataCache
|
|
20
14
|
from kuhl_haus.mdp.models.market_data_analyzer_result import MarketDataAnalyzerResult
|
|
21
15
|
from kuhl_haus.mdp.models.market_data_cache_keys import MarketDataCacheKeys
|
|
16
|
+
from kuhl_haus.mdp.models.market_data_cache_ttl import MarketDataCacheTTL
|
|
22
17
|
from kuhl_haus.mdp.models.market_data_pubsub_keys import MarketDataPubSubKeys
|
|
23
18
|
from kuhl_haus.mdp.models.top_stocks_cache_item import TopStocksCacheItem
|
|
24
19
|
|
|
@@ -94,24 +89,24 @@ class TopStocksAnalyzer(Analyzer):
|
|
|
94
89
|
MarketDataAnalyzerResult(
|
|
95
90
|
data=self.cache_item.to_dict(),
|
|
96
91
|
cache_key=self.cache_key,
|
|
97
|
-
cache_ttl=
|
|
92
|
+
cache_ttl=MarketDataCacheTTL.TOP_STOCKS_SCANNER.value,
|
|
98
93
|
),
|
|
99
94
|
MarketDataAnalyzerResult(
|
|
100
95
|
data=self.cache_item.top_volume(100),
|
|
101
96
|
cache_key=MarketDataPubSubKeys.TOP_VOLUME_SCANNER.value,
|
|
102
|
-
cache_ttl=
|
|
97
|
+
cache_ttl=MarketDataCacheTTL.TOP_VOLUME_SCANNER.value,
|
|
103
98
|
publish_key=MarketDataPubSubKeys.TOP_VOLUME_SCANNER.value,
|
|
104
99
|
),
|
|
105
100
|
MarketDataAnalyzerResult(
|
|
106
101
|
data=self.cache_item.top_gainers(500),
|
|
107
102
|
cache_key=MarketDataPubSubKeys.TOP_GAINERS_SCANNER.value,
|
|
108
|
-
cache_ttl=
|
|
103
|
+
cache_ttl=MarketDataCacheTTL.TOP_GAINERS_SCANNER.value,
|
|
109
104
|
publish_key=MarketDataPubSubKeys.TOP_GAINERS_SCANNER.value,
|
|
110
105
|
),
|
|
111
106
|
MarketDataAnalyzerResult(
|
|
112
107
|
data=self.cache_item.top_gappers(500),
|
|
113
108
|
cache_key=MarketDataPubSubKeys.TOP_GAPPERS_SCANNER.value,
|
|
114
|
-
cache_ttl=
|
|
109
|
+
cache_ttl=MarketDataCacheTTL.TOP_GAPPERS_SCANNER.value,
|
|
115
110
|
publish_key=MarketDataPubSubKeys.TOP_GAPPERS_SCANNER.value,
|
|
116
111
|
)
|
|
117
112
|
]
|
|
@@ -141,7 +136,11 @@ class TopStocksAnalyzer(Analyzer):
|
|
|
141
136
|
prev_day_volume = snapshot.prev_day.volume
|
|
142
137
|
prev_day_vwap = snapshot.prev_day.vwap
|
|
143
138
|
break
|
|
144
|
-
except
|
|
139
|
+
except KeyError:
|
|
140
|
+
retry_count += 1
|
|
141
|
+
await self.cache.delete_ticker_snapshot(event.symbol)
|
|
142
|
+
except Exception as e:
|
|
143
|
+
self.logger.error(f"Failed to get snapshot for {event.symbol}: {e}")
|
|
145
144
|
retry_count += 1
|
|
146
145
|
if retry_count == max_tries and prev_day_close == 0:
|
|
147
146
|
self.logger.error(f"Failed to get snapshot for {event.symbol} after {max_tries} tries.")
|
|
@@ -155,7 +154,8 @@ class TopStocksAnalyzer(Analyzer):
|
|
|
155
154
|
try:
|
|
156
155
|
avg_volume = await self.cache.get_avg_volume(event.symbol)
|
|
157
156
|
break
|
|
158
|
-
except Exception:
|
|
157
|
+
except Exception as e:
|
|
158
|
+
self.logger.error(f"Failed to get average volume for {event.symbol}: {e}")
|
|
159
159
|
retry_count += 1
|
|
160
160
|
if retry_count == max_tries and avg_volume == 0:
|
|
161
161
|
self.logger.error(f"Failed to get average volume for {event.symbol} after {max_tries} tries.")
|
|
@@ -169,7 +169,8 @@ class TopStocksAnalyzer(Analyzer):
|
|
|
169
169
|
try:
|
|
170
170
|
free_float = await self.cache.get_free_float(event.symbol)
|
|
171
171
|
break
|
|
172
|
-
except Exception:
|
|
172
|
+
except Exception as e:
|
|
173
|
+
self.logger.error(f"Failed to get free float for {event.symbol}: {e}")
|
|
173
174
|
retry_count += 1
|
|
174
175
|
if retry_count == max_tries and free_float == 0:
|
|
175
176
|
self.logger.error(f"Failed to get free float for {event.symbol} after {max_tries} tries.")
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/market_data_cache.py
RENAMED
|
@@ -26,6 +26,18 @@ class MarketDataCache:
|
|
|
26
26
|
self.redis_client = redis_client
|
|
27
27
|
self.http_session = None
|
|
28
28
|
|
|
29
|
+
async def delete_cache(self, cache_key: str):
|
|
30
|
+
"""
|
|
31
|
+
Delete cache entry.
|
|
32
|
+
|
|
33
|
+
:arg cache_key: Cache key to delete
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
await self.redis_client.delete(cache_key)
|
|
37
|
+
self.logger.info(f"Deleted cache entry: {cache_key}")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
self.logger.error(f"Error deleting cache entry: {e}")
|
|
40
|
+
|
|
29
41
|
async def get_cache(self, cache_key: str) -> Optional[dict]:
|
|
30
42
|
"""Fetch current value from Redis cache (for snapshot requests)."""
|
|
31
43
|
value = await self.redis_client.get(cache_key)
|
|
@@ -44,6 +56,16 @@ class MarketDataCache:
|
|
|
44
56
|
await self.redis_client.publish(publish_key, json.dumps(data))
|
|
45
57
|
self.logger.info(f"Published data for {publish_key}")
|
|
46
58
|
|
|
59
|
+
async def delete_ticker_snapshot(self, ticker: str):
|
|
60
|
+
"""
|
|
61
|
+
Delete ticker snapshot from cache.
|
|
62
|
+
|
|
63
|
+
:param ticker: symbol of ticker to delete snapshot for
|
|
64
|
+
:return: None
|
|
65
|
+
"""
|
|
66
|
+
cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:{ticker}"
|
|
67
|
+
await self.delete_cache(cache_key=cache_key)
|
|
68
|
+
|
|
47
69
|
async def get_ticker_snapshot(self, ticker: str) -> TickerSnapshot:
|
|
48
70
|
self.logger.info(f"Getting snapshot for {ticker}")
|
|
49
71
|
cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:{ticker}"
|
|
@@ -61,7 +83,7 @@ class MarketDataCache:
|
|
|
61
83
|
await self.cache_data(
|
|
62
84
|
data=data,
|
|
63
85
|
cache_key=cache_key,
|
|
64
|
-
cache_ttl=MarketDataCacheTTL.
|
|
86
|
+
cache_ttl=MarketDataCacheTTL.TICKER_SNAPSHOTS.value
|
|
65
87
|
)
|
|
66
88
|
return snapshot
|
|
67
89
|
|
|
@@ -109,13 +131,15 @@ class MarketDataCache:
|
|
|
109
131
|
periods_calculated += 1
|
|
110
132
|
else:
|
|
111
133
|
break
|
|
134
|
+
if periods_calculated == 0:
|
|
135
|
+
raise Exception(f"No volume data returned for {ticker}")
|
|
112
136
|
avg_volume = total_volume / periods_calculated
|
|
113
137
|
|
|
114
138
|
self.logger.info(f"average volume {ticker}: {avg_volume}")
|
|
115
139
|
await self.cache_data(
|
|
116
140
|
data=avg_volume,
|
|
117
141
|
cache_key=cache_key,
|
|
118
|
-
cache_ttl=MarketDataCacheTTL.
|
|
142
|
+
cache_ttl=MarketDataCacheTTL.TICKER_AVG_VOLUME.value
|
|
119
143
|
)
|
|
120
144
|
return avg_volume
|
|
121
145
|
|
|
@@ -162,7 +186,7 @@ class MarketDataCache:
|
|
|
162
186
|
await self.cache_data(
|
|
163
187
|
data=free_float,
|
|
164
188
|
cache_key=cache_key,
|
|
165
|
-
cache_ttl=MarketDataCacheTTL.
|
|
189
|
+
cache_ttl=MarketDataCacheTTL.TICKER_FREE_FLOAT.value
|
|
166
190
|
)
|
|
167
191
|
return free_float
|
|
168
192
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Minutes in seconds
|
|
2
|
+
ONE_MINUTE = 60
|
|
3
|
+
FIVE_MINUTES = 300
|
|
4
|
+
TEN_MINUTES = 600
|
|
5
|
+
FIFTEEN_MINUTES = 900
|
|
6
|
+
TWENTY_MINUTES = 1200
|
|
7
|
+
THIRTY_MINUTES = 1800
|
|
8
|
+
|
|
9
|
+
# Hours in seconds
|
|
10
|
+
ONE_HOUR = 3600
|
|
11
|
+
TWO_HOURS = 7200
|
|
12
|
+
FOUR_HOURS = 14400
|
|
13
|
+
SIX_HOURS = 21600
|
|
14
|
+
EIGHT_HOURS = 28800
|
|
15
|
+
TWELVE_HOURS = 43200
|
|
16
|
+
|
|
17
|
+
# Days in seconds
|
|
18
|
+
ONE_DAY = 86400
|
|
19
|
+
TWO_DAYS = 172800
|
|
20
|
+
THREE_DAYS = 259200
|
|
21
|
+
FOUR_DAYS = 345600
|
|
22
|
+
FIVE_DAYS = 432000
|
|
23
|
+
SIX_DAYS = 518400
|
|
24
|
+
SEVEN_DAYS = 604800
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from kuhl_haus.mdp.models.constants import (
|
|
3
|
+
EIGHT_HOURS,
|
|
4
|
+
FIVE_MINUTES,
|
|
5
|
+
ONE_DAY,
|
|
6
|
+
ONE_HOUR,
|
|
7
|
+
THREE_DAYS,
|
|
8
|
+
TWELVE_HOURS,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MarketDataCacheTTL(Enum):
|
|
13
|
+
# Raw market data caches
|
|
14
|
+
AGGREGATE = FIVE_MINUTES
|
|
15
|
+
HALTS = ONE_DAY
|
|
16
|
+
QUOTES = ONE_HOUR
|
|
17
|
+
TRADES = ONE_HOUR
|
|
18
|
+
UNKNOWN = ONE_DAY
|
|
19
|
+
|
|
20
|
+
# Ticker caches
|
|
21
|
+
TICKER_AVG_VOLUME = TWELVE_HOURS
|
|
22
|
+
TICKER_FREE_FLOAT = TWELVE_HOURS
|
|
23
|
+
TICKER_SNAPSHOTS = EIGHT_HOURS
|
|
24
|
+
|
|
25
|
+
# Scanner caches
|
|
26
|
+
TOP_STOCKS_SCANNER = EIGHT_HOURS
|
|
27
|
+
TOP_VOLUME_SCANNER = THREE_DAYS
|
|
28
|
+
TOP_GAINERS_SCANNER = THREE_DAYS
|
|
29
|
+
TOP_GAPPERS_SCANNER = THREE_DAYS
|
|
@@ -49,8 +49,8 @@ def test_analyze_data_with_valid_luld_event_expect_valid_result(valid_symbol, va
|
|
|
49
49
|
|
|
50
50
|
# Assert
|
|
51
51
|
assert len(result) == 1
|
|
52
|
-
assert result[0].cache_key == f"{MarketDataCacheKeys.HALTS.value}:{symbol}"
|
|
53
|
-
assert result[0].cache_ttl == MarketDataCacheTTL.
|
|
52
|
+
# assert result[0].cache_key == f"{MarketDataCacheKeys.HALTS.value}:{symbol}"
|
|
53
|
+
# assert result[0].cache_ttl == MarketDataCacheTTL.HALTS.value
|
|
54
54
|
assert result[0].publish_key == f"{MarketDataCacheKeys.HALTS.value}:{symbol}"
|
|
55
55
|
assert result[0].data == data
|
|
56
56
|
|
|
@@ -64,8 +64,8 @@ def test_analyze_data_with_equity_agg_event_happy_path(valid_symbol, valid_equit
|
|
|
64
64
|
|
|
65
65
|
# Assert
|
|
66
66
|
assert len(result) == 1
|
|
67
|
-
assert result[0].cache_key == f"{MarketDataCacheKeys.AGGREGATE.value}:{valid_symbol}"
|
|
68
|
-
assert result[0].cache_ttl == MarketDataCacheTTL.
|
|
67
|
+
# assert result[0].cache_key == f"{MarketDataCacheKeys.AGGREGATE.value}:{valid_symbol}"
|
|
68
|
+
# assert result[0].cache_ttl == MarketDataCacheTTL.AGGREGATE.value
|
|
69
69
|
assert result[0].publish_key == f"{MarketDataCacheKeys.AGGREGATE.value}:{valid_symbol}"
|
|
70
70
|
assert result[0].data == valid_equity_agg_data
|
|
71
71
|
|
|
@@ -79,8 +79,8 @@ def test_analyze_data_with_equity_agg_min_event_happy_path(valid_symbol, valid_e
|
|
|
79
79
|
|
|
80
80
|
# Assert
|
|
81
81
|
assert len(result) == 1
|
|
82
|
-
assert result[0].cache_key == f"{MarketDataCacheKeys.AGGREGATE.value}:{valid_symbol}"
|
|
83
|
-
assert result[0].cache_ttl == MarketDataCacheTTL.
|
|
82
|
+
# assert result[0].cache_key == f"{MarketDataCacheKeys.AGGREGATE.value}:{valid_symbol}"
|
|
83
|
+
# assert result[0].cache_ttl == MarketDataCacheTTL.AGGREGATE.value
|
|
84
84
|
assert result[0].publish_key == f"{MarketDataCacheKeys.AGGREGATE.value}:{valid_symbol}"
|
|
85
85
|
assert result[0].data == valid_equity_agg_minute_data
|
|
86
86
|
|
|
@@ -94,8 +94,8 @@ def test_analyze_data_with_equity_trade_event_happy_path(valid_symbol, valid_equ
|
|
|
94
94
|
|
|
95
95
|
# Assert
|
|
96
96
|
assert len(result) == 1
|
|
97
|
-
assert result[0].cache_key == f"{MarketDataCacheKeys.TRADES.value}:{valid_symbol}"
|
|
98
|
-
assert result[0].cache_ttl == MarketDataCacheTTL.
|
|
97
|
+
# assert result[0].cache_key == f"{MarketDataCacheKeys.TRADES.value}:{valid_symbol}"
|
|
98
|
+
# assert result[0].cache_ttl == MarketDataCacheTTL.TRADES.value
|
|
99
99
|
assert result[0].publish_key == f"{MarketDataCacheKeys.TRADES.value}:{valid_symbol}"
|
|
100
100
|
assert result[0].data == valid_equity_trade_data
|
|
101
101
|
|
|
@@ -109,8 +109,8 @@ def test_analyze_data_equity_quote_event_happy_path(valid_symbol, valid_equity_q
|
|
|
109
109
|
|
|
110
110
|
# Assert
|
|
111
111
|
assert len(result) == 1
|
|
112
|
-
assert result[0].cache_key == f"{MarketDataCacheKeys.QUOTES.value}:{valid_symbol}"
|
|
113
|
-
assert result[0].cache_ttl == MarketDataCacheTTL.
|
|
112
|
+
# assert result[0].cache_key == f"{MarketDataCacheKeys.QUOTES.value}:{valid_symbol}"
|
|
113
|
+
# assert result[0].cache_ttl == MarketDataCacheTTL.QUOTES.value
|
|
114
114
|
assert result[0].publish_key == f"{MarketDataCacheKeys.QUOTES.value}:{valid_symbol}"
|
|
115
115
|
assert result[0].data == valid_equity_quote_data
|
|
116
116
|
|
|
@@ -167,7 +167,7 @@ def test_handle_luld_event_happy_path(valid_symbol, valid_luld_data):
|
|
|
167
167
|
# Assert
|
|
168
168
|
assert len(result) == 1
|
|
169
169
|
assert result[0].cache_key == f"{MarketDataCacheKeys.HALTS.value}:{valid_symbol}"
|
|
170
|
-
assert result[0].cache_ttl == MarketDataCacheTTL.
|
|
170
|
+
assert result[0].cache_ttl == MarketDataCacheTTL.HALTS.value
|
|
171
171
|
assert result[0].publish_key == f"{MarketDataCacheKeys.HALTS.value}:{valid_symbol}"
|
|
172
172
|
assert result[0].data == valid_luld_data
|
|
173
173
|
|
|
@@ -181,8 +181,8 @@ def test_handle_equity_agg_event_happy_path(valid_symbol, valid_equity_agg_data)
|
|
|
181
181
|
|
|
182
182
|
# Assert
|
|
183
183
|
assert len(result) == 1
|
|
184
|
-
assert result[0].cache_key == f"{MarketDataCacheKeys.AGGREGATE.value}:{valid_symbol}"
|
|
185
|
-
assert result[0].cache_ttl == MarketDataCacheTTL.
|
|
184
|
+
# assert result[0].cache_key == f"{MarketDataCacheKeys.AGGREGATE.value}:{valid_symbol}"
|
|
185
|
+
# assert result[0].cache_ttl == MarketDataCacheTTL.AGGREGATE.value
|
|
186
186
|
assert result[0].publish_key == f"{MarketDataCacheKeys.AGGREGATE.value}:{valid_symbol}"
|
|
187
187
|
assert result[0].data == valid_equity_agg_data
|
|
188
188
|
|
|
@@ -196,8 +196,8 @@ def test_handle_equity_trade_event_happy_path(valid_symbol, valid_equity_trade_d
|
|
|
196
196
|
|
|
197
197
|
# Assert
|
|
198
198
|
assert len(result) == 1
|
|
199
|
-
assert result[0].cache_key == f"{MarketDataCacheKeys.TRADES.value}:{valid_symbol}"
|
|
200
|
-
assert result[0].cache_ttl == MarketDataCacheTTL.
|
|
199
|
+
# assert result[0].cache_key == f"{MarketDataCacheKeys.TRADES.value}:{valid_symbol}"
|
|
200
|
+
# assert result[0].cache_ttl == MarketDataCacheTTL.TRADES.value
|
|
201
201
|
assert result[0].publish_key == f"{MarketDataCacheKeys.TRADES.value}:{valid_symbol}"
|
|
202
202
|
assert result[0].data == valid_equity_trade_data
|
|
203
203
|
|
|
@@ -211,8 +211,8 @@ def test_handle_equity_quote_event_happy_path(valid_symbol, valid_equity_quote_d
|
|
|
211
211
|
|
|
212
212
|
# Assert
|
|
213
213
|
assert len(result) == 1
|
|
214
|
-
assert result[0].cache_key == f"{MarketDataCacheKeys.QUOTES.value}:{valid_symbol}"
|
|
215
|
-
assert result[0].cache_ttl == MarketDataCacheTTL.
|
|
214
|
+
# assert result[0].cache_key == f"{MarketDataCacheKeys.QUOTES.value}:{valid_symbol}"
|
|
215
|
+
# assert result[0].cache_ttl == MarketDataCacheTTL.QUOTES.value
|
|
216
216
|
assert result[0].publish_key == f"{MarketDataCacheKeys.QUOTES.value}:{valid_symbol}"
|
|
217
217
|
assert result[0].data == valid_equity_quote_data
|
|
218
218
|
|
|
@@ -228,6 +228,6 @@ def test_handle_unknown_event_happy_path():
|
|
|
228
228
|
# Assert
|
|
229
229
|
assert len(result) == 1
|
|
230
230
|
assert result[0].cache_key.startswith(f"{MarketDataCacheKeys.UNKNOWN.value}:")
|
|
231
|
-
assert result[0].cache_ttl == MarketDataCacheTTL.
|
|
231
|
+
assert result[0].cache_ttl == MarketDataCacheTTL.UNKNOWN.value
|
|
232
232
|
assert result[0].publish_key == MarketDataCacheKeys.UNKNOWN.value
|
|
233
233
|
assert result[0].data == data
|
|
@@ -6,6 +6,7 @@ from kuhl_haus.mdp.components.market_data_cache import MarketDataCache
|
|
|
6
6
|
from massive.rest.models import TickerSnapshot
|
|
7
7
|
|
|
8
8
|
from kuhl_haus.mdp.models.market_data_cache_keys import MarketDataCacheKeys
|
|
9
|
+
from kuhl_haus.mdp.models.market_data_cache_ttl import MarketDataCacheTTL
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@pytest.fixture
|
|
@@ -289,10 +290,10 @@ async def test_get_avg_volume_caches_with_correct_ttl():
|
|
|
289
290
|
# Assert
|
|
290
291
|
mock_redis_client.get.assert_awaited_once_with(mock_cache_key)
|
|
291
292
|
mock_rest_client.list_financials_ratios.assert_called_once_with(ticker="TEST")
|
|
292
|
-
# Verify setex was called with the correct TTL
|
|
293
|
+
# Verify setex was called with the correct TTL
|
|
293
294
|
call_args = mock_redis_client.setex.await_args
|
|
294
295
|
assert call_args[0][0] == mock_cache_key
|
|
295
|
-
assert call_args[0][1] ==
|
|
296
|
+
assert call_args[0][1] == MarketDataCacheTTL.TICKER_AVG_VOLUME.value
|
|
296
297
|
assert result == mock_avg_volume
|
|
297
298
|
|
|
298
299
|
|
|
@@ -318,10 +319,10 @@ async def test_get_avg_volume_caches_with_correct_ttl():
|
|
|
318
319
|
# Assert
|
|
319
320
|
mock_redis_client.get.assert_awaited_once_with(mock_cache_key)
|
|
320
321
|
mock_rest_client.list_financials_ratios.assert_called_once_with(ticker="TEST")
|
|
321
|
-
# Verify setex was called with the correct TTL
|
|
322
|
+
# Verify setex was called with the correct TTL
|
|
322
323
|
call_args = mock_redis_client.setex.await_args
|
|
323
324
|
assert call_args[0][0] == mock_cache_key
|
|
324
|
-
assert call_args[0][1] ==
|
|
325
|
+
assert call_args[0][1] == MarketDataCacheTTL.TICKER_AVG_VOLUME.value
|
|
325
326
|
assert result == mock_avg_volume
|
|
326
327
|
|
|
327
328
|
|
|
@@ -445,7 +446,7 @@ async def test_get_free_float_caches_with_correct_ttl():
|
|
|
445
446
|
# Verify setex was called with the correct TTL (TWELVE_HOURS = 43200 seconds)
|
|
446
447
|
call_args = mock_redis_client.setex.await_args
|
|
447
448
|
assert call_args[0][0] == mock_cache_key
|
|
448
|
-
assert call_args[0][1] ==
|
|
449
|
+
assert call_args[0][1] == MarketDataCacheTTL.TICKER_FREE_FLOAT.value
|
|
449
450
|
assert result == mock_free_float
|
|
450
451
|
|
|
451
452
|
|
|
@@ -780,4 +781,105 @@ async def test_get_http_session_singleton_behavior(mock_client_session):
|
|
|
780
781
|
# Assert
|
|
781
782
|
mock_client_session.assert_called_once() # Should only be called once
|
|
782
783
|
assert result1 == result2 == result3 == mock_session # All should return same instance
|
|
783
|
-
assert id(result1) == id(result2) == id(result3) # Verify same object in memory
|
|
784
|
+
assert id(result1) == id(result2) == id(result3) # Verify same object in memory
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
@pytest.mark.asyncio
|
|
788
|
+
async def test_delete_ticker_snapshot_with_valid_ticker_expect_cache_deleted():
|
|
789
|
+
# Arrange
|
|
790
|
+
mock_redis_client = AsyncMock()
|
|
791
|
+
mock_rest_client = MagicMock()
|
|
792
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
793
|
+
ticker = "TEST"
|
|
794
|
+
cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:{ticker}"
|
|
795
|
+
|
|
796
|
+
# Act
|
|
797
|
+
await sut.delete_ticker_snapshot(ticker)
|
|
798
|
+
|
|
799
|
+
# Assert
|
|
800
|
+
mock_redis_client.delete.assert_awaited_once_with(cache_key)
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
@pytest.mark.asyncio
|
|
804
|
+
async def test_delete_ticker_snapshot_with_empty_ticker_expect_no_side_effect():
|
|
805
|
+
# Arrange
|
|
806
|
+
mock_redis_client = AsyncMock()
|
|
807
|
+
mock_rest_client = MagicMock()
|
|
808
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
809
|
+
ticker = ""
|
|
810
|
+
cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:{ticker}"
|
|
811
|
+
|
|
812
|
+
# Act
|
|
813
|
+
await sut.delete_ticker_snapshot(ticker)
|
|
814
|
+
|
|
815
|
+
# Assert
|
|
816
|
+
mock_redis_client.delete.assert_awaited_once_with(cache_key)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
@pytest.mark.asyncio
|
|
820
|
+
@patch("logging.Logger.error")
|
|
821
|
+
async def test_delete_ticker_snapshot_with_redis_error_expect_logged_error(mock_logger):
|
|
822
|
+
# Arrange
|
|
823
|
+
mock_redis_client = AsyncMock()
|
|
824
|
+
mock_redis_client.delete = AsyncMock(side_effect=Exception("Redis connection error"))
|
|
825
|
+
mock_rest_client = MagicMock()
|
|
826
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
827
|
+
ticker = "TEST"
|
|
828
|
+
cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:{ticker}"
|
|
829
|
+
|
|
830
|
+
# Act
|
|
831
|
+
await sut.delete_ticker_snapshot(ticker)
|
|
832
|
+
|
|
833
|
+
# Assert
|
|
834
|
+
mock_redis_client.delete.assert_awaited_once_with(cache_key)
|
|
835
|
+
mock_logger.assert_called_once_with("Error deleting cache entry: Redis connection error")
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
@pytest.mark.asyncio
|
|
839
|
+
async def test_delete_cache_with_existing_key_expect_cache_deleted():
|
|
840
|
+
# Arrange
|
|
841
|
+
mock_redis_client = AsyncMock()
|
|
842
|
+
mock_rest_client = MagicMock()
|
|
843
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
844
|
+
test_cache_key = "test:cache:key"
|
|
845
|
+
|
|
846
|
+
# Act
|
|
847
|
+
await sut.delete_cache(test_cache_key)
|
|
848
|
+
|
|
849
|
+
# Assert
|
|
850
|
+
mock_redis_client.delete.assert_awaited_once_with(test_cache_key)
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
@pytest.mark.asyncio
|
|
854
|
+
@patch("logging.Logger.error")
|
|
855
|
+
async def test_delete_cache_with_redis_error_expect_error_logged(mock_logger):
|
|
856
|
+
# Arrange
|
|
857
|
+
mock_redis_client = AsyncMock()
|
|
858
|
+
mock_redis_client.delete = AsyncMock(side_effect=Exception("Redis connection error"))
|
|
859
|
+
mock_rest_client = MagicMock()
|
|
860
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
861
|
+
test_cache_key = "test:cache:key"
|
|
862
|
+
|
|
863
|
+
# Act
|
|
864
|
+
await sut.delete_cache(test_cache_key)
|
|
865
|
+
|
|
866
|
+
# Assert
|
|
867
|
+
mock_redis_client.delete.assert_awaited_once_with(test_cache_key)
|
|
868
|
+
mock_logger.assert_called_once_with(f"Error deleting cache entry: Redis connection error")
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
@pytest.mark.asyncio
|
|
872
|
+
@patch("logging.Logger.info")
|
|
873
|
+
async def test_delete_cache_with_successful_deletion_expect_info_logged(mock_logger):
|
|
874
|
+
# Arrange
|
|
875
|
+
mock_redis_client = AsyncMock()
|
|
876
|
+
mock_rest_client = MagicMock()
|
|
877
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
878
|
+
test_cache_key = "test:cache:key"
|
|
879
|
+
|
|
880
|
+
# Act
|
|
881
|
+
await sut.delete_cache(test_cache_key)
|
|
882
|
+
|
|
883
|
+
# Assert
|
|
884
|
+
mock_redis_client.delete.assert_awaited_once_with(test_cache_key)
|
|
885
|
+
mock_logger.assert_called_once_with(f"Deleted cache entry: {test_cache_key}")
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
from enum import Enum
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class MarketDataCacheTTL(Enum):
|
|
5
|
-
# Hours
|
|
6
|
-
ONE_HOUR = 3600
|
|
7
|
-
TWO_HOURS = 7200
|
|
8
|
-
FOUR_HOURS = 14400
|
|
9
|
-
SIX_HOURS = 21600
|
|
10
|
-
EIGHT_HOURS = 28800
|
|
11
|
-
TWELVE_HOURS = 43200
|
|
12
|
-
|
|
13
|
-
# Days
|
|
14
|
-
ONE_DAY = 86400
|
|
15
|
-
TWO_DAYS = 172800
|
|
16
|
-
THREE_DAYS = 259200
|
|
17
|
-
FOUR_DAYS = 345600
|
|
18
|
-
FIVE_DAYS = 432000
|
|
19
|
-
SIX_DAYS = 518400
|
|
20
|
-
SEVEN_DAYS = 604800
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/market_data_scanner.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/widget_data_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/helpers/queue_name_resolver.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/massive_data_listener.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/massive_data_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/web_socket_message_serde.py
RENAMED
|
File without changes
|
|
File without changes
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_analyzer_result.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_cache_keys.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_pubsub_keys.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_scanner_names.py
RENAMED
|
File without changes
|
|
File without changes
|
{kuhl_haus_mdp-0.1.9 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/top_stocks_cache_item.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|