followsm-sdk 0.1.0__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.
- followsm_sdk-0.1.0/PKG-INFO +46 -0
- followsm_sdk-0.1.0/README.md +30 -0
- followsm_sdk-0.1.0/followsm_sdk/__init__.py +16 -0
- followsm_sdk-0.1.0/followsm_sdk/client.py +79 -0
- followsm_sdk-0.1.0/followsm_sdk/exceptions.py +22 -0
- followsm_sdk-0.1.0/followsm_sdk/models.py +27 -0
- followsm_sdk-0.1.0/pyproject.toml +23 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: followsm-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the FollowSM smart-money & orderbook toxicity API
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: requests>=2.31
|
|
8
|
+
Requires-Dist: pydantic>=2.0
|
|
9
|
+
Requires-Dist: websockets>=12.0
|
|
10
|
+
Requires-Dist: pytest>=8.0 ; extra == "test"
|
|
11
|
+
Requires-Dist: pytest-mock>=3.12 ; extra == "test"
|
|
12
|
+
Project-URL: Documentation, https://follow-sm.com/docs
|
|
13
|
+
Project-URL: Homepage, https://follow-sm.com
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
|
|
16
|
+
# followsm-sdk
|
|
17
|
+
|
|
18
|
+
Official Python SDK for the [FollowSM](https://follow-sm.com) smart-money & orderbook toxicity API.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install followsm-sdk
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from followsm_sdk import FollowSMClient
|
|
30
|
+
|
|
31
|
+
client = FollowSMClient(api_key="fsm_live_...") # or omit to use the free tier
|
|
32
|
+
snapshot = client.get_toxicity_snapshot("BTCUSDT")
|
|
33
|
+
print(snapshot.vpin, snapshot.is_toxic_alert)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Free vs Developer API
|
|
37
|
+
|
|
38
|
+
| | Free / Unauthenticated | DEVELOPER_API ($199/mo) |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| Requests/min | 30 | 300 |
|
|
41
|
+
| WebSocket connections | 1 | Multiple |
|
|
42
|
+
| Binance pairs | Limited | 50+ |
|
|
43
|
+
| Latency | Standard | Sub-10ms in-memory snapshots |
|
|
44
|
+
|
|
45
|
+
On HTTP 429, the SDK raises `RateLimitExceededException` with an upgrade prompt pointing to https://follow-sm.com/pricing.
|
|
46
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# followsm-sdk
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [FollowSM](https://follow-sm.com) smart-money & orderbook toxicity API.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install followsm-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from followsm_sdk import FollowSMClient
|
|
15
|
+
|
|
16
|
+
client = FollowSMClient(api_key="fsm_live_...") # or omit to use the free tier
|
|
17
|
+
snapshot = client.get_toxicity_snapshot("BTCUSDT")
|
|
18
|
+
print(snapshot.vpin, snapshot.is_toxic_alert)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Free vs Developer API
|
|
22
|
+
|
|
23
|
+
| | Free / Unauthenticated | DEVELOPER_API ($199/mo) |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Requests/min | 30 | 300 |
|
|
26
|
+
| WebSocket connections | 1 | Multiple |
|
|
27
|
+
| Binance pairs | Limited | 50+ |
|
|
28
|
+
| Latency | Standard | Sub-10ms in-memory snapshots |
|
|
29
|
+
|
|
30
|
+
On HTTP 429, the SDK raises `RateLimitExceededException` with an upgrade prompt pointing to https://follow-sm.com/pricing.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""followsm-sdk — official Python client for the FollowSM API."""
|
|
2
|
+
|
|
3
|
+
from .client import FollowSMClient
|
|
4
|
+
from .exceptions import AuthenticationError, FollowSMError, RateLimitExceededException
|
|
5
|
+
from .models import OrderBookDepthBand, SymbolToxicityMetrics
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"FollowSMClient",
|
|
9
|
+
"FollowSMError",
|
|
10
|
+
"RateLimitExceededException",
|
|
11
|
+
"AuthenticationError",
|
|
12
|
+
"SymbolToxicityMetrics",
|
|
13
|
+
"OrderBookDepthBand",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Core synchronous + async streaming client for the FollowSM API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import AsyncIterator, List, Optional
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
import websockets
|
|
10
|
+
|
|
11
|
+
from .exceptions import AuthenticationError, RateLimitExceededException
|
|
12
|
+
from .models import SymbolToxicityMetrics
|
|
13
|
+
|
|
14
|
+
DEFAULT_BASE_URL = "https://follow-sm.com/api/v1"
|
|
15
|
+
DEFAULT_WS_URL = "wss://follow-sm.com/api/v1/developer/toxicity/stream"
|
|
16
|
+
FREE_COMMUNITY_KEY = "FREE_COMMUNITY_KEY"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FollowSMClient:
|
|
20
|
+
"""Client for the FollowSM Developer API.
|
|
21
|
+
|
|
22
|
+
Falls back to the shared FREE_COMMUNITY_KEY (30 req/min, unauthenticated
|
|
23
|
+
tier) when no `api_key` is supplied.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
api_key: Optional[str] = None,
|
|
29
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
30
|
+
ws_url: str = DEFAULT_WS_URL,
|
|
31
|
+
timeout: float = 10.0,
|
|
32
|
+
):
|
|
33
|
+
self.api_key = api_key or FREE_COMMUNITY_KEY
|
|
34
|
+
self.base_url = base_url.rstrip("/")
|
|
35
|
+
self.ws_url = ws_url
|
|
36
|
+
self.timeout = timeout
|
|
37
|
+
|
|
38
|
+
def _headers(self) -> dict:
|
|
39
|
+
return {"X-FollowSM-Key": self.api_key}
|
|
40
|
+
|
|
41
|
+
def _handle_response(self, response: requests.Response) -> requests.Response:
|
|
42
|
+
if response.status_code == 429:
|
|
43
|
+
reset_time = int(response.headers.get("X-RateLimit-Reset", 0))
|
|
44
|
+
raise RateLimitExceededException(
|
|
45
|
+
response.json().get("detail", "Rate limit exceeded")
|
|
46
|
+
if response.content
|
|
47
|
+
else "Rate limit exceeded",
|
|
48
|
+
reset_time,
|
|
49
|
+
)
|
|
50
|
+
if response.status_code in (401, 403):
|
|
51
|
+
raise AuthenticationError(response.text)
|
|
52
|
+
response.raise_for_status()
|
|
53
|
+
return response
|
|
54
|
+
|
|
55
|
+
def get_toxicity_snapshot(self, symbol: str) -> SymbolToxicityMetrics:
|
|
56
|
+
response = requests.get(
|
|
57
|
+
f"{self.base_url}/developer/toxicity/snapshot",
|
|
58
|
+
params={"symbol": symbol},
|
|
59
|
+
headers=self._headers(),
|
|
60
|
+
timeout=self.timeout,
|
|
61
|
+
)
|
|
62
|
+
self._handle_response(response)
|
|
63
|
+
return SymbolToxicityMetrics.model_validate(response.json())
|
|
64
|
+
|
|
65
|
+
def get_toxic_pairs(self) -> List[SymbolToxicityMetrics]:
|
|
66
|
+
response = requests.get(
|
|
67
|
+
f"{self.base_url}/developer/toxicity/toxic-pairs",
|
|
68
|
+
headers=self._headers(),
|
|
69
|
+
timeout=self.timeout,
|
|
70
|
+
)
|
|
71
|
+
self._handle_response(response)
|
|
72
|
+
return [SymbolToxicityMetrics.model_validate(item) for item in response.json()]
|
|
73
|
+
|
|
74
|
+
async def stream_toxicity(self) -> AsyncIterator[SymbolToxicityMetrics]:
|
|
75
|
+
"""Async-iterate live toxicity snapshots over the WebSocket feed."""
|
|
76
|
+
uri = f"{self.ws_url}?api_key={self.api_key}"
|
|
77
|
+
async with websockets.connect(uri) as ws:
|
|
78
|
+
async for message in ws:
|
|
79
|
+
yield SymbolToxicityMetrics.model_validate(json.loads(message))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Custom exceptions raised by the FollowSM SDK."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FollowSMError(Exception):
|
|
5
|
+
"""Base class for all FollowSM SDK errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RateLimitExceededException(FollowSMError):
|
|
9
|
+
def __init__(self, message: str, reset_time: int):
|
|
10
|
+
self.reset_time = reset_time
|
|
11
|
+
upgrade_url = "https://follow-sm.com/pricing"
|
|
12
|
+
formatted_msg = (
|
|
13
|
+
f"\n\033[91m[FollowSM RateLimitExceeded]\033[0m {message}\n"
|
|
14
|
+
f"\033[93m⚡️ Free Tier Limit Reached (30 req/min).\033[0m\n"
|
|
15
|
+
f"Unlock 300 req/min, 50+ Binance pairs & sub-10ms latency:\n"
|
|
16
|
+
f"👉 \033[94m{upgrade_url}\033[0m\n"
|
|
17
|
+
)
|
|
18
|
+
super().__init__(formatted_msg)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AuthenticationError(FollowSMError):
|
|
22
|
+
"""Raised on HTTP 401/403 responses (invalid key or insufficient plan)."""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Pydantic data models mirroring backend/models/toxicity_models.py."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OrderBookDepthBand(BaseModel):
|
|
11
|
+
bid_notional: float
|
|
12
|
+
ask_notional: float
|
|
13
|
+
imbalance_ratio: float
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SymbolToxicityMetrics(BaseModel):
|
|
17
|
+
symbol: str
|
|
18
|
+
timestamp: float
|
|
19
|
+
price: float
|
|
20
|
+
vpin: float
|
|
21
|
+
ob_toxicity_1pct: float
|
|
22
|
+
ob_imbalance_l1: float
|
|
23
|
+
depth_bands: Dict[str, OrderBookDepthBand]
|
|
24
|
+
volume_z_score: float
|
|
25
|
+
natr_15m: float
|
|
26
|
+
taker_buy_ratio: float
|
|
27
|
+
is_toxic_alert: bool
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["flit_core>=3.9,<4"]
|
|
3
|
+
build-backend = "flit_core.buildapi"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "followsm-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the FollowSM smart-money & orderbook toxicity API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = [
|
|
13
|
+
"requests>=2.31",
|
|
14
|
+
"pydantic>=2.0",
|
|
15
|
+
"websockets>=12.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
test = ["pytest>=8.0", "pytest-mock>=3.12"]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://follow-sm.com"
|
|
23
|
+
Documentation = "https://follow-sm.com/docs"
|