quantwise 1.2.5 → 1.2.6
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.
- package/.claude/skills/canslim-screener/scripts/__pycache__/fmp_client.cpython-314.pyc +0 -0
- package/.claude/skills/canslim-screener/scripts/fmp_client.py +26 -22
- package/.claude/skills/ftd-detector/scripts/fmp_client.py +17 -16
- package/.claude/skills/institutional-flow-tracker/scripts/analyze_single_stock.py +5 -5
- package/.claude/skills/institutional-flow-tracker/scripts/track_institutional_flow.py +3 -4
- package/.claude/skills/macro-regime-detector/scripts/fmp_client.py +12 -8
- package/.claude/skills/market-top-detector/scripts/fmp_client.py +17 -16
- package/.claude/skills/vcp-screener/scripts/fmp_client.py +18 -16
- package/cli.mjs +545 -515
- package/package.json +1 -1
|
Binary file
|
|
@@ -16,7 +16,7 @@ import os
|
|
|
16
16
|
import sys
|
|
17
17
|
import time
|
|
18
18
|
from typing import Dict, List, Optional
|
|
19
|
-
from datetime import datetime
|
|
19
|
+
from datetime import datetime, timedelta
|
|
20
20
|
|
|
21
21
|
try:
|
|
22
22
|
import requests
|
|
@@ -28,7 +28,7 @@ except ImportError:
|
|
|
28
28
|
class FMPClient:
|
|
29
29
|
"""Client for Financial Modeling Prep API with rate limiting and caching"""
|
|
30
30
|
|
|
31
|
-
BASE_URL = "https://financialmodelingprep.com/
|
|
31
|
+
BASE_URL = "https://financialmodelingprep.com/stable"
|
|
32
32
|
RATE_LIMIT_DELAY = 0.3 # 300ms between requests (200 requests/minute max)
|
|
33
33
|
|
|
34
34
|
def __init__(self, api_key: Optional[str] = None):
|
|
@@ -131,8 +131,8 @@ class FMPClient:
|
|
|
131
131
|
if cache_key in self.cache:
|
|
132
132
|
return self.cache[cache_key]
|
|
133
133
|
|
|
134
|
-
url = f"{self.BASE_URL}/income-statement
|
|
135
|
-
params = {"period": period, "limit": limit}
|
|
134
|
+
url = f"{self.BASE_URL}/income-statement"
|
|
135
|
+
params = {"symbol": symbol, "period": period, "limit": limit}
|
|
136
136
|
|
|
137
137
|
data = self._rate_limited_get(url, params)
|
|
138
138
|
|
|
@@ -163,9 +163,9 @@ class FMPClient:
|
|
|
163
163
|
if cache_key in self.cache:
|
|
164
164
|
return self.cache[cache_key]
|
|
165
165
|
|
|
166
|
-
url = f"{self.BASE_URL}/quote
|
|
166
|
+
url = f"{self.BASE_URL}/quote"
|
|
167
167
|
|
|
168
|
-
data = self._rate_limited_get(url)
|
|
168
|
+
data = self._rate_limited_get(url, {"symbol": symbols})
|
|
169
169
|
|
|
170
170
|
if data:
|
|
171
171
|
self.cache[cache_key] = data
|
|
@@ -195,15 +195,19 @@ class FMPClient:
|
|
|
195
195
|
if cache_key in self.cache:
|
|
196
196
|
return self.cache[cache_key]
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
to_date = datetime.now().strftime("%Y-%m-%d")
|
|
199
|
+
from_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
200
|
+
url = f"{self.BASE_URL}/historical-price-eod/full"
|
|
201
|
+
params = {"symbol": symbol, "from": from_date, "to": to_date}
|
|
200
202
|
|
|
201
|
-
|
|
203
|
+
raw = self._rate_limited_get(url, params)
|
|
202
204
|
|
|
203
|
-
if
|
|
205
|
+
if raw and isinstance(raw, list):
|
|
206
|
+
data = {"symbol": symbol, "historical": raw}
|
|
204
207
|
self.cache[cache_key] = data
|
|
208
|
+
return data
|
|
205
209
|
|
|
206
|
-
return
|
|
210
|
+
return None
|
|
207
211
|
|
|
208
212
|
def get_profile(self, symbol: str) -> Optional[List[Dict]]:
|
|
209
213
|
"""
|
|
@@ -224,9 +228,9 @@ class FMPClient:
|
|
|
224
228
|
if cache_key in self.cache:
|
|
225
229
|
return self.cache[cache_key]
|
|
226
230
|
|
|
227
|
-
url = f"{self.BASE_URL}/profile
|
|
231
|
+
url = f"{self.BASE_URL}/profile"
|
|
228
232
|
|
|
229
|
-
data = self._rate_limited_get(url)
|
|
233
|
+
data = self._rate_limited_get(url, {"symbol": symbol})
|
|
230
234
|
|
|
231
235
|
if data:
|
|
232
236
|
self.cache[cache_key] = data
|
|
@@ -261,9 +265,9 @@ class FMPClient:
|
|
|
261
265
|
if cache_key in self.cache:
|
|
262
266
|
return self.cache[cache_key]
|
|
263
267
|
|
|
264
|
-
url = f"{self.BASE_URL}/institutional-holder
|
|
268
|
+
url = f"{self.BASE_URL}/institutional-holder"
|
|
265
269
|
|
|
266
|
-
data = self._rate_limited_get(url)
|
|
270
|
+
data = self._rate_limited_get(url, {"symbol": symbol})
|
|
267
271
|
|
|
268
272
|
if data:
|
|
269
273
|
self.cache[cache_key] = data
|
|
@@ -365,14 +369,14 @@ def test_client():
|
|
|
365
369
|
else:
|
|
366
370
|
print("✗ Historical prices failed")
|
|
367
371
|
|
|
368
|
-
# Test 5: Market indices (
|
|
372
|
+
# Test 5: Market indices (individual requests)
|
|
369
373
|
print("\n5. Testing market indices (^GSPC, ^VIX)...")
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
print(f"✓ {idx['symbol']}: {idx['price']:.2f}")
|
|
374
|
-
|
|
375
|
-
|
|
374
|
+
for sym in ["^GSPC", "^VIX"]:
|
|
375
|
+
idx = client.get_quote(sym)
|
|
376
|
+
if idx:
|
|
377
|
+
print(f"✓ {idx[0]['symbol']}: {idx[0]['price']:.2f}")
|
|
378
|
+
else:
|
|
379
|
+
print(f"✗ {sym} failed")
|
|
376
380
|
|
|
377
381
|
# Test 6: Cache
|
|
378
382
|
print("\n6. Testing cache (repeat AAPL quote)...")
|
|
@@ -16,6 +16,7 @@ import os
|
|
|
16
16
|
import sys
|
|
17
17
|
import time
|
|
18
18
|
from typing import Dict, List, Optional
|
|
19
|
+
from datetime import datetime, timedelta
|
|
19
20
|
|
|
20
21
|
try:
|
|
21
22
|
import requests
|
|
@@ -27,7 +28,7 @@ except ImportError:
|
|
|
27
28
|
class FMPClient:
|
|
28
29
|
"""Client for Financial Modeling Prep API with rate limiting and caching"""
|
|
29
30
|
|
|
30
|
-
BASE_URL = "https://financialmodelingprep.com/
|
|
31
|
+
BASE_URL = "https://financialmodelingprep.com/stable"
|
|
31
32
|
RATE_LIMIT_DELAY = 0.3 # 300ms between requests
|
|
32
33
|
|
|
33
34
|
def __init__(self, api_key: Optional[str] = None):
|
|
@@ -84,13 +85,13 @@ class FMPClient:
|
|
|
84
85
|
return None
|
|
85
86
|
|
|
86
87
|
def get_quote(self, symbols: str) -> Optional[List[Dict]]:
|
|
87
|
-
"""Fetch real-time quote data for
|
|
88
|
+
"""Fetch real-time quote data for a single symbol"""
|
|
88
89
|
cache_key = f"quote_{symbols}"
|
|
89
90
|
if cache_key in self.cache:
|
|
90
91
|
return self.cache[cache_key]
|
|
91
92
|
|
|
92
|
-
url = f"{self.BASE_URL}/quote
|
|
93
|
-
data = self._rate_limited_get(url)
|
|
93
|
+
url = f"{self.BASE_URL}/quote"
|
|
94
|
+
data = self._rate_limited_get(url, {"symbol": symbols})
|
|
94
95
|
if data:
|
|
95
96
|
self.cache[cache_key] = data
|
|
96
97
|
return data
|
|
@@ -101,22 +102,22 @@ class FMPClient:
|
|
|
101
102
|
if cache_key in self.cache:
|
|
102
103
|
return self.cache[cache_key]
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
to_date = datetime.now().strftime("%Y-%m-%d")
|
|
106
|
+
from_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
107
|
+
url = f"{self.BASE_URL}/historical-price-eod/full"
|
|
108
|
+
params = {"symbol": symbol, "from": from_date, "to": to_date}
|
|
109
|
+
raw = self._rate_limited_get(url, params)
|
|
110
|
+
if raw and isinstance(raw, list):
|
|
111
|
+
data = {"symbol": symbol, "historical": raw}
|
|
108
112
|
self.cache[cache_key] = data
|
|
109
|
-
|
|
113
|
+
return data
|
|
114
|
+
return None
|
|
110
115
|
|
|
111
116
|
def get_batch_quotes(self, symbols: List[str]) -> Dict[str, Dict]:
|
|
112
|
-
"""Fetch quotes for a list of symbols
|
|
117
|
+
"""Fetch quotes for a list of symbols (individual requests)"""
|
|
113
118
|
results = {}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
for i in range(0, len(symbols), batch_size):
|
|
117
|
-
batch = symbols[i:i+batch_size]
|
|
118
|
-
batch_str = ",".join(batch)
|
|
119
|
-
quotes = self.get_quote(batch_str)
|
|
119
|
+
for symbol in symbols:
|
|
120
|
+
quotes = self.get_quote(symbol)
|
|
120
121
|
if quotes:
|
|
121
122
|
for q in quotes:
|
|
122
123
|
results[q['symbol']] = q
|
|
@@ -34,12 +34,12 @@ class SingleStockAnalyzer:
|
|
|
34
34
|
|
|
35
35
|
def __init__(self, api_key: str):
|
|
36
36
|
self.api_key = api_key
|
|
37
|
-
self.base_url = "https://financialmodelingprep.com/
|
|
37
|
+
self.base_url = "https://financialmodelingprep.com/stable"
|
|
38
38
|
|
|
39
39
|
def get_institutional_holders(self, symbol: str) -> List[Dict]:
|
|
40
40
|
"""Get all institutional holders data for a stock"""
|
|
41
|
-
url = f"{self.base_url}/institutional-holder
|
|
42
|
-
params = {"apikey": self.api_key}
|
|
41
|
+
url = f"{self.base_url}/institutional-holder"
|
|
42
|
+
params = {"symbol": symbol, "apikey": self.api_key}
|
|
43
43
|
|
|
44
44
|
try:
|
|
45
45
|
response = requests.get(url, params=params, timeout=30)
|
|
@@ -52,8 +52,8 @@ class SingleStockAnalyzer:
|
|
|
52
52
|
|
|
53
53
|
def get_company_profile(self, symbol: str) -> Dict:
|
|
54
54
|
"""Get company profile information"""
|
|
55
|
-
url = f"{self.base_url}/profile
|
|
56
|
-
params = {"apikey": self.api_key}
|
|
55
|
+
url = f"{self.base_url}/profile"
|
|
56
|
+
params = {"symbol": symbol, "apikey": self.api_key}
|
|
57
57
|
|
|
58
58
|
try:
|
|
59
59
|
response = requests.get(url, params=params, timeout=30)
|
|
@@ -35,8 +35,7 @@ class InstitutionalFlowTracker:
|
|
|
35
35
|
|
|
36
36
|
def __init__(self, api_key: str):
|
|
37
37
|
self.api_key = api_key
|
|
38
|
-
self.base_url = "https://financialmodelingprep.com/
|
|
39
|
-
self.base_url_v4 = "https://financialmodelingprep.com/api/v4"
|
|
38
|
+
self.base_url = "https://financialmodelingprep.com/stable"
|
|
40
39
|
|
|
41
40
|
def get_stock_screener(
|
|
42
41
|
self,
|
|
@@ -61,8 +60,8 @@ class InstitutionalFlowTracker:
|
|
|
61
60
|
|
|
62
61
|
def get_institutional_holders(self, symbol: str) -> List[Dict]:
|
|
63
62
|
"""Get institutional holders for a specific stock"""
|
|
64
|
-
url = f"{self.base_url}/institutional-holder
|
|
65
|
-
params = {"apikey": self.api_key}
|
|
63
|
+
url = f"{self.base_url}/institutional-holder"
|
|
64
|
+
params = {"symbol": symbol, "apikey": self.api_key}
|
|
66
65
|
|
|
67
66
|
try:
|
|
68
67
|
response = requests.get(url, params=params, timeout=30)
|
|
@@ -17,6 +17,7 @@ import os
|
|
|
17
17
|
import sys
|
|
18
18
|
import time
|
|
19
19
|
from typing import Dict, List, Optional
|
|
20
|
+
from datetime import datetime, timedelta
|
|
20
21
|
|
|
21
22
|
try:
|
|
22
23
|
import requests
|
|
@@ -28,8 +29,7 @@ except ImportError:
|
|
|
28
29
|
class FMPClient:
|
|
29
30
|
"""Client for Financial Modeling Prep API with rate limiting and caching"""
|
|
30
31
|
|
|
31
|
-
BASE_URL = "https://financialmodelingprep.com/
|
|
32
|
-
STABLE_URL = "https://financialmodelingprep.com/stable"
|
|
32
|
+
BASE_URL = "https://financialmodelingprep.com/stable"
|
|
33
33
|
RATE_LIMIT_DELAY = 0.3 # 300ms between requests
|
|
34
34
|
|
|
35
35
|
def __init__(self, api_key: Optional[str] = None):
|
|
@@ -91,12 +91,16 @@ class FMPClient:
|
|
|
91
91
|
if cache_key in self.cache:
|
|
92
92
|
return self.cache[cache_key]
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
to_date = datetime.now().strftime("%Y-%m-%d")
|
|
95
|
+
from_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
96
|
+
url = f"{self.BASE_URL}/historical-price-eod/full"
|
|
97
|
+
params = {"symbol": symbol, "from": from_date, "to": to_date}
|
|
98
|
+
raw = self._rate_limited_get(url, params)
|
|
99
|
+
if raw and isinstance(raw, list):
|
|
100
|
+
data = {"symbol": symbol, "historical": raw}
|
|
98
101
|
self.cache[cache_key] = data
|
|
99
|
-
|
|
102
|
+
return data
|
|
103
|
+
return None
|
|
100
104
|
|
|
101
105
|
def get_batch_historical(self, symbols: List[str], days: int = 600) -> Dict[str, List[Dict]]:
|
|
102
106
|
"""Fetch historical prices for multiple symbols"""
|
|
@@ -118,7 +122,7 @@ class FMPClient:
|
|
|
118
122
|
if cache_key in self.cache:
|
|
119
123
|
return self.cache[cache_key]
|
|
120
124
|
|
|
121
|
-
url = f"{self.
|
|
125
|
+
url = f"{self.BASE_URL}/treasury-rates"
|
|
122
126
|
params = {"limit": days}
|
|
123
127
|
data = self._rate_limited_get(url, params)
|
|
124
128
|
if data and isinstance(data, list):
|
|
@@ -16,6 +16,7 @@ import os
|
|
|
16
16
|
import sys
|
|
17
17
|
import time
|
|
18
18
|
from typing import Dict, List, Optional
|
|
19
|
+
from datetime import datetime, timedelta
|
|
19
20
|
|
|
20
21
|
try:
|
|
21
22
|
import requests
|
|
@@ -27,7 +28,7 @@ except ImportError:
|
|
|
27
28
|
class FMPClient:
|
|
28
29
|
"""Client for Financial Modeling Prep API with rate limiting and caching"""
|
|
29
30
|
|
|
30
|
-
BASE_URL = "https://financialmodelingprep.com/
|
|
31
|
+
BASE_URL = "https://financialmodelingprep.com/stable"
|
|
31
32
|
RATE_LIMIT_DELAY = 0.3 # 300ms between requests
|
|
32
33
|
|
|
33
34
|
def __init__(self, api_key: Optional[str] = None):
|
|
@@ -84,13 +85,13 @@ class FMPClient:
|
|
|
84
85
|
return None
|
|
85
86
|
|
|
86
87
|
def get_quote(self, symbols: str) -> Optional[List[Dict]]:
|
|
87
|
-
"""Fetch real-time quote data for
|
|
88
|
+
"""Fetch real-time quote data for a single symbol"""
|
|
88
89
|
cache_key = f"quote_{symbols}"
|
|
89
90
|
if cache_key in self.cache:
|
|
90
91
|
return self.cache[cache_key]
|
|
91
92
|
|
|
92
|
-
url = f"{self.BASE_URL}/quote
|
|
93
|
-
data = self._rate_limited_get(url)
|
|
93
|
+
url = f"{self.BASE_URL}/quote"
|
|
94
|
+
data = self._rate_limited_get(url, {"symbol": symbols})
|
|
94
95
|
if data:
|
|
95
96
|
self.cache[cache_key] = data
|
|
96
97
|
return data
|
|
@@ -101,22 +102,22 @@ class FMPClient:
|
|
|
101
102
|
if cache_key in self.cache:
|
|
102
103
|
return self.cache[cache_key]
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
to_date = datetime.now().strftime("%Y-%m-%d")
|
|
106
|
+
from_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
107
|
+
url = f"{self.BASE_URL}/historical-price-eod/full"
|
|
108
|
+
params = {"symbol": symbol, "from": from_date, "to": to_date}
|
|
109
|
+
raw = self._rate_limited_get(url, params)
|
|
110
|
+
if raw and isinstance(raw, list):
|
|
111
|
+
data = {"symbol": symbol, "historical": raw}
|
|
108
112
|
self.cache[cache_key] = data
|
|
109
|
-
|
|
113
|
+
return data
|
|
114
|
+
return None
|
|
110
115
|
|
|
111
116
|
def get_batch_quotes(self, symbols: List[str]) -> Dict[str, Dict]:
|
|
112
|
-
"""Fetch quotes for a list of symbols
|
|
117
|
+
"""Fetch quotes for a list of symbols (individual requests)"""
|
|
113
118
|
results = {}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
for i in range(0, len(symbols), batch_size):
|
|
117
|
-
batch = symbols[i:i+batch_size]
|
|
118
|
-
batch_str = ",".join(batch)
|
|
119
|
-
quotes = self.get_quote(batch_str)
|
|
119
|
+
for symbol in symbols:
|
|
120
|
+
quotes = self.get_quote(symbol)
|
|
120
121
|
if quotes:
|
|
121
122
|
for q in quotes:
|
|
122
123
|
results[q['symbol']] = q
|
|
@@ -17,6 +17,7 @@ import os
|
|
|
17
17
|
import sys
|
|
18
18
|
import time
|
|
19
19
|
from typing import Dict, List, Optional
|
|
20
|
+
from datetime import datetime, timedelta
|
|
20
21
|
|
|
21
22
|
try:
|
|
22
23
|
import requests
|
|
@@ -28,7 +29,7 @@ except ImportError:
|
|
|
28
29
|
class FMPClient:
|
|
29
30
|
"""Client for Financial Modeling Prep API with rate limiting and caching"""
|
|
30
31
|
|
|
31
|
-
BASE_URL = "https://financialmodelingprep.com/
|
|
32
|
+
BASE_URL = "https://financialmodelingprep.com/stable"
|
|
32
33
|
RATE_LIMIT_DELAY = 0.3 # 300ms between requests
|
|
33
34
|
|
|
34
35
|
def __init__(self, api_key: Optional[str] = None):
|
|
@@ -95,20 +96,20 @@ class FMPClient:
|
|
|
95
96
|
if cache_key in self.cache:
|
|
96
97
|
return self.cache[cache_key]
|
|
97
98
|
|
|
98
|
-
url = f"{self.BASE_URL}/
|
|
99
|
+
url = f"{self.BASE_URL}/sp500-constituent"
|
|
99
100
|
data = self._rate_limited_get(url)
|
|
100
101
|
if data:
|
|
101
102
|
self.cache[cache_key] = data
|
|
102
103
|
return data
|
|
103
104
|
|
|
104
105
|
def get_quote(self, symbols: str) -> Optional[List[Dict]]:
|
|
105
|
-
"""Fetch real-time quote data for
|
|
106
|
+
"""Fetch real-time quote data for a single symbol"""
|
|
106
107
|
cache_key = f"quote_{symbols}"
|
|
107
108
|
if cache_key in self.cache:
|
|
108
109
|
return self.cache[cache_key]
|
|
109
110
|
|
|
110
|
-
url = f"{self.BASE_URL}/quote
|
|
111
|
-
data = self._rate_limited_get(url)
|
|
111
|
+
url = f"{self.BASE_URL}/quote"
|
|
112
|
+
data = self._rate_limited_get(url, {"symbol": symbols})
|
|
112
113
|
if data:
|
|
113
114
|
self.cache[cache_key] = data
|
|
114
115
|
return data
|
|
@@ -119,21 +120,22 @@ class FMPClient:
|
|
|
119
120
|
if cache_key in self.cache:
|
|
120
121
|
return self.cache[cache_key]
|
|
121
122
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
to_date = datetime.now().strftime("%Y-%m-%d")
|
|
124
|
+
from_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
125
|
+
url = f"{self.BASE_URL}/historical-price-eod/full"
|
|
126
|
+
params = {"symbol": symbol, "from": from_date, "to": to_date}
|
|
127
|
+
raw = self._rate_limited_get(url, params)
|
|
128
|
+
if raw and isinstance(raw, list):
|
|
129
|
+
data = {"symbol": symbol, "historical": raw}
|
|
126
130
|
self.cache[cache_key] = data
|
|
127
|
-
|
|
131
|
+
return data
|
|
132
|
+
return None
|
|
128
133
|
|
|
129
134
|
def get_batch_quotes(self, symbols: List[str]) -> Dict[str, Dict]:
|
|
130
|
-
"""Fetch quotes for a list of symbols
|
|
135
|
+
"""Fetch quotes for a list of symbols (individual requests)"""
|
|
131
136
|
results = {}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
batch = symbols[i:i+batch_size]
|
|
135
|
-
batch_str = ",".join(batch)
|
|
136
|
-
quotes = self.get_quote(batch_str)
|
|
137
|
+
for symbol in symbols:
|
|
138
|
+
quotes = self.get_quote(symbol)
|
|
137
139
|
if quotes:
|
|
138
140
|
for q in quotes:
|
|
139
141
|
results[q['symbol']] = q
|