-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2. fix badges 3. add codecov automation and details 4. implement process() function, Switch to live end-point, 5. Extend support for partial download of data, expose additonal functions. 6. Additional documentation
- Loading branch information
Showing
10 changed files
with
198 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
tools/ | ||
|
||
# C extensions | ||
*.so | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
__version__ = "0.0.01" | ||
|
||
from .auth import auth | ||
from .equity import get_chunk_and_size, process |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import asyncio | ||
import concurrent.futures | ||
import time | ||
from typing import Optional, Tuple | ||
|
||
import pandas as pd | ||
import requests | ||
|
||
url: str = "https://api.finra.org/data/group/OTCMarket/name/regShoDaily" | ||
|
||
|
||
def _requests_get(token: str, chunk_size: int, offset: int) -> pd.DataFrame: | ||
r = requests.get( | ||
url=url, | ||
headers={ | ||
"Authorization": f"Bearer {token}", | ||
"Accept": "application/json", | ||
}, | ||
params={"limit": chunk_size, "offset": offset}, | ||
) | ||
r.raise_for_status() | ||
|
||
if r.status_code in (429, 502): | ||
print(f"{url} return {r.status_code}, waiting and re-trying") | ||
time.sleep(10) | ||
return _requests_get(token, chunk_size, offset) | ||
|
||
x = r.json() | ||
df = pd.DataFrame(x) | ||
df.rename( | ||
columns={"securitiesInformationProcessorSymbolIdentifier": "symbol"}, | ||
inplace=True, | ||
) | ||
df.drop(["reportingFacilityCode", "marketCode"], axis=1, inplace=True) | ||
return df | ||
|
||
|
||
def get_chunk_and_size(token: str) -> Tuple[int, int]: | ||
"""Return the optimal chunk size and total number of data-points, | ||
Chunk size is used internally, by the process() function | ||
to reduce the number of calls to the FINRA end-point, | ||
it is also used as the 'offset' step when calling process() directly with restrictions. | ||
Input Arguments: token obtained from the auth() function. | ||
Returns: tuple with chunk size followed by number of data-points to be loaded from FINRA end-point. | ||
""" | ||
r = requests.get( | ||
url=url, | ||
headers={ | ||
"Authorization": f"Bearer {token}", | ||
"Accept": "application/json", | ||
}, | ||
params={"limit": 1}, | ||
) | ||
r.raise_for_status() | ||
|
||
return int(r.headers["Record-Max-Limit"]), int(r.headers["Record-Total"]) | ||
|
||
|
||
async def process( | ||
token: str, offset: int = 0, limit: Optional[int] = None | ||
) -> pd.DataFrame: | ||
"""Download Daily Short details | ||
Input Arguments: | ||
token -> obtained from the auth() function. | ||
offset -> starting point (default 0). | ||
limit -> end point (default not limit). | ||
Returns: If successful returns DataFrame with all details | ||
""" | ||
chunk_size, max_records = get_chunk_and_size(token) | ||
|
||
if limit: | ||
max_records = min(max_records, limit) | ||
|
||
print( | ||
f"loading data (chunk_size={chunk_size}, max_records={max_records-offset})..." | ||
) | ||
returned_df: pd.DataFrame = pd.DataFrame() | ||
with concurrent.futures.ThreadPoolExecutor() as executor: | ||
loop = asyncio.get_event_loop() | ||
futures = [ | ||
loop.run_in_executor( | ||
executor, _requests_get, token, chunk_size, offset | ||
) | ||
for offset in range(offset, max_records, chunk_size) | ||
] | ||
returned_df = pd.concat(await asyncio.gather(*futures)) | ||
print(returned_df.shape, max_records) | ||
|
||
return returned_df.groupby(["tradeReportDate", "symbol"]).sum() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[pytest] | ||
asyncio_mode=auto |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,6 @@ bandit | |
isort | ||
mypy | ||
requests | ||
pytest | ||
pytest-asyncio | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import os | ||
|
||
import pytest | ||
|
||
from finrashortdata import auth | ||
|
||
|
||
def test_auth_positive() -> bool: | ||
client_id = os.getenv("TEST_API_CLIENT_ID", None) | ||
secret = os.getenv("TEST_API_SECRET", None) | ||
|
||
if not client_id or not secret: | ||
raise AssertionError( | ||
"tests require env variables TEST_API_CLIENT_ID, TEST_API_SECRET" | ||
) | ||
|
||
token = auth(client_id, secret) | ||
|
||
print(token) | ||
|
||
return True | ||
|
||
|
||
def test_auth_no_type() -> bool: | ||
try: | ||
auth() # type: ignore | ||
except TypeError: | ||
return True | ||
|
||
raise AssertionError("Excepted TypeError exception") | ||
|
||
|
||
def test_auth_no_secret() -> bool: | ||
try: | ||
auth("id1") # type: ignore | ||
except TypeError: | ||
return True | ||
|
||
raise AssertionError("Excepted TypeError exception") | ||
|
||
|
||
def test_auth_none_values() -> bool: | ||
try: | ||
auth(None, "secret") # type: ignore | ||
except TypeError: | ||
return True | ||
|
||
raise AssertionError("Excepted TypeError exception") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import os | ||
|
||
import pandas as pd | ||
import pytest | ||
|
||
from finrashortdata import auth, get_chunk_and_size, process | ||
|
||
|
||
async def test_process_positive() -> bool: | ||
client_id = os.getenv("TEST_API_CLIENT_ID", None) | ||
secret = os.getenv("TEST_API_SECRET", None) | ||
|
||
if not client_id or not secret: | ||
raise AssertionError( | ||
"tests require env variables TEST_API_CLIENT_ID, TEST_API_SECRET" | ||
) | ||
token = auth(client_id, secret) | ||
chunk, max_data = get_chunk_and_size(token) | ||
_df: pd.DataFrame = await process( | ||
token=token, offset=max_data - 10 * chunk | ||
) | ||
print(_df) | ||
|
||
return True |