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.
@@ -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/api/v3"
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/{symbol}"
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/{symbols}"
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
- url = f"{self.BASE_URL}/historical-price-full/{symbol}"
199
- params = {"timeseries": days}
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
- data = self._rate_limited_get(url, params)
203
+ raw = self._rate_limited_get(url, params)
202
204
 
203
- if data:
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 data
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/{symbol}"
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/{symbol}"
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 (batch)
372
+ # Test 5: Market indices (individual requests)
369
373
  print("\n5. Testing market indices (^GSPC, ^VIX)...")
370
- indices = client.get_quote("^GSPC,^VIX")
371
- if indices:
372
- for idx in indices:
373
- print(f"✓ {idx['symbol']}: {idx['price']:.2f}")
374
- else:
375
- print("✗ Market indices failed")
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/api/v3"
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 one or more symbols (comma-separated)"""
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/{symbols}"
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
- url = f"{self.BASE_URL}/historical-price-full/{symbol}"
105
- params = {"timeseries": days}
106
- data = self._rate_limited_get(url, params)
107
- if data:
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
- return data
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, batching up to 5 per request"""
117
+ """Fetch quotes for a list of symbols (individual requests)"""
113
118
  results = {}
114
- # FMP supports comma-separated symbols in quote endpoint
115
- batch_size = 5
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/api/v3"
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/{symbol}"
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/{symbol}"
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/api/v3"
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/{symbol}"
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/api/v3"
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
- url = f"{self.BASE_URL}/historical-price-full/{symbol}"
95
- params = {"timeseries": days}
96
- data = self._rate_limited_get(url, params)
97
- if data:
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
- return data
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.STABLE_URL}/treasury-rates"
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/api/v3"
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 one or more symbols (comma-separated)"""
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/{symbols}"
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
- url = f"{self.BASE_URL}/historical-price-full/{symbol}"
105
- params = {"timeseries": days}
106
- data = self._rate_limited_get(url, params)
107
- if data:
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
- return data
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, batching up to 5 per request"""
117
+ """Fetch quotes for a list of symbols (individual requests)"""
113
118
  results = {}
114
- # FMP supports comma-separated symbols in quote endpoint
115
- batch_size = 5
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/api/v3"
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}/sp500_constituent"
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 one or more symbols (comma-separated)"""
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/{symbols}"
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
- url = f"{self.BASE_URL}/historical-price-full/{symbol}"
123
- params = {"timeseries": days}
124
- data = self._rate_limited_get(url, params)
125
- if data:
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
- return data
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, batching up to 5 per request"""
135
+ """Fetch quotes for a list of symbols (individual requests)"""
131
136
  results = {}
132
- batch_size = 5
133
- for i in range(0, len(symbols), batch_size):
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