ccxt-gateway 1.8.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.
Files changed (61) hide show
  1. ccxt_gateway-1.8.0/.gitignore +44 -0
  2. ccxt_gateway-1.8.0/LICENSE +21 -0
  3. ccxt_gateway-1.8.0/PKG-INFO +124 -0
  4. ccxt_gateway-1.8.0/README.md +90 -0
  5. ccxt_gateway-1.8.0/config/default.yaml +49 -0
  6. ccxt_gateway-1.8.0/pyproject.toml +84 -0
  7. ccxt_gateway-1.8.0/src/ccxt_gateway/__init__.py +8 -0
  8. ccxt_gateway-1.8.0/src/ccxt_gateway/api/__init__.py +1 -0
  9. ccxt_gateway-1.8.0/src/ccxt_gateway/api/admin.py +267 -0
  10. ccxt_gateway-1.8.0/src/ccxt_gateway/api/rest.py +246 -0
  11. ccxt_gateway-1.8.0/src/ccxt_gateway/api/websocket.py +209 -0
  12. ccxt_gateway-1.8.0/src/ccxt_gateway/config.py +119 -0
  13. ccxt_gateway-1.8.0/src/ccxt_gateway/core/__init__.py +1 -0
  14. ccxt_gateway-1.8.0/src/ccxt_gateway/core/process_manager.py +380 -0
  15. ccxt_gateway-1.8.0/src/ccxt_gateway/core/protocol.py +120 -0
  16. ccxt_gateway-1.8.0/src/ccxt_gateway/core/zmq_broker.py +188 -0
  17. ccxt_gateway-1.8.0/src/ccxt_gateway/daemon_gateway.py +154 -0
  18. ccxt_gateway-1.8.0/src/ccxt_gateway/exchange/__init__.py +1 -0
  19. ccxt_gateway-1.8.0/src/ccxt_gateway/exchange/hotfixes.py +79 -0
  20. ccxt_gateway-1.8.0/src/ccxt_gateway/exchange/methods.py +67 -0
  21. ccxt_gateway-1.8.0/src/ccxt_gateway/exchange/subprocess.py +593 -0
  22. ccxt_gateway-1.8.0/src/ccxt_gateway/main.py +215 -0
  23. ccxt_gateway-1.8.0/src/ccxt_gateway/models/__init__.py +1 -0
  24. ccxt_gateway-1.8.0/src/ccxt_gateway/utils/__init__.py +1 -0
  25. ccxt_gateway-1.8.0/src/ccxt_gateway/utils/updates.py +153 -0
  26. ccxt_gateway-1.8.0/tests/conftest.py +37 -0
  27. ccxt_gateway-1.8.0/tests/test_100.py +14 -0
  28. ccxt_gateway-1.8.0/tests/test_admin_api.py +269 -0
  29. ccxt_gateway-1.8.0/tests/test_api_fixed.py +113 -0
  30. ccxt_gateway-1.8.0/tests/test_comprehensive.py +405 -0
  31. ccxt_gateway-1.8.0/tests/test_coverage_100.py +395 -0
  32. ccxt_gateway-1.8.0/tests/test_easiest_gaps.py +131 -0
  33. ccxt_gateway-1.8.0/tests/test_easy_gaps.py +50 -0
  34. ccxt_gateway-1.8.0/tests/test_edge2.py +50 -0
  35. ccxt_gateway-1.8.0/tests/test_edge_cases.py +78 -0
  36. ccxt_gateway-1.8.0/tests/test_final_100.py +174 -0
  37. ccxt_gateway-1.8.0/tests/test_final_gaps.py +168 -0
  38. ccxt_gateway-1.8.0/tests/test_final_gaps2.py +11 -0
  39. ccxt_gateway-1.8.0/tests/test_integration_proper.py +63 -0
  40. ccxt_gateway-1.8.0/tests/test_loops2.py +95 -0
  41. ccxt_gateway-1.8.0/tests/test_main_and_pm.py +183 -0
  42. ccxt_gateway-1.8.0/tests/test_methods.py +109 -0
  43. ccxt_gateway-1.8.0/tests/test_more2.py +236 -0
  44. ccxt_gateway-1.8.0/tests/test_more_coverage.py +224 -0
  45. ccxt_gateway-1.8.0/tests/test_more_coverage2.py +59 -0
  46. ccxt_gateway-1.8.0/tests/test_more_gaps.py +120 -0
  47. ccxt_gateway-1.8.0/tests/test_pm_coverage.py +253 -0
  48. ccxt_gateway-1.8.0/tests/test_push1.py +27 -0
  49. ccxt_gateway-1.8.0/tests/test_push2.py +44 -0
  50. ccxt_gateway-1.8.0/tests/test_push3.py +34 -0
  51. ccxt_gateway-1.8.0/tests/test_push4.py +14 -0
  52. ccxt_gateway-1.8.0/tests/test_push5.py +38 -0
  53. ccxt_gateway-1.8.0/tests/test_push6.py +54 -0
  54. ccxt_gateway-1.8.0/tests/test_push7.py +73 -0
  55. ccxt_gateway-1.8.0/tests/test_push_higher.py +178 -0
  56. ccxt_gateway-1.8.0/tests/test_remaining.py +185 -0
  57. ccxt_gateway-1.8.0/tests/test_rest_api.py +306 -0
  58. ccxt_gateway-1.8.0/tests/test_subprocess_basic.py +310 -0
  59. ccxt_gateway-1.8.0/tests/test_updates_more.py +161 -0
  60. ccxt_gateway-1.8.0/tests/test_websocket.py +103 -0
  61. ccxt_gateway-1.8.0/tests/test_zmq_simple.py +148 -0
@@ -0,0 +1,44 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+
24
+ # Virtual environments
25
+ venv/
26
+ ENV/
27
+ env/
28
+
29
+ # IDEs
30
+ .vscode/
31
+ .idea/
32
+ *.swp
33
+ *.swo
34
+ *~
35
+
36
+ # OS
37
+ .DS_Store
38
+ Thumbs.db
39
+
40
+ # Project specific
41
+ .env
42
+ logs/
43
+ *.log
44
+ .cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 ccxt-gateway contributors
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,124 @@
1
+ Metadata-Version: 2.5
2
+ Name: ccxt-gateway
3
+ Version: 1.8.0
4
+ Summary: High-performance self-hosted gateway for 100+ crypto exchanges via CCXT
5
+ Author: ccxt-gateway contributors
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: aiofiles>=24.1.0
10
+ Requires-Dist: ccxt[pro]>=4.4.0
11
+ Requires-Dist: fastapi>=0.115.0
12
+ Requires-Dist: httpx>=0.27.0
13
+ Requires-Dist: orjson>=3.10.0
14
+ Requires-Dist: packaging>=24.0
15
+ Requires-Dist: psutil>=6.0.0
16
+ Requires-Dist: pydantic-settings>=2.14.2
17
+ Requires-Dist: python-dotenv>=1.0.0
18
+ Requires-Dist: pyyaml>=6.0.0
19
+ Requires-Dist: pyzmq>=26.0.0
20
+ Requires-Dist: setuptools>=83.0.0
21
+ Requires-Dist: uvicorn[standard]>=0.30.0
22
+ Requires-Dist: uvloop>=0.20.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: httpx>=0.27.0; extra == 'dev'
25
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
26
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
27
+ Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
29
+ Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
30
+ Requires-Dist: types-requests>=2.31.0; extra == 'dev'
31
+ Provides-Extra: server
32
+ Requires-Dist: granian>=1.4.0; extra == 'server'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # ccxt-gateway
36
+
37
+ High-performance, self-hosted gateway for 100+ cryptocurrency exchanges via CCXT.
38
+
39
+ ## Features
40
+
41
+ - **High Performance**: Built with FastAPI, uvloop, and orjson for maximum throughput
42
+ - **Rock Solid Stability**: Multi-process design with automatic restart on failures
43
+ - **Memory Management**: Automatic subprocess restart when memory limits exceeded
44
+ - **WebSocket Support**: Proxy CCXT watch* functions via WebSocket connections
45
+ - **Auto-Update**: Automatic CCXT version management
46
+ - **Unified API**: Single REST and WebSocket API for all supported exchanges
47
+
48
+ ## Quick Start
49
+
50
+ ### Installation
51
+
52
+ ```bash
53
+ # Clone the repository
54
+ git clone https://github.com/yourusername/ccxt-gateway.git
55
+ cd ccxt-gateway
56
+
57
+ # Install dependencies
58
+ pip install -e ".[server]"
59
+ ```
60
+
61
+ ### Running
62
+
63
+ ```bash
64
+ # Start the gateway
65
+ ccxt-gateway
66
+
67
+ # Or directly with Python
68
+ python -m ccxt_gateway.main
69
+ ```
70
+
71
+ The gateway will start on `http://0.0.0.0:8000` by default.
72
+
73
+ ### Basic Usage
74
+
75
+ ```bash
76
+ # Create a Binance exchange instance
77
+ curl -X POST "http://localhost:8000/exchanges/my-binance?exchange_name=binance"
78
+
79
+ # Get ticker for BTC/USDT
80
+ curl "http://localhost:8000/exchanges/my-binance/fetch_ticker?symbol=BTC/USDT"
81
+
82
+ # Delete the exchange instance
83
+ curl -X DELETE "http://localhost:8000/exchanges/my-binance"
84
+ ```
85
+
86
+ ## Configuration
87
+
88
+ Configuration is loaded from (in order):
89
+ 1. Environment variables (prefixed with `CCXT_GATEWAY_`)
90
+ 2. `.env` file
91
+ 3. `config/default.yaml`
92
+
93
+ See `config/default.yaml` for all available options.
94
+
95
+ ## API Documentation
96
+
97
+ Once running, visit:
98
+ - Swagger UI: `http://localhost:8000/docs`
99
+ - ReDoc: `http://localhost:8000/redoc`
100
+
101
+ ## Architecture
102
+
103
+ ```
104
+ Client -> FastAPI -> ZMQ Broker -> Exchange Subprocess (CCXT)
105
+ ```
106
+
107
+ Each exchange runs in its own subprocess, communicating via ZeroMQ for isolation and stability.
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ # Install dev dependencies
113
+ pip install -e ".[dev]"
114
+
115
+ # Run tests
116
+ pytest
117
+
118
+ # Format code
119
+ black src/
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT
@@ -0,0 +1,90 @@
1
+ # ccxt-gateway
2
+
3
+ High-performance, self-hosted gateway for 100+ cryptocurrency exchanges via CCXT.
4
+
5
+ ## Features
6
+
7
+ - **High Performance**: Built with FastAPI, uvloop, and orjson for maximum throughput
8
+ - **Rock Solid Stability**: Multi-process design with automatic restart on failures
9
+ - **Memory Management**: Automatic subprocess restart when memory limits exceeded
10
+ - **WebSocket Support**: Proxy CCXT watch* functions via WebSocket connections
11
+ - **Auto-Update**: Automatic CCXT version management
12
+ - **Unified API**: Single REST and WebSocket API for all supported exchanges
13
+
14
+ ## Quick Start
15
+
16
+ ### Installation
17
+
18
+ ```bash
19
+ # Clone the repository
20
+ git clone https://github.com/yourusername/ccxt-gateway.git
21
+ cd ccxt-gateway
22
+
23
+ # Install dependencies
24
+ pip install -e ".[server]"
25
+ ```
26
+
27
+ ### Running
28
+
29
+ ```bash
30
+ # Start the gateway
31
+ ccxt-gateway
32
+
33
+ # Or directly with Python
34
+ python -m ccxt_gateway.main
35
+ ```
36
+
37
+ The gateway will start on `http://0.0.0.0:8000` by default.
38
+
39
+ ### Basic Usage
40
+
41
+ ```bash
42
+ # Create a Binance exchange instance
43
+ curl -X POST "http://localhost:8000/exchanges/my-binance?exchange_name=binance"
44
+
45
+ # Get ticker for BTC/USDT
46
+ curl "http://localhost:8000/exchanges/my-binance/fetch_ticker?symbol=BTC/USDT"
47
+
48
+ # Delete the exchange instance
49
+ curl -X DELETE "http://localhost:8000/exchanges/my-binance"
50
+ ```
51
+
52
+ ## Configuration
53
+
54
+ Configuration is loaded from (in order):
55
+ 1. Environment variables (prefixed with `CCXT_GATEWAY_`)
56
+ 2. `.env` file
57
+ 3. `config/default.yaml`
58
+
59
+ See `config/default.yaml` for all available options.
60
+
61
+ ## API Documentation
62
+
63
+ Once running, visit:
64
+ - Swagger UI: `http://localhost:8000/docs`
65
+ - ReDoc: `http://localhost:8000/redoc`
66
+
67
+ ## Architecture
68
+
69
+ ```
70
+ Client -> FastAPI -> ZMQ Broker -> Exchange Subprocess (CCXT)
71
+ ```
72
+
73
+ Each exchange runs in its own subprocess, communicating via ZeroMQ for isolation and stability.
74
+
75
+ ## Development
76
+
77
+ ```bash
78
+ # Install dev dependencies
79
+ pip install -e ".[dev]"
80
+
81
+ # Run tests
82
+ pytest
83
+
84
+ # Format code
85
+ black src/
86
+ ```
87
+
88
+ ## License
89
+
90
+ MIT
@@ -0,0 +1,49 @@
1
+ # ccxt-gateway default configuration
2
+
3
+ server:
4
+ host: "0.0.0.0"
5
+ port: 8999
6
+ debug: false
7
+ use_ssl: false
8
+ ssl_cert: null
9
+ ssl_key: null
10
+ # Use granian for higher performance (pip install ccxt-gateway[server])
11
+ use_granian: false
12
+
13
+ zmq:
14
+ # Main broker address (for main process to communicate with subprocesses)
15
+ broker_address: "tcp://127.0.0.1:5557"
16
+ # Subprocess connect to this address
17
+ subprocess_connect: "tcp://127.0.0.1:5557"
18
+ # High water mark for ZMQ (prevent memory issues)
19
+ high_water_mark: 1000
20
+ # Timeout for request-reply (ms)
21
+ timeout_ms: 30000
22
+
23
+ process_manager:
24
+ # Maximum RSS per exchange subprocess (in MB)
25
+ max_rss_mb: 512
26
+ # Interval to check memory usage (seconds)
27
+ check_interval: 30
28
+ # Auto-restart subprocesses on memory limit or crash
29
+ auto_restart: true
30
+ # Maximum restarts per hour per exchange
31
+ max_restarts_per_hour: 5
32
+ # Subprocess startup timeout (seconds)
33
+ startup_timeout: 30
34
+
35
+ update:
36
+ # Check for ccxt updates interval (hours, 0 to disable)
37
+ check_interval_hours: 24
38
+ # Auto-update ccxt when new version available
39
+ auto_update: false
40
+ # PyPI package name for ccxt
41
+ pypi_package: "ccxt"
42
+
43
+ exchanges:
44
+ # Default settings for exchange instances
45
+ default_enable_rate_limit: true
46
+ # Timeout for exchange API calls (ms)
47
+ default_timeout: 30000
48
+ # Enable verbose logging per exchange
49
+ default_verbose: false
@@ -0,0 +1,84 @@
1
+ [project]
2
+ name = "ccxt-gateway"
3
+ dynamic = ["version"]
4
+ description = "High-performance self-hosted gateway for 100+ crypto exchanges via CCXT"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = {text = "MIT"}
8
+ authors = [
9
+ {name = "ccxt-gateway contributors"}
10
+ ]
11
+ dependencies = [
12
+ "fastapi>=0.115.0",
13
+ "uvicorn[standard]>=0.30.0",
14
+ "pyzmq>=26.0.0",
15
+ "pydantic-settings>=2.14.2",
16
+ "psutil>=6.0.0",
17
+ "ccxt[pro]>=4.4.0",
18
+ "setuptools>=83.0.0",
19
+ "uvloop>=0.20.0",
20
+ "orjson>=3.10.0",
21
+ "aiofiles>=24.1.0",
22
+ "httpx>=0.27.0",
23
+ "python-dotenv>=1.0.0",
24
+ "pyyaml>=6.0.0",
25
+ "packaging>=24.0",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ server = [
30
+ "granian>=1.4.0",
31
+ ]
32
+ dev = [
33
+ "pytest>=8.0.0",
34
+ "pytest-asyncio>=0.23.0",
35
+ "pytest-cov>=5.0.0",
36
+ "mypy>=1.10.0",
37
+ "httpx>=0.27.0",
38
+ "types-pyyaml>=6.0.0",
39
+ "types-requests>=2.31.0",
40
+ ]
41
+
42
+ [tool.mypy]
43
+ python_version = "3.14"
44
+ warn_return_any = true
45
+ warn_unused_configs = true
46
+ disallow_untyped_defs = true
47
+ disallow_incomplete_defs = true
48
+ check_untyped_defs = true
49
+ ignore_missing_imports = true
50
+ strict = true
51
+
52
+ [tool.pytest.ini_options]
53
+ asyncio_mode = "strict"
54
+ testpaths = ["tests"]
55
+
56
+ [build-system]
57
+ requires = ["hatchling", "hatch-vcs"]
58
+ build-backend = "hatchling.build"
59
+
60
+ [tool.hatch.version]
61
+ source = "vcs"
62
+ raw-options = { search_parent_directories = true }
63
+
64
+ [dependency-groups]
65
+ dev = [
66
+ "pytest>=9.0.3",
67
+ "pytest-asyncio>=1.3.0",
68
+ "pytest-cov>=7.1.0",
69
+ ]
70
+
71
+ [tool.hatch.build.targets.wheel]
72
+ packages = ["src/ccxt_gateway"]
73
+
74
+ [tool.hatch.build.targets.sdist]
75
+ include = [
76
+ "src/ccxt_gateway/**/*",
77
+ "config/**/*",
78
+ "scripts/**/*",
79
+ "tests/**/*",
80
+ ]
81
+
82
+ [project.scripts]
83
+ ccxt-gateway = "ccxt_gateway.main:main"
84
+ ccxt-gateway-daemon = "ccxt_gateway.daemon_gateway:main"
@@ -0,0 +1,8 @@
1
+ """ccxt-gateway: High-performance self-hosted gateway for 100+ crypto exchanges via CCXT."""
2
+
3
+ try:
4
+ from importlib.metadata import PackageNotFoundError, version as _metadata_version
5
+
6
+ __version__ = _metadata_version("ccxt-gateway")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.0.0"
@@ -0,0 +1 @@
1
+ """API package for ccxt-gateway."""
@@ -0,0 +1,267 @@
1
+ """Admin API endpoints for ccxt-gateway."""
2
+
3
+ import logging
4
+ import time
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from fastapi import APIRouter, Depends, HTTPException, Request
8
+
9
+ logger: logging.Logger = logging.getLogger(__name__)
10
+
11
+ router: APIRouter = APIRouter()
12
+
13
+
14
+ def get_process_manager(request: Request) -> Any:
15
+ """Dependency to get process manager."""
16
+ process_manager = getattr(request.app.state, "process_manager", None)
17
+
18
+ if process_manager is None:
19
+ raise HTTPException(status_code=503, detail="Process manager not initialized")
20
+ return process_manager
21
+
22
+
23
+ def get_broker(request: Request) -> Any:
24
+ """Dependency to get ZMQ broker."""
25
+ broker = getattr(request.app.state, "broker", None)
26
+
27
+ if broker is None:
28
+ raise HTTPException(status_code=503, detail="ZMQ broker not initialized")
29
+ return broker
30
+
31
+
32
+ @router.get("/exchanges")
33
+ async def list_exchanges(
34
+ process_manager: Any = Depends(get_process_manager),
35
+ ) -> List[Dict[str, Any]]:
36
+ """List all exchange instances."""
37
+ result: List[Dict[str, Any]] = []
38
+ for exchange_id, proc in process_manager.processes.items():
39
+ proc.update_memory()
40
+ result.append(
41
+ {
42
+ "exchange_id": exchange_id,
43
+ "exchange_name": proc.exchange_name,
44
+ "pid": proc.pid,
45
+ "running": proc.is_running,
46
+ "rss_mb": round(proc.rss_mb, 2),
47
+ "restart_count": proc.restart_count,
48
+ "uptime_seconds": round(time.time() - proc.started_at, 2)
49
+ if proc.started_at
50
+ else None,
51
+ }
52
+ )
53
+ return result
54
+
55
+
56
+ @router.get("/exchanges/{exchange_id}")
57
+ async def get_exchange_details(
58
+ exchange_id: str,
59
+ process_manager: Any = Depends(get_process_manager),
60
+ ) -> Dict[str, Any]:
61
+ """Get details of a specific exchange instance."""
62
+ if exchange_id not in process_manager.processes:
63
+ raise HTTPException(status_code=404, detail=f"Exchange {exchange_id} not found")
64
+
65
+ proc: Any = process_manager.processes[exchange_id]
66
+ proc.update_memory()
67
+
68
+ return {
69
+ "exchange_id": exchange_id,
70
+ "exchange_name": proc.exchange_name,
71
+ "pid": proc.pid,
72
+ "running": proc.is_running,
73
+ "rss_mb": round(proc.rss_mb, 2),
74
+ "restart_count": proc.restart_count,
75
+ "last_restart": proc.last_restart,
76
+ "uptime_seconds": round(time.time() - proc.started_at, 2)
77
+ if proc.started_at
78
+ else None,
79
+ "config": proc.config,
80
+ }
81
+
82
+
83
+ @router.post("/exchanges/{exchange_id}/restart")
84
+ async def restart_exchange(
85
+ exchange_id: str,
86
+ process_manager: Any = Depends(get_process_manager),
87
+ ) -> Dict[str, Any]:
88
+ """Manually restart an exchange instance."""
89
+ if exchange_id not in process_manager.processes:
90
+ raise HTTPException(status_code=404, detail=f"Exchange {exchange_id} not found")
91
+
92
+ await process_manager._restart_exchange(exchange_id)
93
+
94
+ return {"status": "restart initiated", "exchange_id": exchange_id}
95
+
96
+
97
+ @router.get("/stats")
98
+ async def get_stats(
99
+ process_manager: Any = Depends(get_process_manager),
100
+ broker: Any = Depends(get_broker),
101
+ ) -> Dict[str, Any]:
102
+ """Get gateway statistics."""
103
+ total_exchanges: int = len(process_manager.processes)
104
+ running: int = sum(1 for p in process_manager.processes.values() if p.is_running)
105
+ total_memory: float = sum(p.rss_mb for p in process_manager.processes.values())
106
+
107
+ return {
108
+ "total_exchanges": total_exchanges,
109
+ "running_exchanges": running,
110
+ "stopped_exchanges": total_exchanges - running,
111
+ "total_memory_mb": round(total_memory, 2),
112
+ "pending_requests": len(broker.pending_requests)
113
+ if hasattr(broker, "pending_requests")
114
+ else 0,
115
+ "registered_exchanges": len(broker.exchange_identities)
116
+ if hasattr(broker, "exchange_identities")
117
+ else 0,
118
+ }
119
+
120
+
121
+ @router.get("/info")
122
+ async def get_info(request: Request) -> Dict[str, Any]:
123
+ """Get gateway server info."""
124
+ import time
125
+ start_time: float = getattr(request.app.state, "start_time", time.time())
126
+ uptime: float = time.time() - start_time
127
+ return {
128
+ "result": {
129
+ "status": "running",
130
+ "version": "0.1.0",
131
+ "uptime_seconds": round(uptime, 2),
132
+ "python_version": __import__("sys").version,
133
+ },
134
+ "error": None,
135
+ "error_code": None,
136
+ }
137
+
138
+
139
+ @router.get("/memory")
140
+ async def get_memory(
141
+ process_manager: Any = Depends(get_process_manager),
142
+ ) -> Dict[str, Any]:
143
+ """Get memory usage."""
144
+ total_memory: float = sum(p.rss_mb for p in process_manager.processes.values())
145
+ return {
146
+ "result": {
147
+ "total_memory_mb": round(total_memory, 2),
148
+ "exchange_count": len(process_manager.processes),
149
+ },
150
+ "error": None,
151
+ "error_code": None,
152
+ }
153
+
154
+
155
+ @router.get("/exchange_names")
156
+ async def get_exchange_names() -> Dict[str, Any]:
157
+ """Get all CCXT exchange names."""
158
+ try:
159
+ import ccxt
160
+ names = sorted(ccxt.exchanges)
161
+ return {
162
+ "result": names,
163
+ "error": None,
164
+ "error_code": None,
165
+ }
166
+ except Exception as e:
167
+ return {
168
+ "result": None,
169
+ "error": str(e),
170
+ "error_code": "EXCHANGE_NAMES_FAILED",
171
+ }
172
+
173
+
174
+ @router.post("/update/ccxt")
175
+ async def trigger_ccxt_update(
176
+ broker: Any = Depends(get_broker),
177
+ ) -> Dict[str, Any]:
178
+ """Manually trigger CCXT update."""
179
+ from ccxt_gateway.utils.updates import update_ccxt, check_update
180
+
181
+ update_available: bool
182
+ current: Optional[str]
183
+ latest: Optional[str]
184
+ update_available, current, latest = await check_update()
185
+
186
+ if not update_available:
187
+ return {
188
+ "status": "no update available",
189
+ "current_version": current,
190
+ "latest_version": latest,
191
+ }
192
+
193
+ success: bool
194
+ message: str
195
+ success, message = await update_ccxt()
196
+
197
+ return {
198
+ "status": "success" if success else "failed",
199
+ "message": message,
200
+ "previous_version": current,
201
+ "new_version": latest,
202
+ }
203
+
204
+
205
+ @router.get("/update/check")
206
+ async def check_ccxt_update(
207
+ broker: Any = Depends(get_broker),
208
+ ) -> Dict[str, Any]:
209
+ """Check if CCXT update is available."""
210
+ from ccxt_gateway.utils.updates import check_update
211
+
212
+ update_available: bool
213
+ current: Optional[str]
214
+ latest: Optional[str]
215
+ update_available, current, latest = await check_update()
216
+
217
+ return {
218
+ "update_available": update_available,
219
+ "current_version": current,
220
+ "latest_version": latest,
221
+ }
222
+
223
+
224
+ @router.get("/errors")
225
+ async def list_errors(
226
+ broker: Any = Depends(get_broker),
227
+ ) -> Dict[str, Any]:
228
+ """List all ccxt error names."""
229
+ try:
230
+ import ccxt
231
+ from ccxt.base.errors import ExchangeError, NetworkError, AuthenticationError, PermissionError, RequestTimeout, ExchangeNotAvailable, NotSupported, OperationFailed, BaseError
232
+
233
+ error_names = [
234
+ "BaseError",
235
+ "ExchangeError",
236
+ "NetworkError",
237
+ "AuthenticationError",
238
+ "PermissionError",
239
+ "RequestTimeout",
240
+ "ExchangeNotAvailable",
241
+ "NotSupported",
242
+ "OperationFailed",
243
+ ]
244
+
245
+ return {
246
+ "result": error_names,
247
+ "error": None,
248
+ "error_code": None,
249
+ }
250
+ except Exception as e:
251
+ return {
252
+ "result": None,
253
+ "error": str(e),
254
+ "error_code": "ERROR_LIST_FAILED",
255
+ }
256
+
257
+
258
+ @router.post("/shutdown")
259
+ async def shutdown_server(request: Request) -> Dict[str, str]:
260
+ """Gracefully shut down the gateway: closes all exchange subprocesses, then exits."""
261
+ logger.info("Shutdown requested via /admin/shutdown")
262
+ import asyncio, sys
263
+ async def _delayed_shutdown():
264
+ await asyncio.sleep(0.2)
265
+ sys.exit(0)
266
+ asyncio.create_task(_delayed_shutdown())
267
+ return {"status": "shutting_down"}