classifiedai 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.
- classifiedai-0.1.0/PKG-INFO +228 -0
- classifiedai-0.1.0/README.md +209 -0
- classifiedai-0.1.0/classified/__init__.py +0 -0
- classifiedai-0.1.0/classified/api/__init__.py +1 -0
- classifiedai-0.1.0/classified/api/app.py +96 -0
- classifiedai-0.1.0/classified/api/models.py +42 -0
- classifiedai-0.1.0/classified/api/routes.py +110 -0
- classifiedai-0.1.0/classified/api/server.py +431 -0
- classifiedai-0.1.0/classified/api/stream.py +190 -0
- classifiedai-0.1.0/classified/api/test_api.py +104 -0
- classifiedai-0.1.0/classified/api/test_stream.py +51 -0
- classifiedai-0.1.0/classified/exchange/__init__.py +17 -0
- classifiedai-0.1.0/classified/exchange/base.py +66 -0
- classifiedai-0.1.0/classified/exchange/ccxt_adapter.py +220 -0
- classifiedai-0.1.0/classified/exchange/market_stream.py +292 -0
- classifiedai-0.1.0/classified/exchange/test_ccxt_adapter.py +56 -0
- classifiedai-0.1.0/classified/exchange/test_ccxt_adapter_current_ticker.py +22 -0
- classifiedai-0.1.0/classified/market_data/__init__.py +0 -0
- classifiedai-0.1.0/classified/market_data/cache.py +187 -0
- classifiedai-0.1.0/classified/market_data/context.py +346 -0
- classifiedai-0.1.0/classified/market_data/data_requests.py +92 -0
- classifiedai-0.1.0/classified/market_data/freshness.py +135 -0
- classifiedai-0.1.0/classified/market_data/manager.py +340 -0
- classifiedai-0.1.0/classified/market_data/models.py +111 -0
- classifiedai-0.1.0/classified/market_data/normalizer.py +217 -0
- classifiedai-0.1.0/classified/market_data/test_cache.py +110 -0
- classifiedai-0.1.0/classified/market_data/test_cache_integration.py +152 -0
- classifiedai-0.1.0/classified/market_data/test_context.py +55 -0
- classifiedai-0.1.0/classified/market_data/test_freshness.py +64 -0
- classifiedai-0.1.0/classified/market_data/test_manager.py +58 -0
- classifiedai-0.1.0/classified/market_data/test_multi_request.py +80 -0
- classifiedai-0.1.0/classified/market_data/test_normalizer.py +144 -0
- classifiedai-0.1.0/classified/market_data/test_request.py +58 -0
- classifiedai-0.1.0/classifiedai.egg-info/PKG-INFO +228 -0
- classifiedai-0.1.0/classifiedai.egg-info/SOURCES.txt +38 -0
- classifiedai-0.1.0/classifiedai.egg-info/dependency_links.txt +1 -0
- classifiedai-0.1.0/classifiedai.egg-info/requires.txt +13 -0
- classifiedai-0.1.0/classifiedai.egg-info/top_level.txt +1 -0
- classifiedai-0.1.0/pyproject.toml +29 -0
- classifiedai-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: classifiedai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Classified trading analysis platform
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: ccxt
|
|
8
|
+
Requires-Dist: pandas
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: aiohttp
|
|
11
|
+
Requires-Dist: python-dotenv
|
|
12
|
+
Requires-Dist: requests
|
|
13
|
+
Requires-Dist: matplotlib
|
|
14
|
+
Requires-Dist: mplfinance
|
|
15
|
+
Requires-Dist: ta
|
|
16
|
+
Requires-Dist: websockets
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest; extra == "test"
|
|
19
|
+
|
|
20
|
+
# CLASSIFIED
|
|
21
|
+
|
|
22
|
+
### Market Data Infrastructure · Real-Time Streaming · Multi-Timeframe Intelligence
|
|
23
|
+
|
|
24
|
+
**A unified market-data infrastructure package for exchange connectivity, normalized OHLCV data, multi-timeframe requests, and HTTP/WebSocket delivery.**
|
|
25
|
+
|
|
26
|
+
CLASSIFIED provides shared market-data infrastructure for charting applications, analytical engines, and specialist trading systems. It centralizes exchange connectivity and market-data handling so independent consumers can work with a consistent foundation rather than implementing their own exchange integrations.
|
|
27
|
+
|
|
28
|
+
The package is published under the distribution name `classifiedai`, while its Python import namespace remains `classified`.
|
|
29
|
+
|
|
30
|
+
> **Core principle:** One market-data foundation. Multiple independent consumers. A consistent data contract.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **Exchange connectivity** — integrate with supported exchanges through CCXT.
|
|
37
|
+
- **Normalized OHLCV data** — work with Open, High, Low, Close, and Volume candle data.
|
|
38
|
+
- **Multi-timeframe requests** — request market data at different candle intervals.
|
|
39
|
+
- **Historical market data** — retrieve candles for charting and analysis.
|
|
40
|
+
- **Adaptive candle requirements** — centralize candle-count requirements for data consumers where supported by the active implementation.
|
|
41
|
+
- **HTTP API** — expose market data through HTTP endpoints.
|
|
42
|
+
- **WebSocket streaming** — support persistent connections for live market-data delivery.
|
|
43
|
+
- **Shared infrastructure** — allow independent applications to reuse the same market-data foundation.
|
|
44
|
+
|
|
45
|
+
CLASSIFIED is infrastructure, not a trading strategy or trade-execution engine.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
Install the package from PyPI:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python -m pip install classifiedai
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Requirements
|
|
58
|
+
|
|
59
|
+
- Python 3.12 or newer.
|
|
60
|
+
- Network access to the configured exchange where live exchange data is required.
|
|
61
|
+
- Appropriate exchange configuration for the selected data source.
|
|
62
|
+
|
|
63
|
+
### Verify the installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python -c "import classified; print(classified.__file__)"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The distribution name used by `pip` is `classifiedai`. The import namespace used in Python remains `classified`.
|
|
70
|
+
|
|
71
|
+
### Install optional test dependencies
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python -m pip install "classifiedai[test]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This command is available when the published release includes the `test` optional dependency extra.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Architecture
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
EXCHANGE DATA SOURCE
|
|
85
|
+
|
|
|
86
|
+
v
|
|
87
|
+
+----------------------+
|
|
88
|
+
| CLASSIFIED Core |
|
|
89
|
+
| |
|
|
90
|
+
| Exchange Connectivity|
|
|
91
|
+
| OHLCV Normalization |
|
|
92
|
+
| Multi-Timeframe Data |
|
|
93
|
+
| Candle Requirements |
|
|
94
|
+
| Cache & Freshness |
|
|
95
|
+
+----------+-----------+
|
|
96
|
+
|
|
|
97
|
+
v
|
|
98
|
+
+----------------------+
|
|
99
|
+
| API Gateway |
|
|
100
|
+
| |
|
|
101
|
+
| HTTP / REST |
|
|
102
|
+
| WebSocket Streaming |
|
|
103
|
+
+----------+-----------+
|
|
104
|
+
|
|
|
105
|
+
+------------+------------+
|
|
106
|
+
| | |
|
|
107
|
+
v v v
|
|
108
|
+
Visualization Analytical Other Data
|
|
109
|
+
Applications Systems Consumers
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
*Conceptual architecture. The precise responsibilities and available capabilities depend on the installed implementation and its configuration.*
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Market Data
|
|
117
|
+
|
|
118
|
+
CLASSIFIED is designed to provide a common interface for market-data consumers that need historical candles, multiple timeframes, or continuous updates.
|
|
119
|
+
|
|
120
|
+
### OHLCV data
|
|
121
|
+
|
|
122
|
+
OHLCV represents:
|
|
123
|
+
|
|
124
|
+
| Field | Meaning |
|
|
125
|
+
|---|---|
|
|
126
|
+
| Open | Opening price for a candle |
|
|
127
|
+
| High | Highest price reached during the candle |
|
|
128
|
+
| Low | Lowest price reached during the candle |
|
|
129
|
+
| Close | Closing price for a candle |
|
|
130
|
+
| Volume | Volume recorded during the candle |
|
|
131
|
+
|
|
132
|
+
### Multi-timeframe access
|
|
133
|
+
|
|
134
|
+
Example candle intervals include:
|
|
135
|
+
|
|
136
|
+
| Timeframe | Typical analytical use |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `4h` | Higher-timeframe market structure |
|
|
139
|
+
| `1h` | Intermediate market context |
|
|
140
|
+
| `15m` | Intraday price action |
|
|
141
|
+
| `5m` | Short-term price monitoring |
|
|
142
|
+
|
|
143
|
+
These are example intervals, not trading signals. Actual supported intervals and candle limits depend on the exchange and implementation.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## API Gateway
|
|
148
|
+
|
|
149
|
+
The gateway provides HTTP-based access and WebSocket streaming through an asynchronous server using `aiohttp`.
|
|
150
|
+
|
|
151
|
+
The documented local server address is:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
http://127.0.0.1:8080
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The server must be started separately when using the gateway endpoints.
|
|
158
|
+
|
|
159
|
+
### Start the gateway
|
|
160
|
+
|
|
161
|
+
From an environment where CLASSIFIED and its dependencies are installed, run:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
python -m classified.api.server
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The server is configured to listen on port `8080` in the documented setup.
|
|
168
|
+
|
|
169
|
+
### 1. Health check
|
|
170
|
+
|
|
171
|
+
**Method:** `GET`
|
|
172
|
+
|
|
173
|
+
```http
|
|
174
|
+
GET /health
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Local URL:
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
http://127.0.0.1:8080/health
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Use this endpoint to check whether the gateway is responding.
|
|
184
|
+
|
|
185
|
+
### 2. Historical candle data
|
|
186
|
+
|
|
187
|
+
**Method:** `GET`
|
|
188
|
+
|
|
189
|
+
```http
|
|
190
|
+
GET /api/v1/market-data/candles
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Query parameters:
|
|
194
|
+
|
|
195
|
+
| Parameter | Description | Example |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `symbol` | Trading pair | `BTC/USDT` |
|
|
198
|
+
| `timeframe` | Candle interval | `5m` |
|
|
199
|
+
| `candle_count` | Requested candle count | `150` |
|
|
200
|
+
|
|
201
|
+
Example URL:
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
http://127.0.0.1:8080/api/v1/market-data/candles?symbol=BTC%2FUSDT&timeframe=5m&candle_count=150
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
PowerShell example:
|
|
208
|
+
|
|
209
|
+
```powershell
|
|
210
|
+
Invoke-RestMethod `
|
|
211
|
+
"http://127.0.0.1:8080/api/v1/market-data/candles?symbol=BTC%2FUSDT&timeframe=5m&candle_count=150"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The endpoint is intended to supply historical candle data to charting and analytical consumers. Consult the active server implementation for the exact response schema and error behavior.
|
|
215
|
+
|
|
216
|
+
### 3. Live market-data stream
|
|
217
|
+
|
|
218
|
+
**Protocol:** WebSocket
|
|
219
|
+
|
|
220
|
+
```text
|
|
221
|
+
ws://127.0.0.1:8080/api/v1/market-data/stream
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Query parameters:
|
|
225
|
+
|
|
226
|
+
| Parameter | Description | Example |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `symbol`
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# CLASSIFIED
|
|
2
|
+
|
|
3
|
+
### Market Data Infrastructure · Real-Time Streaming · Multi-Timeframe Intelligence
|
|
4
|
+
|
|
5
|
+
**A unified market-data infrastructure package for exchange connectivity, normalized OHLCV data, multi-timeframe requests, and HTTP/WebSocket delivery.**
|
|
6
|
+
|
|
7
|
+
CLASSIFIED provides shared market-data infrastructure for charting applications, analytical engines, and specialist trading systems. It centralizes exchange connectivity and market-data handling so independent consumers can work with a consistent foundation rather than implementing their own exchange integrations.
|
|
8
|
+
|
|
9
|
+
The package is published under the distribution name `classifiedai`, while its Python import namespace remains `classified`.
|
|
10
|
+
|
|
11
|
+
> **Core principle:** One market-data foundation. Multiple independent consumers. A consistent data contract.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Exchange connectivity** — integrate with supported exchanges through CCXT.
|
|
18
|
+
- **Normalized OHLCV data** — work with Open, High, Low, Close, and Volume candle data.
|
|
19
|
+
- **Multi-timeframe requests** — request market data at different candle intervals.
|
|
20
|
+
- **Historical market data** — retrieve candles for charting and analysis.
|
|
21
|
+
- **Adaptive candle requirements** — centralize candle-count requirements for data consumers where supported by the active implementation.
|
|
22
|
+
- **HTTP API** — expose market data through HTTP endpoints.
|
|
23
|
+
- **WebSocket streaming** — support persistent connections for live market-data delivery.
|
|
24
|
+
- **Shared infrastructure** — allow independent applications to reuse the same market-data foundation.
|
|
25
|
+
|
|
26
|
+
CLASSIFIED is infrastructure, not a trading strategy or trade-execution engine.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
Install the package from PyPI:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m pip install classifiedai
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Requirements
|
|
39
|
+
|
|
40
|
+
- Python 3.12 or newer.
|
|
41
|
+
- Network access to the configured exchange where live exchange data is required.
|
|
42
|
+
- Appropriate exchange configuration for the selected data source.
|
|
43
|
+
|
|
44
|
+
### Verify the installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python -c "import classified; print(classified.__file__)"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The distribution name used by `pip` is `classifiedai`. The import namespace used in Python remains `classified`.
|
|
51
|
+
|
|
52
|
+
### Install optional test dependencies
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python -m pip install "classifiedai[test]"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This command is available when the published release includes the `test` optional dependency extra.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Architecture
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
EXCHANGE DATA SOURCE
|
|
66
|
+
|
|
|
67
|
+
v
|
|
68
|
+
+----------------------+
|
|
69
|
+
| CLASSIFIED Core |
|
|
70
|
+
| |
|
|
71
|
+
| Exchange Connectivity|
|
|
72
|
+
| OHLCV Normalization |
|
|
73
|
+
| Multi-Timeframe Data |
|
|
74
|
+
| Candle Requirements |
|
|
75
|
+
| Cache & Freshness |
|
|
76
|
+
+----------+-----------+
|
|
77
|
+
|
|
|
78
|
+
v
|
|
79
|
+
+----------------------+
|
|
80
|
+
| API Gateway |
|
|
81
|
+
| |
|
|
82
|
+
| HTTP / REST |
|
|
83
|
+
| WebSocket Streaming |
|
|
84
|
+
+----------+-----------+
|
|
85
|
+
|
|
|
86
|
+
+------------+------------+
|
|
87
|
+
| | |
|
|
88
|
+
v v v
|
|
89
|
+
Visualization Analytical Other Data
|
|
90
|
+
Applications Systems Consumers
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
*Conceptual architecture. The precise responsibilities and available capabilities depend on the installed implementation and its configuration.*
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Market Data
|
|
98
|
+
|
|
99
|
+
CLASSIFIED is designed to provide a common interface for market-data consumers that need historical candles, multiple timeframes, or continuous updates.
|
|
100
|
+
|
|
101
|
+
### OHLCV data
|
|
102
|
+
|
|
103
|
+
OHLCV represents:
|
|
104
|
+
|
|
105
|
+
| Field | Meaning |
|
|
106
|
+
|---|---|
|
|
107
|
+
| Open | Opening price for a candle |
|
|
108
|
+
| High | Highest price reached during the candle |
|
|
109
|
+
| Low | Lowest price reached during the candle |
|
|
110
|
+
| Close | Closing price for a candle |
|
|
111
|
+
| Volume | Volume recorded during the candle |
|
|
112
|
+
|
|
113
|
+
### Multi-timeframe access
|
|
114
|
+
|
|
115
|
+
Example candle intervals include:
|
|
116
|
+
|
|
117
|
+
| Timeframe | Typical analytical use |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `4h` | Higher-timeframe market structure |
|
|
120
|
+
| `1h` | Intermediate market context |
|
|
121
|
+
| `15m` | Intraday price action |
|
|
122
|
+
| `5m` | Short-term price monitoring |
|
|
123
|
+
|
|
124
|
+
These are example intervals, not trading signals. Actual supported intervals and candle limits depend on the exchange and implementation.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## API Gateway
|
|
129
|
+
|
|
130
|
+
The gateway provides HTTP-based access and WebSocket streaming through an asynchronous server using `aiohttp`.
|
|
131
|
+
|
|
132
|
+
The documented local server address is:
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
http://127.0.0.1:8080
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The server must be started separately when using the gateway endpoints.
|
|
139
|
+
|
|
140
|
+
### Start the gateway
|
|
141
|
+
|
|
142
|
+
From an environment where CLASSIFIED and its dependencies are installed, run:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
python -m classified.api.server
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The server is configured to listen on port `8080` in the documented setup.
|
|
149
|
+
|
|
150
|
+
### 1. Health check
|
|
151
|
+
|
|
152
|
+
**Method:** `GET`
|
|
153
|
+
|
|
154
|
+
```http
|
|
155
|
+
GET /health
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Local URL:
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
http://127.0.0.1:8080/health
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Use this endpoint to check whether the gateway is responding.
|
|
165
|
+
|
|
166
|
+
### 2. Historical candle data
|
|
167
|
+
|
|
168
|
+
**Method:** `GET`
|
|
169
|
+
|
|
170
|
+
```http
|
|
171
|
+
GET /api/v1/market-data/candles
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Query parameters:
|
|
175
|
+
|
|
176
|
+
| Parameter | Description | Example |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `symbol` | Trading pair | `BTC/USDT` |
|
|
179
|
+
| `timeframe` | Candle interval | `5m` |
|
|
180
|
+
| `candle_count` | Requested candle count | `150` |
|
|
181
|
+
|
|
182
|
+
Example URL:
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
http://127.0.0.1:8080/api/v1/market-data/candles?symbol=BTC%2FUSDT&timeframe=5m&candle_count=150
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
PowerShell example:
|
|
189
|
+
|
|
190
|
+
```powershell
|
|
191
|
+
Invoke-RestMethod `
|
|
192
|
+
"http://127.0.0.1:8080/api/v1/market-data/candles?symbol=BTC%2FUSDT&timeframe=5m&candle_count=150"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The endpoint is intended to supply historical candle data to charting and analytical consumers. Consult the active server implementation for the exact response schema and error behavior.
|
|
196
|
+
|
|
197
|
+
### 3. Live market-data stream
|
|
198
|
+
|
|
199
|
+
**Protocol:** WebSocket
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
ws://127.0.0.1:8080/api/v1/market-data/stream
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Query parameters:
|
|
206
|
+
|
|
207
|
+
| Parameter | Description | Example |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| `symbol`
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Classified market-data transport APIs."""
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from aiohttp import web
|
|
6
|
+
|
|
7
|
+
from classified.exchange.ccxt_adapter import CCXTExchangeAdapter
|
|
8
|
+
from classified.market_data.manager import MarketDataManager
|
|
9
|
+
|
|
10
|
+
from .routes import register_routes
|
|
11
|
+
from .stream import create_stream_registry
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _env_bool(name: str, default: bool = False) -> bool:
|
|
15
|
+
value = os.getenv(name)
|
|
16
|
+
if value is None:
|
|
17
|
+
return default
|
|
18
|
+
return value.strip().lower() in {"1", "true", "yes", "on"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_manager() -> MarketDataManager:
|
|
22
|
+
exchange_id = os.getenv("CLASSIFIED_EXCHANGE_ID", "bingx").strip().lower()
|
|
23
|
+
adapter = CCXTExchangeAdapter(
|
|
24
|
+
exchange_id,
|
|
25
|
+
api_key=os.getenv("CLASSIFIED_EXCHANGE_API_KEY") or None,
|
|
26
|
+
secret=os.getenv("CLASSIFIED_EXCHANGE_SECRET") or None,
|
|
27
|
+
password=os.getenv("CLASSIFIED_EXCHANGE_PASSWORD") or None,
|
|
28
|
+
sandbox=_env_bool("CLASSIFIED_EXCHANGE_SANDBOX", False),
|
|
29
|
+
enable_rate_limit=True,
|
|
30
|
+
timeout=int(os.getenv("CLASSIFIED_EXCHANGE_TIMEOUT_MS", "30000")),
|
|
31
|
+
)
|
|
32
|
+
return MarketDataManager(adapter)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _cors_middleware() -> web.middleware:
|
|
36
|
+
raw = os.getenv(
|
|
37
|
+
"CLASSIFIED_API_CORS_ORIGINS",
|
|
38
|
+
"http://localhost:5173,http://127.0.0.1:5173",
|
|
39
|
+
)
|
|
40
|
+
allowed_origins = frozenset(
|
|
41
|
+
origin.strip() for origin in raw.split(",") if origin.strip()
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@web.middleware
|
|
45
|
+
async def middleware(request: web.Request, handler):
|
|
46
|
+
if request.method == "OPTIONS":
|
|
47
|
+
response = web.Response(status=204)
|
|
48
|
+
else:
|
|
49
|
+
response = await handler(request)
|
|
50
|
+
|
|
51
|
+
origin = request.headers.get("Origin")
|
|
52
|
+
if origin in allowed_origins:
|
|
53
|
+
response.headers["Access-Control-Allow-Origin"] = origin
|
|
54
|
+
response.headers["Vary"] = "Origin"
|
|
55
|
+
response.headers["Access-Control-Allow-Methods"] = "GET,OPTIONS"
|
|
56
|
+
response.headers["Access-Control-Allow-Headers"] = "Content-Type"
|
|
57
|
+
|
|
58
|
+
return response
|
|
59
|
+
|
|
60
|
+
return middleware
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def _on_startup(app: web.Application) -> None:
|
|
64
|
+
manager: MarketDataManager = app["market_data_manager"]
|
|
65
|
+
manager.connect()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def _on_cleanup(app: web.Application) -> None:
|
|
69
|
+
registry = app.get("market_stream_registry")
|
|
70
|
+
if registry is not None:
|
|
71
|
+
await registry.close()
|
|
72
|
+
|
|
73
|
+
manager: MarketDataManager = app["market_data_manager"]
|
|
74
|
+
manager.close()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def create_app(manager: MarketDataManager | None = None) -> web.Application:
|
|
78
|
+
resolved_manager = manager or create_manager()
|
|
79
|
+
app = web.Application(middlewares=[_cors_middleware()])
|
|
80
|
+
register_routes(app, resolved_manager)
|
|
81
|
+
app["market_stream_registry"] = create_stream_registry(
|
|
82
|
+
resolved_manager.exchange_name
|
|
83
|
+
)
|
|
84
|
+
app.on_startup.append(_on_startup)
|
|
85
|
+
app.on_cleanup.append(_on_cleanup)
|
|
86
|
+
return app
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main() -> None:
|
|
90
|
+
host = os.getenv("CLASSIFIED_API_HOST", "127.0.0.1")
|
|
91
|
+
port = int(os.getenv("CLASSIFIED_API_PORT", "8080"))
|
|
92
|
+
web.run_app(create_app(), host=host, port=port)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
main()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import timezone
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from classified.market_data.manager import MarketDataResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def candle_to_dict(candle: Any) -> dict[str, Any]:
|
|
10
|
+
"""Serialize one Classified Candle for external clients."""
|
|
11
|
+
timestamp = candle.timestamp
|
|
12
|
+
|
|
13
|
+
if timestamp.tzinfo is None or timestamp.utcoffset() is None:
|
|
14
|
+
timestamp = timestamp.replace(tzinfo=timezone.utc)
|
|
15
|
+
else:
|
|
16
|
+
timestamp = timestamp.astimezone(timezone.utc)
|
|
17
|
+
|
|
18
|
+
epoch_seconds = int(timestamp.timestamp())
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
"timestamp": epoch_seconds,
|
|
22
|
+
"timestampMs": epoch_seconds * 1000,
|
|
23
|
+
"open": candle.open,
|
|
24
|
+
"high": candle.high,
|
|
25
|
+
"low": candle.low,
|
|
26
|
+
"close": candle.close,
|
|
27
|
+
"volume": candle.volume,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def market_data_result_to_dict(result: MarketDataResult) -> dict[str, Any]:
|
|
32
|
+
"""Serialize a market-data result without exposing internal objects."""
|
|
33
|
+
return {
|
|
34
|
+
"symbol": result.symbol,
|
|
35
|
+
"timeframe": result.timeframe,
|
|
36
|
+
"purpose": result.request.purpose,
|
|
37
|
+
"requestedCandleCount": result.request.candle_count,
|
|
38
|
+
"candleCount": result.candle_count,
|
|
39
|
+
"fromCache": result.from_cache,
|
|
40
|
+
"refreshed": result.refreshed,
|
|
41
|
+
"candles": [candle_to_dict(candle) for candle in result.candles],
|
|
42
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from aiohttp import web
|
|
4
|
+
|
|
5
|
+
from classified.market_data.data_requests import MarketDataRequest
|
|
6
|
+
from classified.market_data.manager import (
|
|
7
|
+
MarketDataManager,
|
|
8
|
+
MarketDataUnavailableError,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .models import market_data_result_to_dict
|
|
12
|
+
from .stream import market_stream
|
|
13
|
+
|
|
14
|
+
SUPPORTED_TIMEFRAMES: frozenset[str] = frozenset(
|
|
15
|
+
{
|
|
16
|
+
"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h",
|
|
17
|
+
"6h", "8h", "12h", "1d", "3d", "1w", "1M",
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
SUPPORTED_PURPOSES: frozenset[str] = frozenset(
|
|
22
|
+
{
|
|
23
|
+
"higher_timeframe", "structure", "setup", "execution",
|
|
24
|
+
"indicator", "liquidity", "general",
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _error(message: str, status: int) -> web.Response:
|
|
30
|
+
return web.json_response(
|
|
31
|
+
{"error": {"message": message, "status": status}},
|
|
32
|
+
status=status,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _positive_int(value: str | None, *, name: str, default: int) -> int:
|
|
37
|
+
if value is None or value == "":
|
|
38
|
+
return default
|
|
39
|
+
try:
|
|
40
|
+
parsed = int(value)
|
|
41
|
+
except ValueError as exc:
|
|
42
|
+
raise ValueError(f"{name} must be an integer.") from exc
|
|
43
|
+
if parsed < 1:
|
|
44
|
+
raise ValueError(f"{name} must be at least 1.")
|
|
45
|
+
return parsed
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def register_routes(app: web.Application, manager: MarketDataManager) -> None:
|
|
49
|
+
app["market_data_manager"] = manager
|
|
50
|
+
app.router.add_get("/health", health)
|
|
51
|
+
app.router.add_get("/api/v1/market-data/candles", candles)
|
|
52
|
+
app.router.add_get("/api/v1/market-data/stream", market_stream)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def health(request: web.Request) -> web.Response:
|
|
56
|
+
manager: MarketDataManager = request.app["market_data_manager"]
|
|
57
|
+
return web.json_response(
|
|
58
|
+
{
|
|
59
|
+
"status": "ok",
|
|
60
|
+
"service": "classified",
|
|
61
|
+
"exchange": manager.exchange_name,
|
|
62
|
+
"exchangeConnected": manager.is_connected(),
|
|
63
|
+
"liveMarketStream": True,
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def candles(request: web.Request) -> web.Response:
|
|
69
|
+
manager: MarketDataManager = request.app["market_data_manager"]
|
|
70
|
+
symbol = request.query.get("symbol", "").strip()
|
|
71
|
+
timeframe = request.query.get("timeframe", "").strip()
|
|
72
|
+
purpose = request.query.get("purpose", "general").strip()
|
|
73
|
+
|
|
74
|
+
if not symbol:
|
|
75
|
+
return _error("Query parameter 'symbol' is required.", 400)
|
|
76
|
+
if not timeframe:
|
|
77
|
+
return _error("Query parameter 'timeframe' is required.", 400)
|
|
78
|
+
if timeframe not in SUPPORTED_TIMEFRAMES:
|
|
79
|
+
return _error("Unsupported timeframe.", 400)
|
|
80
|
+
if purpose not in SUPPORTED_PURPOSES:
|
|
81
|
+
return _error("Unsupported purpose.", 400)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
candle_count = _positive_int(
|
|
85
|
+
request.query.get("candle_count"),
|
|
86
|
+
name="candle_count",
|
|
87
|
+
default=150,
|
|
88
|
+
)
|
|
89
|
+
except ValueError as exc:
|
|
90
|
+
return _error(str(exc), 400)
|
|
91
|
+
|
|
92
|
+
if candle_count > 1000:
|
|
93
|
+
return _error("candle_count cannot exceed 1000.", 400)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
market_request = MarketDataRequest(
|
|
97
|
+
symbol=symbol,
|
|
98
|
+
timeframe=timeframe, # type: ignore[arg-type]
|
|
99
|
+
candle_count=candle_count,
|
|
100
|
+
purpose=purpose, # type: ignore[arg-type]
|
|
101
|
+
)
|
|
102
|
+
result = manager.fulfill_request(market_request)
|
|
103
|
+
except MarketDataUnavailableError as exc:
|
|
104
|
+
return _error(str(exc), 503)
|
|
105
|
+
except ValueError as exc:
|
|
106
|
+
return _error(str(exc), 400)
|
|
107
|
+
except Exception as exc:
|
|
108
|
+
return _error(f"Unexpected market-data error: {exc}", 500)
|
|
109
|
+
|
|
110
|
+
return web.json_response({"data": market_data_result_to_dict(result)})
|