ticker-classifier 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.
- ticker_classifier-0.1.0/LICENSE +21 -0
- ticker_classifier-0.1.0/PKG-INFO +175 -0
- ticker_classifier-0.1.0/README.md +163 -0
- ticker_classifier-0.1.0/pyproject.toml +35 -0
- ticker_classifier-0.1.0/setup.cfg +4 -0
- ticker_classifier-0.1.0/ticker_classifier/__init__.py +4 -0
- ticker_classifier-0.1.0/ticker_classifier/apis/__init__.py +0 -0
- ticker_classifier-0.1.0/ticker_classifier/apis/coingecko.py +217 -0
- ticker_classifier-0.1.0/ticker_classifier/apis/yahoo.py +225 -0
- ticker_classifier-0.1.0/ticker_classifier/classifier.py +240 -0
- ticker_classifier-0.1.0/ticker_classifier/constants.py +192 -0
- ticker_classifier-0.1.0/ticker_classifier/db/__init__.py +0 -0
- ticker_classifier-0.1.0/ticker_classifier/db/cache.py +93 -0
- ticker_classifier-0.1.0/ticker_classifier.egg-info/PKG-INFO +175 -0
- ticker_classifier-0.1.0/ticker_classifier.egg-info/SOURCES.txt +16 -0
- ticker_classifier-0.1.0/ticker_classifier.egg-info/dependency_links.txt +1 -0
- ticker_classifier-0.1.0/ticker_classifier.egg-info/requires.txt +2 -0
- ticker_classifier-0.1.0/ticker_classifier.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Stephan Akkerman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ticker_classifier
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A robust stock, crypto, and forex classifier with async support.
|
|
5
|
+
Author-email: Stephan Akkerman <stephan@akkerman.ai>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: requests
|
|
10
|
+
Requires-Dist: aiohttp
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# ticker-classifier
|
|
14
|
+
|
|
15
|
+
<!-- Add a banner here like: https://github.com/StephanAkkerman/fintwit-bot/blob/main/img/logo/fintwit-banner.png -->
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
<!-- Adjust the link of the first and second badges to your own repo -->
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/StephanAkkerman/ticker-classifier/pyversions.yml?label=python%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13&logo=python&style=flat-square">
|
|
21
|
+
<img src="https://img.shields.io/github/license/StephanAkkerman/ticker-classifier.svg?color=brightgreen" alt="License">
|
|
22
|
+
<a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code style: black"></a>
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
## Introduction
|
|
26
|
+
|
|
27
|
+
`ticker-classifier` is a small Python library for classifying ticker-like symbols (for example `AAPL`, `BTC`, `EUR`, `GOLD`) into a simple market/category representation.
|
|
28
|
+
It uses Yahoo Finance for equities, CoinGecko for cryptocurrencies and a few heuristics for currencies/commodities. The output indicates the most likely category, a display name, market cap when available, and a `yahoo_lookup` value to fetch further data if desired.
|
|
29
|
+
|
|
30
|
+
## Table of Contents 🗂
|
|
31
|
+
|
|
32
|
+
- [Key Features](#key-features)
|
|
33
|
+
- [Installation](#installation)
|
|
34
|
+
- [Usage](#usage)
|
|
35
|
+
- [API](#api)
|
|
36
|
+
- [Development](#development)
|
|
37
|
+
- [Release and Versioning](#release-and-versioning)
|
|
38
|
+
- [Citation](#citation)
|
|
39
|
+
- [Contributing](#contributing)
|
|
40
|
+
- [License](#license)
|
|
41
|
+
|
|
42
|
+
## Key Features 🔑
|
|
43
|
+
|
|
44
|
+
- Classify symbols as `Equity`, `Crypto`, `Forex`, `Commodity`, `Index` or `Unknown`.
|
|
45
|
+
- Uses multiple public APIs and simple heuristics to make robust decisions.
|
|
46
|
+
- Provides both synchronous and asynchronous APIs.
|
|
47
|
+
- Lightweight disk cache to avoid repeated lookups (`TickerCache`).
|
|
48
|
+
|
|
49
|
+
## Installation ⚙️
|
|
50
|
+
|
|
51
|
+
Install from pip using the provided `requirements.txt` or install the package directly from the repository for latest changes:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install -r requirements.txt
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
or
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install git+https://github.com/StephanAkkerman/ticker-classifier.git
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Usage ⌨️
|
|
64
|
+
|
|
65
|
+
Basic synchronous usage:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from ticker_classifier.classifier import TickerClassifier
|
|
69
|
+
|
|
70
|
+
classifier = TickerClassifier()
|
|
71
|
+
symbols = ["AAPL", "BTC", "EUR", "GOLD", "UNKNOWN123"]
|
|
72
|
+
results = classifier.classify(symbols)
|
|
73
|
+
for r in results:
|
|
74
|
+
print(r)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Example asynchronous usage:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import asyncio
|
|
81
|
+
from ticker_classifier.classifier import TickerClassifier
|
|
82
|
+
|
|
83
|
+
async def main():
|
|
84
|
+
classifier = TickerClassifier()
|
|
85
|
+
symbols = ["AAPL", "BTC", "ETH", "JPY"]
|
|
86
|
+
results = await classifier.classify_async(symbols)
|
|
87
|
+
for r in results:
|
|
88
|
+
print(r)
|
|
89
|
+
|
|
90
|
+
asyncio.run(main())
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The output for each symbol is a dictionary like:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
{'category': 'EQUITY', 'ticker': 'AAPL', 'name': 'Apple Inc.', 'market_cap': 4029017227264, 'yahoo_lookup': 'AAPL', 'alternatives': ['crypto'], 'source': 'api'}
|
|
97
|
+
{'category': 'crypto', 'ticker': 'BTC', 'name': 'Bitcoin', 'market_cap': 1736590593460.9607, 'yahoo_lookup': 'BTC-USD', 'alternatives': ['stock'], 'source': 'api'}
|
|
98
|
+
{'category': 'crypto', 'ticker': 'ETH', 'name': 'Ethereum', 'market_cap': 338145915081.1455, 'yahoo_lookup': 'ETH-USD', 'alternatives': ['stock'], 'source': 'cache'}
|
|
99
|
+
{'category': 'forex', 'ticker': 'JPY', 'name': 'JPY Currency', 'market_cap': None, 'yahoo_lookup': 'JPYUSD=X', 'alternatives': ['stock'], 'source': 'cache'}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Notes
|
|
103
|
+
- The classifier caches positive classifications (non-`Unknown`) in an
|
|
104
|
+
SQLite database (default `ticker_cache.db`) for `24` hours by default.
|
|
105
|
+
- You can customize the cache filename and expiry by passing `db_name` and
|
|
106
|
+
`hours_to_expire` to `TickerClassifier`.
|
|
107
|
+
|
|
108
|
+
## API
|
|
109
|
+
|
|
110
|
+
- `ticker_classifier.classifier.TickerClassifier`
|
|
111
|
+
- `classify(symbols: List[str]) -> List[dict]` – synchronous classification.
|
|
112
|
+
- `classify_async(symbols: List[str]) -> List[dict]` – async classification.
|
|
113
|
+
- `ticker_classifier.apis.yahoo.YahooClient` – low-level Yahoo quote fetcher (sync + async helpers).
|
|
114
|
+
- `ticker_classifier.apis.coingecko.CoinGeckoClient` – crypto lookup + market cap helpers (sync + async).
|
|
115
|
+
- `ticker_classifier.db.cache.TickerCache` – tiny SQLite-backed cache used by `TickerClassifier`.
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
Run formatting and linting tools you prefer (project uses `black` code style).
|
|
120
|
+
|
|
121
|
+
Run a quick smoke check by running the `classifier.py` module directly:
|
|
122
|
+
|
|
123
|
+
```powershell
|
|
124
|
+
& .venv\Scripts\python.exe ticker_classifier\classifier.py
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
If you add tests, run them with your chosen test runner (e.g. `pytest`).
|
|
128
|
+
|
|
129
|
+
## Release and Versioning
|
|
130
|
+
|
|
131
|
+
This package is published to PyPI through GitHub Actions:
|
|
132
|
+
|
|
133
|
+
- Workflow: `.github/workflows/publish.yml`
|
|
134
|
+
- Trigger: GitHub Release published
|
|
135
|
+
- Publisher: `pypa/gh-action-pypi-publish` using trusted publishing (OIDC)
|
|
136
|
+
|
|
137
|
+
Release flow:
|
|
138
|
+
|
|
139
|
+
1. Update version in `pyproject.toml`.
|
|
140
|
+
2. Update `ticker_classifier/__init__.py` `__version__` to match.
|
|
141
|
+
3. Commit and push.
|
|
142
|
+
4. Create a GitHub release with tag `vX.Y.Z` (or `X.Y.Z`).
|
|
143
|
+
|
|
144
|
+
The publish workflow validates that the release tag version matches `pyproject.toml` before uploading to PyPI.
|
|
145
|
+
|
|
146
|
+
## Citation ✍️
|
|
147
|
+
If you use this project in your research, please cite as follows (adjust
|
|
148
|
+
metadata accordingly):
|
|
149
|
+
|
|
150
|
+
```bibtex
|
|
151
|
+
@misc{ticker-classifier,
|
|
152
|
+
author = {Stephan Akkerman},
|
|
153
|
+
title = {ticker-classifier},
|
|
154
|
+
year = {2025},
|
|
155
|
+
publisher = {GitHub},
|
|
156
|
+
howpublished = {\url{https://github.com/StephanAkkerman/ticker-classifier}}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Contributing 🛠
|
|
161
|
+
|
|
162
|
+
Contributions are welcome. Suggested workflow:
|
|
163
|
+
|
|
164
|
+
1. Fork the repository and create a feature branch.
|
|
165
|
+
2. Run tests and format your changes with `black`.
|
|
166
|
+
3. Open a pull request with a clear description of the change.
|
|
167
|
+
|
|
168
|
+
Please open issues for feature requests or bugs and include a small
|
|
169
|
+
reproducible example when possible.
|
|
170
|
+
|
|
171
|
+

|
|
172
|
+
|
|
173
|
+
## License 📜
|
|
174
|
+
|
|
175
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# ticker-classifier
|
|
2
|
+
|
|
3
|
+
<!-- Add a banner here like: https://github.com/StephanAkkerman/fintwit-bot/blob/main/img/logo/fintwit-banner.png -->
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
<!-- Adjust the link of the first and second badges to your own repo -->
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/StephanAkkerman/ticker-classifier/pyversions.yml?label=python%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13&logo=python&style=flat-square">
|
|
9
|
+
<img src="https://img.shields.io/github/license/StephanAkkerman/ticker-classifier.svg?color=brightgreen" alt="License">
|
|
10
|
+
<a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code style: black"></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
## Introduction
|
|
14
|
+
|
|
15
|
+
`ticker-classifier` is a small Python library for classifying ticker-like symbols (for example `AAPL`, `BTC`, `EUR`, `GOLD`) into a simple market/category representation.
|
|
16
|
+
It uses Yahoo Finance for equities, CoinGecko for cryptocurrencies and a few heuristics for currencies/commodities. The output indicates the most likely category, a display name, market cap when available, and a `yahoo_lookup` value to fetch further data if desired.
|
|
17
|
+
|
|
18
|
+
## Table of Contents 🗂
|
|
19
|
+
|
|
20
|
+
- [Key Features](#key-features)
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Usage](#usage)
|
|
23
|
+
- [API](#api)
|
|
24
|
+
- [Development](#development)
|
|
25
|
+
- [Release and Versioning](#release-and-versioning)
|
|
26
|
+
- [Citation](#citation)
|
|
27
|
+
- [Contributing](#contributing)
|
|
28
|
+
- [License](#license)
|
|
29
|
+
|
|
30
|
+
## Key Features 🔑
|
|
31
|
+
|
|
32
|
+
- Classify symbols as `Equity`, `Crypto`, `Forex`, `Commodity`, `Index` or `Unknown`.
|
|
33
|
+
- Uses multiple public APIs and simple heuristics to make robust decisions.
|
|
34
|
+
- Provides both synchronous and asynchronous APIs.
|
|
35
|
+
- Lightweight disk cache to avoid repeated lookups (`TickerCache`).
|
|
36
|
+
|
|
37
|
+
## Installation ⚙️
|
|
38
|
+
|
|
39
|
+
Install from pip using the provided `requirements.txt` or install the package directly from the repository for latest changes:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install -r requirements.txt
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
or
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install git+https://github.com/StephanAkkerman/ticker-classifier.git
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage ⌨️
|
|
52
|
+
|
|
53
|
+
Basic synchronous usage:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from ticker_classifier.classifier import TickerClassifier
|
|
57
|
+
|
|
58
|
+
classifier = TickerClassifier()
|
|
59
|
+
symbols = ["AAPL", "BTC", "EUR", "GOLD", "UNKNOWN123"]
|
|
60
|
+
results = classifier.classify(symbols)
|
|
61
|
+
for r in results:
|
|
62
|
+
print(r)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Example asynchronous usage:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import asyncio
|
|
69
|
+
from ticker_classifier.classifier import TickerClassifier
|
|
70
|
+
|
|
71
|
+
async def main():
|
|
72
|
+
classifier = TickerClassifier()
|
|
73
|
+
symbols = ["AAPL", "BTC", "ETH", "JPY"]
|
|
74
|
+
results = await classifier.classify_async(symbols)
|
|
75
|
+
for r in results:
|
|
76
|
+
print(r)
|
|
77
|
+
|
|
78
|
+
asyncio.run(main())
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The output for each symbol is a dictionary like:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
{'category': 'EQUITY', 'ticker': 'AAPL', 'name': 'Apple Inc.', 'market_cap': 4029017227264, 'yahoo_lookup': 'AAPL', 'alternatives': ['crypto'], 'source': 'api'}
|
|
85
|
+
{'category': 'crypto', 'ticker': 'BTC', 'name': 'Bitcoin', 'market_cap': 1736590593460.9607, 'yahoo_lookup': 'BTC-USD', 'alternatives': ['stock'], 'source': 'api'}
|
|
86
|
+
{'category': 'crypto', 'ticker': 'ETH', 'name': 'Ethereum', 'market_cap': 338145915081.1455, 'yahoo_lookup': 'ETH-USD', 'alternatives': ['stock'], 'source': 'cache'}
|
|
87
|
+
{'category': 'forex', 'ticker': 'JPY', 'name': 'JPY Currency', 'market_cap': None, 'yahoo_lookup': 'JPYUSD=X', 'alternatives': ['stock'], 'source': 'cache'}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Notes
|
|
91
|
+
- The classifier caches positive classifications (non-`Unknown`) in an
|
|
92
|
+
SQLite database (default `ticker_cache.db`) for `24` hours by default.
|
|
93
|
+
- You can customize the cache filename and expiry by passing `db_name` and
|
|
94
|
+
`hours_to_expire` to `TickerClassifier`.
|
|
95
|
+
|
|
96
|
+
## API
|
|
97
|
+
|
|
98
|
+
- `ticker_classifier.classifier.TickerClassifier`
|
|
99
|
+
- `classify(symbols: List[str]) -> List[dict]` – synchronous classification.
|
|
100
|
+
- `classify_async(symbols: List[str]) -> List[dict]` – async classification.
|
|
101
|
+
- `ticker_classifier.apis.yahoo.YahooClient` – low-level Yahoo quote fetcher (sync + async helpers).
|
|
102
|
+
- `ticker_classifier.apis.coingecko.CoinGeckoClient` – crypto lookup + market cap helpers (sync + async).
|
|
103
|
+
- `ticker_classifier.db.cache.TickerCache` – tiny SQLite-backed cache used by `TickerClassifier`.
|
|
104
|
+
|
|
105
|
+
## Development
|
|
106
|
+
|
|
107
|
+
Run formatting and linting tools you prefer (project uses `black` code style).
|
|
108
|
+
|
|
109
|
+
Run a quick smoke check by running the `classifier.py` module directly:
|
|
110
|
+
|
|
111
|
+
```powershell
|
|
112
|
+
& .venv\Scripts\python.exe ticker_classifier\classifier.py
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
If you add tests, run them with your chosen test runner (e.g. `pytest`).
|
|
116
|
+
|
|
117
|
+
## Release and Versioning
|
|
118
|
+
|
|
119
|
+
This package is published to PyPI through GitHub Actions:
|
|
120
|
+
|
|
121
|
+
- Workflow: `.github/workflows/publish.yml`
|
|
122
|
+
- Trigger: GitHub Release published
|
|
123
|
+
- Publisher: `pypa/gh-action-pypi-publish` using trusted publishing (OIDC)
|
|
124
|
+
|
|
125
|
+
Release flow:
|
|
126
|
+
|
|
127
|
+
1. Update version in `pyproject.toml`.
|
|
128
|
+
2. Update `ticker_classifier/__init__.py` `__version__` to match.
|
|
129
|
+
3. Commit and push.
|
|
130
|
+
4. Create a GitHub release with tag `vX.Y.Z` (or `X.Y.Z`).
|
|
131
|
+
|
|
132
|
+
The publish workflow validates that the release tag version matches `pyproject.toml` before uploading to PyPI.
|
|
133
|
+
|
|
134
|
+
## Citation ✍️
|
|
135
|
+
If you use this project in your research, please cite as follows (adjust
|
|
136
|
+
metadata accordingly):
|
|
137
|
+
|
|
138
|
+
```bibtex
|
|
139
|
+
@misc{ticker-classifier,
|
|
140
|
+
author = {Stephan Akkerman},
|
|
141
|
+
title = {ticker-classifier},
|
|
142
|
+
year = {2025},
|
|
143
|
+
publisher = {GitHub},
|
|
144
|
+
howpublished = {\url{https://github.com/StephanAkkerman/ticker-classifier}}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Contributing 🛠
|
|
149
|
+
|
|
150
|
+
Contributions are welcome. Suggested workflow:
|
|
151
|
+
|
|
152
|
+
1. Fork the repository and create a feature branch.
|
|
153
|
+
2. Run tests and format your changes with `black`.
|
|
154
|
+
3. Open a pull request with a clear description of the change.
|
|
155
|
+
|
|
156
|
+
Please open issues for feature requests or bugs and include a small
|
|
157
|
+
reproducible example when possible.
|
|
158
|
+
|
|
159
|
+

|
|
160
|
+
|
|
161
|
+
## License 📜
|
|
162
|
+
|
|
163
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ticker_classifier"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A robust stock, crypto, and forex classifier with async support."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.8"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Stephan Akkerman", email = "stephan@akkerman.ai" },
|
|
9
|
+
]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"requests",
|
|
12
|
+
"aiohttp"
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["setuptools>=61.0"]
|
|
17
|
+
build-backend = "setuptools.build_meta"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
include = ["ticker_classifier*"]
|
|
21
|
+
|
|
22
|
+
[tool.isort]
|
|
23
|
+
multi_line_output = 3
|
|
24
|
+
include_trailing_comma = true
|
|
25
|
+
force_grid_wrap = 0
|
|
26
|
+
line_length = 88
|
|
27
|
+
profile = "black"
|
|
28
|
+
|
|
29
|
+
[tool.ruff]
|
|
30
|
+
line-length = 88
|
|
31
|
+
#select = ["I001"]
|
|
32
|
+
|
|
33
|
+
[tool.ruff.lint.pydocstyle]
|
|
34
|
+
# Use Google-style docstrings.
|
|
35
|
+
convention = "numpy"
|
|
File without changes
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
import aiohttp
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CoinGeckoClient:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
"""Initialize CoinGecko client.
|
|
11
|
+
|
|
12
|
+
Sets up base endpoints used for retrieving the coin list and simple
|
|
13
|
+
price information and initializes an internal cache for symbol -> id
|
|
14
|
+
mappings.
|
|
15
|
+
|
|
16
|
+
Notes
|
|
17
|
+
-----
|
|
18
|
+
The client keeps an in-memory `_crypto_map` that maps uppercase
|
|
19
|
+
symbols to a list of CoinGecko ids. This map is populated lazily by
|
|
20
|
+
`_load_map_sync` or `_load_map_async` when price lookup is requested.
|
|
21
|
+
"""
|
|
22
|
+
self.list_url = "https://api.coingecko.com/api/v3/coins/list"
|
|
23
|
+
self.price_url = "https://api.coingecko.com/api/v3/simple/price"
|
|
24
|
+
self._crypto_map = None # { 'BTC': ['bitcoin', 'bitcoin-token'], ... }
|
|
25
|
+
|
|
26
|
+
def _load_map_sync(self):
|
|
27
|
+
"""Load the CoinGecko symbol->id map synchronously.
|
|
28
|
+
|
|
29
|
+
This method fetches the full coin list from the CoinGecko API and
|
|
30
|
+
populates the in-memory `_crypto_map` mapping uppercase symbol strings
|
|
31
|
+
to lists of CoinGecko ids. If the map is already loaded this is a
|
|
32
|
+
no-op.
|
|
33
|
+
|
|
34
|
+
Errors
|
|
35
|
+
------
|
|
36
|
+
Any exceptions raised while fetching/parsing are caught and the map
|
|
37
|
+
falls back to an empty dict.
|
|
38
|
+
"""
|
|
39
|
+
if self._crypto_map:
|
|
40
|
+
return
|
|
41
|
+
try:
|
|
42
|
+
resp = requests.get(self.list_url, timeout=10)
|
|
43
|
+
data = resp.json()
|
|
44
|
+
self._crypto_map = defaultdict(list)
|
|
45
|
+
for coin in data:
|
|
46
|
+
self._crypto_map[coin["symbol"].upper()].append(coin["id"])
|
|
47
|
+
except Exception:
|
|
48
|
+
self._crypto_map = {}
|
|
49
|
+
|
|
50
|
+
async def _load_map_async(self, session: aiohttp.ClientSession):
|
|
51
|
+
"""Asynchronously load the CoinGecko symbol->id map.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
session : aiohttp.ClientSession
|
|
56
|
+
Active aiohttp session used for making the HTTP request.
|
|
57
|
+
|
|
58
|
+
Notes
|
|
59
|
+
-----
|
|
60
|
+
This is the async counterpart to `_load_map_sync`. If the internal map
|
|
61
|
+
is already populated this method returns immediately. Exceptions are
|
|
62
|
+
caught and the map will be set to an empty dict on failure.
|
|
63
|
+
"""
|
|
64
|
+
if self._crypto_map:
|
|
65
|
+
return
|
|
66
|
+
try:
|
|
67
|
+
async with session.get(self.list_url) as resp:
|
|
68
|
+
data = await resp.json()
|
|
69
|
+
self._crypto_map = defaultdict(list)
|
|
70
|
+
for coin in data:
|
|
71
|
+
self._crypto_map[coin["symbol"].upper()].append(coin["id"])
|
|
72
|
+
except Exception:
|
|
73
|
+
self._crypto_map = {}
|
|
74
|
+
|
|
75
|
+
def _get_candidate_ids(
|
|
76
|
+
self, symbols: List[str]
|
|
77
|
+
) -> tuple[List[str], Dict[str, str]]:
|
|
78
|
+
"""Return candidate CoinGecko ids for a list of symbols.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
symbols : list[str]
|
|
83
|
+
Uppercase ticker symbols to map to CoinGecko ids.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
ids : list[str]
|
|
88
|
+
Flat list of candidate CoinGecko ids (limited to first 10
|
|
89
|
+
collisions per symbol).
|
|
90
|
+
id_to_parent : dict
|
|
91
|
+
Mapping of coin id -> original symbol (parent) used to group
|
|
92
|
+
results later.
|
|
93
|
+
"""
|
|
94
|
+
ids = []
|
|
95
|
+
id_to_parent = {}
|
|
96
|
+
if not self._crypto_map:
|
|
97
|
+
return ids, id_to_parent
|
|
98
|
+
|
|
99
|
+
for sym in symbols:
|
|
100
|
+
if sym in self._crypto_map:
|
|
101
|
+
# Top 10 collisions only
|
|
102
|
+
for cid in self._crypto_map[sym][:10]:
|
|
103
|
+
ids.append(cid)
|
|
104
|
+
id_to_parent[cid] = sym
|
|
105
|
+
return ids, id_to_parent
|
|
106
|
+
|
|
107
|
+
def get_prices_sync(self, symbols: List[str]) -> Dict[str, Dict]:
|
|
108
|
+
"""Synchronous price lookup for a list of symbols using CoinGecko.
|
|
109
|
+
|
|
110
|
+
This method ensures the internal symbol->id map is loaded, finds
|
|
111
|
+
candidate CoinGecko ids for the requested symbols, and retrieves USD
|
|
112
|
+
prices and market caps in chunks. The highest market cap candidate is
|
|
113
|
+
selected per symbol in `_process_response`.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
symbols : list[str]
|
|
118
|
+
Uppercase ticker symbols to look up.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
dict[str, dict]
|
|
123
|
+
Mapping of symbol -> {"market_cap": ..., "name": ..., "id": ...}
|
|
124
|
+
for matches found. Returns an empty dict if nothing matched.
|
|
125
|
+
"""
|
|
126
|
+
self._load_map_sync()
|
|
127
|
+
results = {}
|
|
128
|
+
ids, id_map = self._get_candidate_ids(symbols)
|
|
129
|
+
if not ids:
|
|
130
|
+
return results
|
|
131
|
+
|
|
132
|
+
chunk_size = 200
|
|
133
|
+
for i in range(0, len(ids), chunk_size):
|
|
134
|
+
chunk = ids[i : i + chunk_size]
|
|
135
|
+
try:
|
|
136
|
+
resp = requests.get(
|
|
137
|
+
self.price_url,
|
|
138
|
+
params={
|
|
139
|
+
"ids": ",".join(chunk),
|
|
140
|
+
"vs_currencies": "usd",
|
|
141
|
+
"include_market_cap": "true",
|
|
142
|
+
},
|
|
143
|
+
timeout=10,
|
|
144
|
+
)
|
|
145
|
+
data = resp.json()
|
|
146
|
+
self._process_response(data, id_map, results)
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
return results
|
|
150
|
+
|
|
151
|
+
async def get_prices_async(
|
|
152
|
+
self, session: aiohttp.ClientSession, symbols: List[str]
|
|
153
|
+
) -> Dict[str, Dict]:
|
|
154
|
+
"""Asynchronously retrieve prices and market caps for symbols.
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
session : aiohttp.ClientSession
|
|
159
|
+
Active aiohttp session used to make HTTP requests.
|
|
160
|
+
symbols : list[str]
|
|
161
|
+
Uppercase ticker symbols to query.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
dict[str, dict]
|
|
166
|
+
Mapping of symbol -> {"market_cap": ..., "name": ..., "id": ...}.
|
|
167
|
+
|
|
168
|
+
Notes
|
|
169
|
+
-----
|
|
170
|
+
Uses the async map loader `_load_map_async` and requests CoinGecko in
|
|
171
|
+
chunks. Failures for a chunk are swallowed and processing continues.
|
|
172
|
+
"""
|
|
173
|
+
await self._load_map_async(session)
|
|
174
|
+
results = {}
|
|
175
|
+
ids, id_map = self._get_candidate_ids(symbols)
|
|
176
|
+
if not ids:
|
|
177
|
+
return results
|
|
178
|
+
|
|
179
|
+
chunk_size = 200
|
|
180
|
+
for i in range(0, len(ids), chunk_size):
|
|
181
|
+
chunk = ids[i : i + chunk_size]
|
|
182
|
+
try:
|
|
183
|
+
params = {
|
|
184
|
+
"ids": ",".join(chunk),
|
|
185
|
+
"vs_currencies": "usd",
|
|
186
|
+
"include_market_cap": "true",
|
|
187
|
+
}
|
|
188
|
+
async with session.get(self.price_url, params=params) as resp:
|
|
189
|
+
data = await resp.json()
|
|
190
|
+
self._process_response(data, id_map, results)
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
return results
|
|
194
|
+
|
|
195
|
+
def _process_response(self, data, id_map, results):
|
|
196
|
+
"""Process a CoinGecko price response and update results.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
data : dict
|
|
201
|
+
JSON-decoded response from the CoinGecko simple/price endpoint.
|
|
202
|
+
id_map : dict
|
|
203
|
+
Mapping of coin id -> parent symbol used to group results.
|
|
204
|
+
results : dict
|
|
205
|
+
Mutable mapping that will be updated in-place with the best
|
|
206
|
+
candidate per parent symbol (highest market cap wins).
|
|
207
|
+
"""
|
|
208
|
+
for cid, val in data.items():
|
|
209
|
+
parent = id_map.get(cid)
|
|
210
|
+
if parent:
|
|
211
|
+
mcap = val.get("usd_market_cap", 0)
|
|
212
|
+
if mcap > results.get(parent, {}).get("market_cap", 0):
|
|
213
|
+
results[parent] = {
|
|
214
|
+
"market_cap": mcap,
|
|
215
|
+
"name": cid.title(),
|
|
216
|
+
"id": cid,
|
|
217
|
+
}
|