| # Architecture Migration Plan: Local Server + HF Client |
|
|
| ## Date: 2026-03-19 |
|
|
| --- |
|
|
| ## Current Architecture (Problem) |
|
|
| ``` |
| HuggingFace Space |
| βββ live_trading_multi.py (bot, all logic) |
| βββ src/ui/api_server.py (Flask REST API, port 5001) |
| βββ src/ui/app.py (Streamlit UI, port 8501) |
| βββ calls http://127.0.0.1:5001/api/* |
| ``` |
|
|
| **Problems:** |
| - Binance Testnet geo-blocked from HuggingFace's datacenter (HTTP 451) |
| - Cloudflare Workers proxy is unreliable and adds latency |
| - Bot cannot reach testnet.binance.vision from HF |
|
|
| --- |
|
|
| ## Target Architecture (Solution) |
|
|
| ``` |
| Local Machine |
| βββ live_trading_multi.py (bot, trading logic) |
| βββ src/ui/api_server.py (Flask REST API, port 5001) |
| βββ [tunnel tool] (exposes :5001 as public HTTPS URL) |
| βββ e.g. https://abc123.ngrok.io β localhost:5001 |
| |
| HuggingFace Space |
| βββ src/ui/app.py (Streamlit UI, CLIENT only) |
| βββ calls $API_SERVER_URL/api/* |
| βββ API_SERVER_URL = https://abc123.ngrok.io (HF Secret) |
| ``` |
|
|
| **Benefits:** |
| - Bot runs locally β direct Binance testnet access (no geo-blocks) |
| - HF Space becomes a pure UI/client, no trading logic |
| - Testnet tab works via server-side `testnet_server.py` (direct connection locally) |
|
|
| --- |
|
|
| ## Implementation Plan |
|
|
| ### Step 1: Configure Flask API Server for Remote Access |
|
|
| **File: `src/ui/api_server.py`** |
| |
| Changes: |
| 1. Enable CORS properly so HuggingFace domain can call the API |
| 2. Add a `/api/ping` health endpoint (lightweight check for client) |
| 3. Add startup banner showing the public URL |
| |
| ```python |
| from flask_cors import CORS |
| CORS(app, origins=["*"]) # Allow HF and any origin |
| ``` |
| |
| **File: `start_local_server.sh`** (new) |
| |
| A convenience script to: |
| 1. Start the Flask API server (port 5001) |
| 2. Optionally start a tunnel (ngrok or Cloudflare) |
| 3. Print the public URL to copy into HF Secrets |
| |
| --- |
| |
| ### Step 2: Make Streamlit UI Configurable |
| |
| **File: `src/ui/app.py`** |
|
|
| Changes: |
| 1. Replace all hardcoded `http://127.0.0.1:5001` with `get_api_url()` helper |
| 2. `get_api_url()` reads from `os.environ.get('API_SERVER_URL', 'http://127.0.0.1:5001')` |
| 3. Show connection status in UI (connected/disconnected + server URL) |
| 4. Graceful degradation when server is unreachable (not just red error) |
|
|
| **Occurrences of hardcoded URL to replace:** |
| - Line 1183: `requests.get('http://127.0.0.1:5001/api/state', ...)` |
| - Line 1221: `requests.get(f'http://127.0.0.1:5001/api/market?symbol=...', ...)` |
| - Line 1427: `requests.get('http://127.0.0.1:5001/api/state', ...)` |
| - Line 1438: `requests.get(f'http://127.0.0.1:5001/api/market?symbol=...', ...)` |
| - Line 1459: `requests.get('http://127.0.0.1:5001/api/trades', ...)` |
| - Line 1539: `requests.get('http://127.0.0.1:5001/api/state', ...)` |
| - Line 1553: `requests.get('http://127.0.0.1:5001/api/trades', ...)` |
|
|
| --- |
|
|
| ### Step 3: Fix Testnet Tab |
|
|
| **File: `src/ui/testnet_server.py`** |
| |
| Changes: |
| 1. Remove the HuggingFace detection/warning block (it no longer applies) |
| 2. Remove Cloudflare proxy fallback (not needed locally) |
| 3. Simplify to direct `BinanceConnector` with `testnet=True` |
| 4. Server now calls `https://testnet.binance.vision` directly |
| |
| **File: `src/ui/app.py` (testnet tab section)** |
| |
| Changes: |
| 1. Always use server-side (`testnet_server.py`) since server runs locally |
| 2. Remove the client-side JS fallback (optional, can keep as backup) |
| |
| --- |
| |
| ### Step 4: Add Tunnel Support (ngrok) |
| |
| **File: `requirements.txt`** |
|
|
| Add: `pyngrok>=7.0.0` |
|
|
| **File: `start_local_server.py`** (new script) |
|
|
| ```python |
| # Start Flask server with optional ngrok tunnel |
| # Usage: |
| # python start_local_server.py # local only |
| # python start_local_server.py --tunnel # with ngrok tunnel |
| ``` |
|
|
| Logic: |
| 1. Start `api_server.py` in a background thread |
| 2. If `--tunnel`: start pyngrok tunnel, print public URL |
| 3. Print instructions to add URL to HF Secrets |
|
|
| --- |
|
|
| ### Step 5: Integration Tests |
|
|
| **File: `tests/test_integration/test_server_client.py`** (new) |
| |
| Tests: |
| 1. `test_server_starts()` - Flask server starts, `/health` returns 200 |
| 2. `test_api_state()` - `/api/state` returns valid JSON |
| 3. `test_api_trades()` - `/api/trades` returns list |
| 4. `test_api_market()` - `/api/market?symbol=BTCUSDT` returns market data |
| 5. `test_cors_headers()` - Response includes CORS headers |
| 6. `test_binance_testnet_connectivity()` - testnet.binance.vision ping returns 200 |
| 7. `test_binance_testnet_with_keys()` - API keys work for account balance |
| 8. `test_client_url_configurable()` - `API_SERVER_URL` env var is read correctly |
| 9. `test_end_to_end_data_flow()` - Bot saves state β API serves it β client reads it |
| |
| --- |
| |
| ### Step 6: Update HuggingFace Space |
| |
| **File: `src/ui/app.py`** (already updated in Step 2) |
|
|
| **HF Space Secrets to add:** |
| - `API_SERVER_URL` = `<ngrok-or-tunnel-url>` |
| - Keep existing: `BINANCE_TESTNET_API_KEY`, `BINANCE_TESTNET_API_SECRET`, etc. |
|
|
| **File: `README.md` or HF Space README** - update deployment instructions |
|
|
| **Push target:** `Chen4700/drl-trading-bot-dev` (DEV space only) |
|
|
| --- |
|
|
| ## File Changes Summary |
|
|
| | File | Action | Description | |
| |------|--------|-------------| |
| | `src/ui/api_server.py` | Modify | Enable CORS, add `/api/ping` | |
| | `src/ui/app.py` | Modify | Replace hardcoded localhost:5001 with `API_SERVER_URL` env var | |
| | `src/ui/testnet_server.py` | Modify | Remove proxy/HF-specific code, direct testnet access | |
| | `start_local_server.py` | Create | Convenience script to start server + optional tunnel | |
| | `tests/test_integration/test_server_client.py` | Create | Integration tests | |
| | `tests/test_integration/__init__.py` | Create | Package init | |
| | `requirements.txt` | Modify | Add `pyngrok>=7.0.0` | |
| | `.env.example` | Modify | Add `API_SERVER_URL` example | |
|
|
| --- |
|
|
| ## Blockers & Solutions |
|
|
| ### Blocker 1: HuggingFace Space Needs Public URL |
| **Problem:** The local Flask server on :5001 is not reachable from HF by default. |
| **Solution:** Use ngrok (pyngrok) or Cloudflare Tunnel (`cloudflared`) to expose it. |
| - ngrok: requires account but easy setup, free tier works |
| - Cloudflare Tunnel: free, persistent, but requires `cloudflared` binary |
| - **Chosen:** ngrok via `pyngrok` (Python package, easiest to automate) |
|
|
| ### Blocker 2: CORS on Flask |
| **Problem:** HuggingFace serves the Streamlit app from a different origin. |
| **Solution:** Add `flask-cors` (already in requirements) and configure `CORS(app, origins=["*"])`. |
|
|
| ### Blocker 3: Testnet Keys in HF Secrets vs .env |
| **Problem:** The testnet tab reads keys from `os.getenv()` β works both locally (.env) and on HF (secrets). |
| **Solution:** No change needed. Keep reading from env vars. |
|
|
| ### Blocker 4: Streamlit on HF can't do ngrok |
| **Problem:** Streamlit on HF is the CLIENT, not the server β it doesn't need ngrok. |
| **Solution:** ngrok only runs on the LOCAL server side. |
|
|
| --- |
|
|
| ## Execution Order |
|
|
| 1. [x] Write PLAN.md (this file) |
| 2. [x] Modify `src/ui/api_server.py` β Enable CORS + add /api/ping |
| 3. [x] Modify `src/ui/app.py` β Replace hardcoded URLs with `API_SERVER_URL` |
| 4. [x] Modify `src/ui/testnet_server.py` β Remove proxy/HF blocks (direct testnet) |
| 5. [x] Create `start_local_server.py` β Server startup script with optional ngrok |
| 6. [x] Add `pyngrok` to `requirements.txt` |
| 7. [x] Add `API_SERVER_URL` to `.env.example` |
| 8. [x] Create integration tests (`tests/test_integration/test_server_client.py`) |
| 9. [~] Run tests locally β BLOCKED: this machine has no project venv (no flask/dotenv/etc.) |
| - Static checks passed: all 5 modified files parse cleanly (ast.parse) |
| - Logic check passed: `get_api_url()` default/custom/trailing-slash behavior verified |
| - Storage check passed: JsonFileStorage save/load verified (when deps available) |
| - Binance testnet check passed: testnet.binance.vision/api/v3/ping reachable locally |
| - Full pytest suite must be run in the project's virtualenv: |
| `pip install -r requirements.txt && pytest tests/test_integration/ -v` |
| 10. [x] Push to `Chen4700/drl-trading-bot-dev` |
| |