Spaces:
Sleeping
Sleeping
Thang6822 commited on
Commit ·
6ed48fa
1
Parent(s): dbfd1de
Deploy v6.0: Dashboard Scoring Fix, UI Refinement, and Auto-Scaling
Browse files- .gitignore +1 -0
- SOURCES_AND_SYMBOLS.md +0 -242
- all_symbols_final.txt +0 -643
- all_symbols_3.txt → backend/__init__.py +0 -0
- backend/check_eurusd.py +0 -14
- backend/check_wallet.py +0 -39
- backend/check_wallet_sync.py +0 -44
- backend/discover_bybit.py +0 -26
- backend/find_any_suffix.py +0 -36
- {Kronos-master → backend/kronos_core}/.gitignore +0 -0
- {Kronos-master → backend/kronos_core}/LICENSE +0 -0
- {Kronos-master → backend/kronos_core}/README.md +0 -0
- {Kronos-master → backend/kronos_core}/model/__init__.py +0 -0
- {Kronos-master → backend/kronos_core}/model/kronos.py +0 -0
- {Kronos-master → backend/kronos_core}/model/module.py +0 -0
- {Kronos-master → backend/kronos_core}/requirements.txt +0 -0
- backend/list_linear.py +0 -14
- backend/list_pybit_final.py +0 -38
- backend/main.py +284 -99
- backend/recovery.py +0 -129
- backend/sign_agreement.py +0 -43
- backend/test_basecoin.py +0 -30
- backend/test_bytick.py +0 -45
- backend/test_cat.py +0 -49
- backend/test_indices.py +0 -43
- backend/test_no_auth.py +0 -15
- backend/test_s.py +0 -43
- backend/verify_bybit.py +0 -31
- backend/verify_new_key.py +0 -43
- frontend/Dark_BG.png +2 -2
- frontend/Light_BG.png +2 -2
- frontend/index.html +57 -16
- update_symbols.py +0 -84
.gitignore
CHANGED
|
@@ -10,3 +10,4 @@ build/
|
|
| 10 |
dist/
|
| 11 |
scratch/
|
| 12 |
*.spec
|
|
|
|
|
|
| 10 |
dist/
|
| 11 |
scratch/
|
| 12 |
*.spec
|
| 13 |
+
data/
|
SOURCES_AND_SYMBOLS.md
DELETED
|
@@ -1,242 +0,0 @@
|
|
| 1 |
-
# NGUỒN DỮ LIỆU MIỄN PHÍ — XÁC NHẬN & PHÂN LOẠI MÃ GIAO DỊCH
|
| 2 |
-
# Free Data Sources — Confirmed & Symbol Classification v4.0
|
| 3 |
-
|
| 4 |
-
═══════════════════════════════════════════════════════════════════════
|
| 5 |
-
## A. BẢNG NGUỒN DỮ LIỆU MIỄN PHÍ (Confirmed Free Sources)
|
| 6 |
-
═══════════════════════════════════════════════════════════════════════
|
| 7 |
-
|
| 8 |
-
| Nguồn | URL cơ sở | API Key? | Giới hạn miễn phí | Tốt nhất cho |
|
| 9 |
-
|--------------|----------------------------------------|----------|--------------------------|---------------------------|
|
| 10 |
-
| Binance | api.binance.com/api/v3 | Không | 1200 req/min | Crypto OHLCV (tốt nhất) |
|
| 11 |
-
| Bybit V5 | api.bybit.com/v5/market | Không | 120 req/min | Crypto backup |
|
| 12 |
-
| CoinGecko | api.coingecko.com/api/v3 | Không | 30 req/min | Crypto OHLCV + market cap |
|
| 13 |
-
| yfinance | (scrapes finance.yahoo.com) | Không | Không giới hạn chính thức| Stocks, ETF, Forex, Chỉ số|
|
| 14 |
-
| Finnhub | finnhub.io/api/v1 | Có (free)| 60 req/min | Forex (OANDA), Stock, Crypto|
|
| 15 |
-
| Twelvedata | api.twelvedata.com | Có (free)| 800 credits/ngày, 8/phút | Forex, Stock, Crypto |
|
| 16 |
-
| Alpha Vantage| alphavantage.co/query | Có (free)| 25 req/ngày | Forex fallback (hạn chế) |
|
| 17 |
-
| FRED | fred.stlouisfed.org/graph/fredgraph | Không | Không giới hạn | Macro (lãi suất, DXY...) |
|
| 18 |
-
|
| 19 |
-
### Ưu tiên nguồn theo loại tài sản:
|
| 20 |
-
- **Crypto**: Binance → Bybit → CoinGecko → yfinance → Finnhub
|
| 21 |
-
- **Forex**: Twelvedata → Finnhub (OANDA) → yfinance
|
| 22 |
-
- **Kim loại**: yfinance (futures =F) → Twelvedata → Finnhub
|
| 23 |
-
- **Năng lượng**: yfinance (futures =F) → Twelvedata → Finnhub
|
| 24 |
-
- **Nông sản**: yfinance (futures =F) → Twelvedata
|
| 25 |
-
- **Chỉ số**: yfinance (^SYMBOL) → Twelvedata → Finnhub
|
| 26 |
-
- **Cổ phiếu Mỹ**: yfinance → Finnhub → Twelvedata
|
| 27 |
-
- **Cổ phiếu VN**: yfinance (.VN suffix)
|
| 28 |
-
- **Trái phiếu**: yfinance (^TNX, ^TYX...)
|
| 29 |
-
|
| 30 |
-
═══════════════════════════════════════════════════════════════════════
|
| 31 |
-
## B. PHÂN LOẠI MÃ GIAO DỊCH CHUẨN
|
| 32 |
-
═══════════════════════════════════════════════════════════════════════
|
| 33 |
-
|
| 34 |
-
### 1. KIM LOẠI (Metals) — 13 mã
|
| 35 |
-
| Mã | Tên VN | Ticker yfinance | Nguồn tốt nhất |
|
| 36 |
-
|--------------|---------------------|-----------------|---------------------|
|
| 37 |
-
| XAUUSD | Vàng | GC=F | Twelvedata, Finnhub |
|
| 38 |
-
| XAGUSD | Bạc | SI=F | Twelvedata, Finnhub |
|
| 39 |
-
| XPTUSD | Bạch kim | PL=F | Twelvedata, yfinance |
|
| 40 |
-
| XPDUSD | Palladium | PA=F | Twelvedata, yfinance |
|
| 41 |
-
| COPPER | Đồng COMEX | HG=F | yfinance, Twelvedata |
|
| 42 |
-
| COPPER_LME | Đồng LME | HG=F | Twelvedata |
|
| 43 |
-
| ALUMINUM | Nhôm COMEX | ALI=F | yfinance |
|
| 44 |
-
| ALUMINUM_LME | Nhôm LME | LMAHDS03 | Twelvedata |
|
| 45 |
-
| NICKEL_LME | Niken LME | NI1 | Twelvedata |
|
| 46 |
-
| ZINC_LME | Kẽm LME | ZN1 | Twelvedata |
|
| 47 |
-
| IRON_ORE | Quặng sắt | TIO=F | yfinance |
|
| 48 |
-
| STEEL_HRC | Thép cuộn cán nóng | HRC=F | yfinance |
|
| 49 |
-
| LITHIUM | Lithium (proxy ALB) | ALB | yfinance |
|
| 50 |
-
|
| 51 |
-
### 2. NĂNG LƯỢNG (Energy) — 8 mã
|
| 52 |
-
| Mã | Tên VN | Ticker yfinance | Ghi chú |
|
| 53 |
-
|--------------|---------------------|-----------------|----------------------|
|
| 54 |
-
| WTI | Dầu thô WTI | CL=F | Chuẩn Mỹ |
|
| 55 |
-
| BRENT | Dầu Brent | BZ=F | Chuẩn quốc tế |
|
| 56 |
-
| NATURAL_GAS | Khí tự nhiên | NG=F | |
|
| 57 |
-
| GASOLINE_RBOB| Xăng RBOB | RB=F | |
|
| 58 |
-
| HEATING_OIL | Dầu sưởi | HO=F | |
|
| 59 |
-
| LNG | Khí hoá lỏng proxy | LNG | Cổ phiếu Cheniere |
|
| 60 |
-
| COAL | Than Newcastle | MTF=F | |
|
| 61 |
-
| URANIUM | Uranium | UX=F | |
|
| 62 |
-
|
| 63 |
-
### 3. NÔNG SẢN (Agricultural) — 7 mã
|
| 64 |
-
| Mã | Tên VN | Ticker yfinance |
|
| 65 |
-
|--------------|---------------------|-----------------|
|
| 66 |
-
| CORN | Ngô | ZC=F |
|
| 67 |
-
| WHEAT | Lúa mì | ZW=F |
|
| 68 |
-
| SOYBEAN | Đậu tương | ZS=F |
|
| 69 |
-
| SOYBEAN_OIL | Dầu đậu tương | ZL=F |
|
| 70 |
-
| SOYBEAN_MEAL | Khô đậu tương | ZM=F |
|
| 71 |
-
| RICE | Gạo thô | ZR=F |
|
| 72 |
-
| OATS | Yến mạch | ZO=F |
|
| 73 |
-
|
| 74 |
-
### 4. NGUYÊN LIỆU CÔNG NGHIỆP (Soft Commodities) — 10 mã
|
| 75 |
-
| Mã | Tên VN | Ticker yfinance |
|
| 76 |
-
|----------------|---------------------|-----------------|
|
| 77 |
-
| COFFEE_ARABICA | Cà phê Arabica | KC=F |
|
| 78 |
-
| COFFEE_ROBUSTA | Cà phê Robusta | RC=F |
|
| 79 |
-
| COCOA | Ca cao | CC=F |
|
| 80 |
-
| SUGAR_11 | Đường 11 | SB=F |
|
| 81 |
-
| WHITE_SUGAR | Đường trắng | LSW=F |
|
| 82 |
-
| COTTON | Bông | CT=F |
|
| 83 |
-
| ORANGE_JUICE | Nước cam | OJ=F |
|
| 84 |
-
| LUMBER | Gỗ xẻ | LBS=F |
|
| 85 |
-
| RUBBER_RSS3 | Cao su RSS3 | JRU=F |
|
| 86 |
-
| PALM_OIL | Dầu cọ thô | FCPO=F |
|
| 87 |
-
|
| 88 |
-
### 5. CRYPTO — 25 mã (Top by market cap)
|
| 89 |
-
| Mã | Tên | Binance | CoinGecko ID |
|
| 90 |
-
|----------|----------------|--------------|---------------------|
|
| 91 |
-
| BTCUSD | Bitcoin | BTCUSDT | bitcoin |
|
| 92 |
-
| ETHUSD | Ethereum | ETHUSDT | ethereum |
|
| 93 |
-
| BNBUSD | BNB | BNBUSDT | binancecoin |
|
| 94 |
-
| SOLUSD | Solana | SOLUSDT | solana |
|
| 95 |
-
| XRPUSD | Ripple | XRPUSDT | ripple |
|
| 96 |
-
| ADAUSD | Cardano | ADAUSDT | cardano |
|
| 97 |
-
| AVAXUSD | Avalanche | AVAXUSDT | avalanche-2 |
|
| 98 |
-
| DOTUSD | Polkadot | DOTUSDT | polkadot |
|
| 99 |
-
| MATICUSD | Polygon | MATICUSDT | matic-network |
|
| 100 |
-
| LINKUSD | Chainlink | LINKUSDT | chainlink |
|
| 101 |
-
| LTCUSD | Litecoin | LTCUSDT | litecoin |
|
| 102 |
-
| UNIUSD | Uniswap | UNIUSDT | uniswap |
|
| 103 |
-
| ATOMUSD | Cosmos | ATOMUSDT | cosmos |
|
| 104 |
-
| NEARUSD | NEAR Protocol | NEARUSDT | near |
|
| 105 |
-
| APTUSD | Aptos | APTUSDT | aptos |
|
| 106 |
-
| SUIUSD | Sui | SUIUSDT | sui |
|
| 107 |
-
| ARBUSD | Arbitrum | ARBUSDT | arbitrum |
|
| 108 |
-
| OPUSD | Optimism | OPUSDT | optimism |
|
| 109 |
-
| INJUSD | Injective | INJUSDT | injective-protocol |
|
| 110 |
-
| TONUSD | Toncoin | TONUSDT | the-open-network |
|
| 111 |
-
| TRXUSD | Tron | TRXUSDT | tron |
|
| 112 |
-
| XMRUSD | Monero | (Bybit only) | monero |
|
| 113 |
-
| DOGEUSD | Dogecoin | DOGEUSDT | dogecoin |
|
| 114 |
-
| SHIBAUSD | Shiba Inu | SHIBUSDT | shiba-inu |
|
| 115 |
-
| PEPE | PEPE Coin | PEPEUSDT | pepe |
|
| 116 |
-
|
| 117 |
-
### 6. CẶP TIỀN (Forex) — 19 mã
|
| 118 |
-
| Mã | Tên | Twelvedata | Finnhub | yfinance |
|
| 119 |
-
|----------|---------------|--------------|------------------|--------------|
|
| 120 |
-
| DXY | Chỉ số USD | — | — | DX-Y.NYB |
|
| 121 |
-
| EURUSD | EUR/USD | EUR/USD | OANDA:EUR_USD | EURUSD=X |
|
| 122 |
-
| GBPUSD | GBP/USD | GBP/USD | OANDA:GBP_USD | GBPUSD=X |
|
| 123 |
-
| USDJPY | USD/JPY | USD/JPY | OANDA:USD_JPY | JPY=X |
|
| 124 |
-
| USDCHF | USD/CHF | USD/CHF | OANDA:USD_CHF | CHF=X |
|
| 125 |
-
| AUDUSD | AUD/USD | AUD/USD | OANDA:AUD_USD | AUDUSD=X |
|
| 126 |
-
| USDCAD | USD/CAD | USD/CAD | OANDA:USD_CAD | CAD=X |
|
| 127 |
-
| NZDUSD | NZD/USD | NZD/USD | OANDA:NZD_USD | NZDUSD=X |
|
| 128 |
-
| GBPJPY | GBP/JPY | GBP/JPY | OANDA:GBP_JPY | GBPJPY=X |
|
| 129 |
-
| EURJPY | EUR/JPY | EUR/JPY | OANDA:EUR_JPY | EURJPY=X |
|
| 130 |
-
| EURGBP | EUR/GBP | EUR/GBP | OANDA:EUR_GBP | EURGBP=X |
|
| 131 |
-
| CADCHF | CAD/CHF | CAD/CHF | OANDA:CAD_CHF | CADCHF=X |
|
| 132 |
-
| AUDNZD | AUD/NZD | AUD/NZD | OANDA:AUD_NZD | AUDNZD=X |
|
| 133 |
-
| USDVND | USD/VND | — | — | VND=X |
|
| 134 |
-
| USDHKD | USD/HKD | USD/HKD | — | HKD=X |
|
| 135 |
-
| USDSGD | USD/SGD | USD/SGD | — | SGD=X |
|
| 136 |
-
| USDCNY | USD/CNY | USD/CNH | — | CNY=X |
|
| 137 |
-
| USDINR | USD/INR | — | — | INR=X |
|
| 138 |
-
| USDBRL | USD/BRL | — | — | BRL=X |
|
| 139 |
-
|
| 140 |
-
### 7. CHỈ SỐ (Stock Indices) — 20 mã
|
| 141 |
-
| Mã | Tên | yfinance | Ghi chú |
|
| 142 |
-
|------------|----------------------|------------|-----------------|
|
| 143 |
-
| SP500 | S&P 500 | ^GSPC | Chuẩn Mỹ |
|
| 144 |
-
| NASDAQ | Nasdaq Composite | ^IXIC | |
|
| 145 |
-
| NASDAQ100 | Nasdaq 100 | ^NDX | |
|
| 146 |
-
| DOW30 | Dow Jones 30 | ^DJI | |
|
| 147 |
-
| RUSSELL2000| Russell 2000 | ^RUT | Small-cap Mỹ |
|
| 148 |
-
| VIX | VIX Fear Index | ^VIX | |
|
| 149 |
-
| VNINDEX | VN-Index (HOSE) | ^VNINDEX | Việt Nam |
|
| 150 |
-
| HNX30 | HNX 30 | ^HNX30 | Việt Nam |
|
| 151 |
-
| UK100 | FTSE 100 | ^FTSE | Anh |
|
| 152 |
-
| DAX40 | DAX 40 | ^GDAXI | Đức |
|
| 153 |
-
| EU50 | Euro Stoxx 50 | ^STOXX50E | Châu Âu |
|
| 154 |
-
| CAC40 | CAC 40 | ^FCHI | Pháp |
|
| 155 |
-
| NIKKEI225 | Nikkei 225 | ^N225 | Nhật Bản |
|
| 156 |
-
| HSI | Hang Seng | ^HSI | Hong Kong |
|
| 157 |
-
| SSE50 | SSE 50 | 000016.SS | Trung Quốc |
|
| 158 |
-
| CSI300 | CSI 300 | 000300.SS | Trung Quốc |
|
| 159 |
-
| ASX200 | ASX 200 | ^AXJO | Úc |
|
| 160 |
-
| SENSEX | BSE Sensex | ^BSESN | Ấn Độ |
|
| 161 |
-
| KOSPI | KOSPI | ^KS11 | Hàn Quốc |
|
| 162 |
-
| SGX | Straits Times | ^STI | Singapore |
|
| 163 |
-
|
| 164 |
-
### 8. CỔ PHIẾU MỸ (US Stocks) — 21 mã
|
| 165 |
-
AAPL, MSFT, NVDA, GOOGL, AMZN, META, TSLA, AVGO, JPM, V, MA,
|
| 166 |
-
XOM, WMT, BAC, GS, AMD, INTC, NFLX, DIS, COIN, MSTR
|
| 167 |
-
→ Tất cả dùng yfinance + Finnhub + Twelvedata (ticker trực tiếp)
|
| 168 |
-
|
| 169 |
-
### 9. CỔ PHIẾU VIỆT NAM (VN Stocks) — 25 mã (yfinance .VN suffix)
|
| 170 |
-
Ngân hàng: VCB, BID, CTG, TCB, MBB, VPB, ACB, HDB, SHB, STB
|
| 171 |
-
Công nghệ: FPT
|
| 172 |
-
Bất động sản: VIC, VHM
|
| 173 |
-
FMCG: VNM, MSN, SAB
|
| 174 |
-
Năng lượng/CN: GAS, PLX, POW, HPG, DGC
|
| 175 |
-
Tiêu dùng: MWG
|
| 176 |
-
Chứng khoán: SSI, VND, HCM
|
| 177 |
-
|
| 178 |
-
### 10. TRÁI PHIẾU & LÃI SUẤT (Bonds & Rates) — 5 mã
|
| 179 |
-
| Mã | Tên | yfinance | Ghi chú |
|
| 180 |
-
|--------|----------------------|--------------|------------------------|
|
| 181 |
-
| US10Y | Trái phiếu Mỹ 10 năm | ^TNX | Chuẩn lãi suất toàn cầu|
|
| 182 |
-
| US02Y | Trái phiếu Mỹ 2 năm | ^IRX | |
|
| 183 |
-
| US30Y | Trái phiếu Mỹ 30 năm | ^TYX | |
|
| 184 |
-
| DE10Y | Bund Đức 10 năm | ^DE10YT=RR | |
|
| 185 |
-
| JP10Y | JGB Nhật 10 năm | ^JP10YT=RR | |
|
| 186 |
-
|
| 187 |
-
═══════════════════════════════════════════════════════════════════════
|
| 188 |
-
## C. API ENDPOINTS v4.0
|
| 189 |
-
═══════════════════════════════════════════════════════════════════════
|
| 190 |
-
|
| 191 |
-
### Dữ liệu lịch sử
|
| 192 |
-
GET /api/symbols → Danh sách tất cả mã (có thể lọc theo category)
|
| 193 |
-
GET /api/search?q=gold → Tìm kiếm mờ (fuzzy search)
|
| 194 |
-
GET /api/historical/{symbol} → Nến OHLCV (interval, limit)
|
| 195 |
-
GET /api/indicators/{symbol} → Chỉ báo kỹ thuật (RSI, MACD, BB, EMA, ATR)
|
| 196 |
-
|
| 197 |
-
### Giá thực tế
|
| 198 |
-
GET /api/ticker/{symbol} → Giá cuối + 24h stats
|
| 199 |
-
POST /api/watchlist/tickers → Batch ticker cho danh mục
|
| 200 |
-
GET /api/crypto/market → Top N crypto theo vốn hoá (CoinGecko)
|
| 201 |
-
WS /ws/price/{symbol} → WebSocket stream giá thực tế (5s)
|
| 202 |
-
|
| 203 |
-
### AI Forecast
|
| 204 |
-
GET /api/forecast/{symbol} → Dự báo Kronos (horizon, quantiles 10/50/90)
|
| 205 |
-
|
| 206 |
-
### Thị trường
|
| 207 |
-
GET /api/market-status → Trạng thái mở/đóng cửa các sàn thế giới
|
| 208 |
-
|
| 209 |
-
### Cache & Admin
|
| 210 |
-
POST /api/switch → Xoá cache khi đổi symbol/interval
|
| 211 |
-
DELETE /api/cache/{sym}/{iv} → Xoá cache cụ thể
|
| 212 |
-
DELETE /api/cache → Xoá tất cả cache
|
| 213 |
-
GET /api/cache/stats → Thống kê cache
|
| 214 |
-
GET /api/health → Trạng thái server
|
| 215 |
-
|
| 216 |
-
═══════════════════════════════════════════════════════════════════════
|
| 217 |
-
## D. CÀI ĐẶT & CHẠY
|
| 218 |
-
═══════════════════════════════════════════════════════════════════════
|
| 219 |
-
|
| 220 |
-
# 1. Cài đặt dependencies
|
| 221 |
-
pip install -r requirements.txt
|
| 222 |
-
|
| 223 |
-
# 2. Cài pytz (cần cho market-status)
|
| 224 |
-
pip install pytz
|
| 225 |
-
|
| 226 |
-
# 3. Chạy server
|
| 227 |
-
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
| 228 |
-
|
| 229 |
-
# 4. Xem API docs
|
| 230 |
-
http://localhost:8000/docs
|
| 231 |
-
|
| 232 |
-
# 5. Kiểm tra health
|
| 233 |
-
curl http://localhost:8000/api/health
|
| 234 |
-
|
| 235 |
-
# 6. Test lấy giá vàng
|
| 236 |
-
curl "http://localhost:8000/api/historical/XAUUSD?interval=1h&limit=100"
|
| 237 |
-
|
| 238 |
-
# 7. Test chỉ báo kỹ thuật BTC
|
| 239 |
-
curl "http://localhost:8000/api/indicators/BTCUSD?interval=4h"
|
| 240 |
-
|
| 241 |
-
# 8. Test thị trường đang mở
|
| 242 |
-
curl http://localhost:8000/api/market-status
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
all_symbols_final.txt
DELETED
|
@@ -1,643 +0,0 @@
|
|
| 1 |
-
0GUSDT
|
| 2 |
-
1000000BABYDOGEUSDT
|
| 3 |
-
1000000CHEEMSUSDT
|
| 4 |
-
1000000MOGUSDT
|
| 5 |
-
10000QUBICUSDT
|
| 6 |
-
10000SATSUSDT
|
| 7 |
-
1000BONKPERP
|
| 8 |
-
1000BONKUSDT
|
| 9 |
-
1000BTTUSDT
|
| 10 |
-
1000CATUSDT
|
| 11 |
-
1000FLOKIUSDT
|
| 12 |
-
1000LUNCUSDT
|
| 13 |
-
1000NEIROCTOPERP
|
| 14 |
-
1000NEIROCTOUSDT
|
| 15 |
-
1000PEPEPERP
|
| 16 |
-
1000PEPEUSDT
|
| 17 |
-
1000RATSUSDT
|
| 18 |
-
1000TAGUSDT
|
| 19 |
-
1000TOSHIUSDT
|
| 20 |
-
1000TURBOUSDT
|
| 21 |
-
1000XECUSDT
|
| 22 |
-
1INCHUSDT
|
| 23 |
-
2ZUSDT
|
| 24 |
-
4USDT
|
| 25 |
-
AAVEPERP
|
| 26 |
-
AAVEUSDT
|
| 27 |
-
ACEUSDT
|
| 28 |
-
ACHUSDT
|
| 29 |
-
ACTUSDT
|
| 30 |
-
ACUUSDT
|
| 31 |
-
ACXUSDT
|
| 32 |
-
ADAUSDT
|
| 33 |
-
AERGOUSDT
|
| 34 |
-
AEROUSDT
|
| 35 |
-
AEVOPERP
|
| 36 |
-
AEVOUSDT
|
| 37 |
-
AGIUSDT
|
| 38 |
-
AGLDUSDT
|
| 39 |
-
AIOUSDT
|
| 40 |
-
AIOZUSDT
|
| 41 |
-
AIXBTUSDT
|
| 42 |
-
AKEUSDT
|
| 43 |
-
AKTUSDT
|
| 44 |
-
ALCHUSDT
|
| 45 |
-
ALGOUSDT
|
| 46 |
-
ALICEUSDT
|
| 47 |
-
ALLOUSDT
|
| 48 |
-
ALPINEUSDT
|
| 49 |
-
ALTUSDT
|
| 50 |
-
ANIMEUSDT
|
| 51 |
-
ANKRUSDT
|
| 52 |
-
APEUSDT
|
| 53 |
-
APEXUSDT
|
| 54 |
-
API3USDT
|
| 55 |
-
APRUSDT
|
| 56 |
-
APTUSDT
|
| 57 |
-
ARBPERP
|
| 58 |
-
ARBUSDT
|
| 59 |
-
ARCUSDT
|
| 60 |
-
ARIAUSDT
|
| 61 |
-
ARKMUSDT
|
| 62 |
-
ARKUSDT
|
| 63 |
-
ARPAUSDT
|
| 64 |
-
ARUSDT
|
| 65 |
-
ASPUSDT
|
| 66 |
-
ASRUSDT
|
| 67 |
-
ASTERPERP
|
| 68 |
-
ASTERUSDT
|
| 69 |
-
ASTRUSDT
|
| 70 |
-
ATHUSDT
|
| 71 |
-
ATOMUSDT
|
| 72 |
-
ATUSDT
|
| 73 |
-
AUCTIONUSDT
|
| 74 |
-
AUSDT
|
| 75 |
-
AVAAIUSDT
|
| 76 |
-
AVAUSDT
|
| 77 |
-
AVAXUSDT
|
| 78 |
-
AVNTPERP
|
| 79 |
-
AVNTUSDT
|
| 80 |
-
AWEUSDT
|
| 81 |
-
AXLUSDT
|
| 82 |
-
AXSUSDT
|
| 83 |
-
AZTECUSDT
|
| 84 |
-
B2USDT
|
| 85 |
-
B3USDT
|
| 86 |
-
BABYUSDT
|
| 87 |
-
BANANAS31USDT
|
| 88 |
-
BANANAUSDT
|
| 89 |
-
BANDUSDT
|
| 90 |
-
BANKUSDT
|
| 91 |
-
BANUSDT
|
| 92 |
-
BARDUSDT
|
| 93 |
-
BASEDUSDT
|
| 94 |
-
BATUSDT
|
| 95 |
-
BBUSDT
|
| 96 |
-
BCHPERP
|
| 97 |
-
BCHUSDT
|
| 98 |
-
BEAMUSDT
|
| 99 |
-
BEATUSDT
|
| 100 |
-
BELUSDT
|
| 101 |
-
BERAUSDT
|
| 102 |
-
BICOUSDT
|
| 103 |
-
BIGTIMEUSDT
|
| 104 |
-
BIOUSDT
|
| 105 |
-
BIRBUSDT
|
| 106 |
-
BLASTUSDT
|
| 107 |
-
BLESSUSDT
|
| 108 |
-
BLUAIUSDT
|
| 109 |
-
BLURUSDT
|
| 110 |
-
BMTUSDT
|
| 111 |
-
BNBPERP
|
| 112 |
-
BNBUSDT
|
| 113 |
-
BNTUSDT
|
| 114 |
-
BOBAUSDT
|
| 115 |
-
BOBBOBUSDT
|
| 116 |
-
BOMEUSDT
|
| 117 |
-
BRETTUSDT
|
| 118 |
-
BREVUSDT
|
| 119 |
-
BROCCOLIUSDT
|
| 120 |
-
BRUSDT
|
| 121 |
-
BSBUSDT
|
| 122 |
-
BSUUSDT
|
| 123 |
-
BSVUSDT
|
| 124 |
-
BTCPERP
|
| 125 |
-
BTCUSDT
|
| 126 |
-
BTCUSDT-01MAY26
|
| 127 |
-
BTCUSDT-08MAY26
|
| 128 |
-
BTCUSDT-24APR26
|
| 129 |
-
BTCUSDT-25DEC26
|
| 130 |
-
BTCUSDT-25SEP26
|
| 131 |
-
BTCUSDT-26JUN26
|
| 132 |
-
BTCUSDT-26MAR27
|
| 133 |
-
BTCUSDT-29MAY26
|
| 134 |
-
BTRUSDT
|
| 135 |
-
BUSDT
|
| 136 |
-
C98USDT
|
| 137 |
-
CAKEUSDT
|
| 138 |
-
CARVUSDT
|
| 139 |
-
CATIUSDT
|
| 140 |
-
CCUSDT
|
| 141 |
-
CELOUSDT
|
| 142 |
-
CETUSUSDT
|
| 143 |
-
CFGUSDT
|
| 144 |
-
CFXUSDT
|
| 145 |
-
CGPTUSDT
|
| 146 |
-
CHILLGUYUSDT
|
| 147 |
-
CHIPUSDT
|
| 148 |
-
CHRUSDT
|
| 149 |
-
CHZUSDT
|
| 150 |
-
CKBUSDT
|
| 151 |
-
CLANKERUSDT
|
| 152 |
-
CLOUDUSDT
|
| 153 |
-
CLOUSDT
|
| 154 |
-
CLUSDT
|
| 155 |
-
COAIUSDT
|
| 156 |
-
COMPUSDT
|
| 157 |
-
COOKIEUSDT
|
| 158 |
-
COREUSDT
|
| 159 |
-
COTIUSDT
|
| 160 |
-
COWUSDT
|
| 161 |
-
CROSSUSDT
|
| 162 |
-
CROUSDT
|
| 163 |
-
CRVPERP
|
| 164 |
-
CRVUSDT
|
| 165 |
-
CTCUSDT
|
| 166 |
-
CUSDT
|
| 167 |
-
CVCUSDT
|
| 168 |
-
CVXUSDT
|
| 169 |
-
CYBERUSDT
|
| 170 |
-
CYSUSDT
|
| 171 |
-
DASHUSDT
|
| 172 |
-
DBRUSDT
|
| 173 |
-
DEEPUSDT
|
| 174 |
-
DEGENUSDT
|
| 175 |
-
DEXEUSDT
|
| 176 |
-
DIAUSDT
|
| 177 |
-
DOGEPERP
|
| 178 |
-
DOGEUSDT
|
| 179 |
-
DOGEUSDT-01MAY26
|
| 180 |
-
DOGEUSDT-08MAY26
|
| 181 |
-
DOGEUSDT-24APR26
|
| 182 |
-
DOGEUSDT-29MAY26
|
| 183 |
-
DOGUSDT
|
| 184 |
-
DOLOUSDT
|
| 185 |
-
DOODUSDT
|
| 186 |
-
DOTPERP
|
| 187 |
-
DOTUSDT
|
| 188 |
-
DRIFTUSDT
|
| 189 |
-
DUSKUSDT
|
| 190 |
-
DYDXUSDT
|
| 191 |
-
DYMUSDT
|
| 192 |
-
EDENUSDT
|
| 193 |
-
EDGEUSDT
|
| 194 |
-
EDUUSDT
|
| 195 |
-
EGLDUSDT
|
| 196 |
-
EIGENPERP
|
| 197 |
-
EIGENUSDT
|
| 198 |
-
ELSAUSDT
|
| 199 |
-
ENAPERP
|
| 200 |
-
ENAUSDT
|
| 201 |
-
ENJUSDT
|
| 202 |
-
ENSOUSDT
|
| 203 |
-
ENSUSDT
|
| 204 |
-
EPICUSDT
|
| 205 |
-
ERAUSDT
|
| 206 |
-
ESPORTSUSDT
|
| 207 |
-
ESPUSDT
|
| 208 |
-
ESUSDT
|
| 209 |
-
ETCPERP
|
| 210 |
-
ETCUSDT
|
| 211 |
-
ETHBTCUSDT
|
| 212 |
-
ETHFIPERP
|
| 213 |
-
ETHFIUSDT
|
| 214 |
-
ETHPERP
|
| 215 |
-
ETHUSDT
|
| 216 |
-
ETHUSDT-01MAY26
|
| 217 |
-
ETHUSDT-08MAY26
|
| 218 |
-
ETHUSDT-24APR26
|
| 219 |
-
ETHUSDT-25DEC26
|
| 220 |
-
ETHUSDT-25SEP26
|
| 221 |
-
ETHUSDT-26JUN26
|
| 222 |
-
ETHUSDT-26MAR27
|
| 223 |
-
ETHUSDT-29MAY26
|
| 224 |
-
EULUSDT
|
| 225 |
-
EVAAUSDT
|
| 226 |
-
FARTCOINPERP
|
| 227 |
-
FARTCOINUSDT
|
| 228 |
-
FFUSDT
|
| 229 |
-
FHEUSDT
|
| 230 |
-
FIDAUSDT
|
| 231 |
-
FIGHTUSDT
|
| 232 |
-
FILPERP
|
| 233 |
-
FILUSDT
|
| 234 |
-
FIOUSDT
|
| 235 |
-
FLOCKUSDT
|
| 236 |
-
FLOWUSDT
|
| 237 |
-
FLRUSDT
|
| 238 |
-
FLUIDUSDT
|
| 239 |
-
FLUXUSDT
|
| 240 |
-
FOGOUSDT
|
| 241 |
-
FOLKSUSDT
|
| 242 |
-
FORMUSDT
|
| 243 |
-
FUSDT
|
| 244 |
-
GALAUSDT
|
| 245 |
-
GASUSDT
|
| 246 |
-
GENIUSUSDT
|
| 247 |
-
GIGAUSDT
|
| 248 |
-
GIGGLEUSDT
|
| 249 |
-
GLMUSDT
|
| 250 |
-
GMTUSDT
|
| 251 |
-
GMXUSDT
|
| 252 |
-
GNOUSDT
|
| 253 |
-
GOATUSDT
|
| 254 |
-
GODSUSDT
|
| 255 |
-
GPSUSDT
|
| 256 |
-
GRASSUSDT
|
| 257 |
-
GRIFFAINUSDT
|
| 258 |
-
GRTUSDT
|
| 259 |
-
GUNUSDT
|
| 260 |
-
HAEDALUSDT
|
| 261 |
-
HANAUSDT
|
| 262 |
-
HBARPERP
|
| 263 |
-
HBARUSDT
|
| 264 |
-
HEIUSDT
|
| 265 |
-
HEMIUSDT
|
| 266 |
-
HFTUSDT
|
| 267 |
-
HIGHUSDT
|
| 268 |
-
HIVEUSDT
|
| 269 |
-
HMSTRUSDT
|
| 270 |
-
HNTUSDT
|
| 271 |
-
HOLOUSDT
|
| 272 |
-
HOMEUSDT
|
| 273 |
-
HOODUSDT
|
| 274 |
-
HPERP
|
| 275 |
-
HPOS10IUSDT
|
| 276 |
-
HUMAUSDT
|
| 277 |
-
HUSDT
|
| 278 |
-
HYPEPERP
|
| 279 |
-
HYPERUSDT
|
| 280 |
-
HYPEUSDT
|
| 281 |
-
ICNTPERP
|
| 282 |
-
ICNTUSDT
|
| 283 |
-
ICPPERP
|
| 284 |
-
ICPUSDT
|
| 285 |
-
ICXUSDT
|
| 286 |
-
IDUSDT
|
| 287 |
-
ILVUSDT
|
| 288 |
-
IMXUSDT
|
| 289 |
-
INITUSDT
|
| 290 |
-
INJPERP
|
| 291 |
-
INJUSDT
|
| 292 |
-
INTCUSDT
|
| 293 |
-
INUSDT
|
| 294 |
-
INXUSDT
|
| 295 |
-
IOSTUSDT
|
| 296 |
-
IOTAUSDT
|
| 297 |
-
IOTXUSDT
|
| 298 |
-
IOUSDT
|
| 299 |
-
IPPERP
|
| 300 |
-
IPUSDT
|
| 301 |
-
IRYSUSDT
|
| 302 |
-
JASMYUSDT
|
| 303 |
-
JCTUSDT
|
| 304 |
-
JELLYJELLYUSDT
|
| 305 |
-
JSTUSDT
|
| 306 |
-
JTOUSDT
|
| 307 |
-
JUPUSDT
|
| 308 |
-
KAIAUSDT
|
| 309 |
-
KAITOUSDT
|
| 310 |
-
KASPERP
|
| 311 |
-
KASUSDT
|
| 312 |
-
KATUSDT
|
| 313 |
-
KAVAUSDT
|
| 314 |
-
KERNELUSDT
|
| 315 |
-
KGENUSDT
|
| 316 |
-
KITEUSDT
|
| 317 |
-
KMNOUSDT
|
| 318 |
-
KNCUSDT
|
| 319 |
-
KSMUSDT
|
| 320 |
-
LABUSDT
|
| 321 |
-
LAUSDT
|
| 322 |
-
LDOUSDT
|
| 323 |
-
LIGHTUSDT
|
| 324 |
-
LINEAUSDT
|
| 325 |
-
LINKPERP
|
| 326 |
-
LINKUSDT
|
| 327 |
-
LITUSDT
|
| 328 |
-
LPTUSDT
|
| 329 |
-
LQTYUSDT
|
| 330 |
-
LRCUSDT
|
| 331 |
-
LSKUSDT
|
| 332 |
-
LTCPERP
|
| 333 |
-
LTCUSDT
|
| 334 |
-
LUMIAUSDT
|
| 335 |
-
LUNA2USDT
|
| 336 |
-
LYNUSDT
|
| 337 |
-
MAGICUSDT
|
| 338 |
-
MAGMAUSDT
|
| 339 |
-
MANAUSDT
|
| 340 |
-
MANTAUSDT
|
| 341 |
-
MASKUSDT
|
| 342 |
-
MAVIAUSDT
|
| 343 |
-
MAVUSDT
|
| 344 |
-
MBOXUSDT
|
| 345 |
-
MELANIAUSDT
|
| 346 |
-
MEMEUSDT
|
| 347 |
-
MERLPERP
|
| 348 |
-
MERLUSDT
|
| 349 |
-
METISUSDT
|
| 350 |
-
METUSDT
|
| 351 |
-
MEUSDT
|
| 352 |
-
MEWUSDT
|
| 353 |
-
MINAUSDT
|
| 354 |
-
MIRAUSDT
|
| 355 |
-
MITOUSDT
|
| 356 |
-
MLNUSDT
|
| 357 |
-
MMTUSDT
|
| 358 |
-
MNTPERP
|
| 359 |
-
MNTUSDT
|
| 360 |
-
MNTUSDT-01MAY26
|
| 361 |
-
MNTUSDT-08MAY26
|
| 362 |
-
MNTUSDT-24APR26
|
| 363 |
-
MNTUSDT-29MAY26
|
| 364 |
-
MOCAUSDT
|
| 365 |
-
MONUSDT
|
| 366 |
-
MOODENGPERP
|
| 367 |
-
MOODENGUSDT
|
| 368 |
-
MORPHOUSDT
|
| 369 |
-
MOVEUSDT
|
| 370 |
-
MOVRUSDT
|
| 371 |
-
MTLUSDT
|
| 372 |
-
MUBARAKUSDT
|
| 373 |
-
MUSDT
|
| 374 |
-
MYXUSDT
|
| 375 |
-
NAORISUSDT
|
| 376 |
-
NEARUSDT
|
| 377 |
-
NEOUSDT
|
| 378 |
-
NEWTUSDT
|
| 379 |
-
NIGHTPERP
|
| 380 |
-
NIGHTUSDT
|
| 381 |
-
NILUSDT
|
| 382 |
-
NMRUSDT
|
| 383 |
-
NOMUSDT
|
| 384 |
-
NOTPERP
|
| 385 |
-
NOTUSDT
|
| 386 |
-
NXPCUSDT
|
| 387 |
-
OGNUSDT
|
| 388 |
-
OGUSDT
|
| 389 |
-
OKBUSDT
|
| 390 |
-
OLUSDT
|
| 391 |
-
ONDOPERP
|
| 392 |
-
ONDOUSDT
|
| 393 |
-
ONGUSDT
|
| 394 |
-
ONTUSDT
|
| 395 |
-
OPENUSDT
|
| 396 |
-
OPNUSDT
|
| 397 |
-
OPPERP
|
| 398 |
-
OPUSDT
|
| 399 |
-
ORBSUSDT
|
| 400 |
-
ORCAUSDT
|
| 401 |
-
ORDERUSDT
|
| 402 |
-
ORDIPERP
|
| 403 |
-
ORDIUSDT
|
| 404 |
-
OXTUSDT
|
| 405 |
-
PARTIUSDT
|
| 406 |
-
PAXGPERP
|
| 407 |
-
PAXGUSDT
|
| 408 |
-
PEAQUSDT
|
| 409 |
-
PENDLEPERP
|
| 410 |
-
PENDLEUSDT
|
| 411 |
-
PENGUPERP
|
| 412 |
-
PENGUUSDT
|
| 413 |
-
PEOPLEUSDT
|
| 414 |
-
PHAUSDT
|
| 415 |
-
PIEVERSEUSDT
|
| 416 |
-
PIPPINUSDT
|
| 417 |
-
PIXELUSDT
|
| 418 |
-
PLAYSOUTUSDT
|
| 419 |
-
PLUMEUSDT
|
| 420 |
-
PNUTPERP
|
| 421 |
-
PNUTUSDT
|
| 422 |
-
POLPERP
|
| 423 |
-
POLUSDT
|
| 424 |
-
POLYXUSDT
|
| 425 |
-
POPCATPERP
|
| 426 |
-
POPCATUSDT
|
| 427 |
-
PORTALUSDT
|
| 428 |
-
POWERUSDT
|
| 429 |
-
POWRUSDT
|
| 430 |
-
PRLUSDT
|
| 431 |
-
PROMPTUSDT
|
| 432 |
-
PROVEUSDT
|
| 433 |
-
PTBUSDT
|
| 434 |
-
PUFFERUSDT
|
| 435 |
-
PUMPBTCUSDT
|
| 436 |
-
PUMPFUNPERP
|
| 437 |
-
PUMPFUNUSDT
|
| 438 |
-
PUNDIXUSDT
|
| 439 |
-
PYRUSDT
|
| 440 |
-
PYTHUSDT
|
| 441 |
-
QNTUSDT
|
| 442 |
-
QTUMUSDT
|
| 443 |
-
QUSDT
|
| 444 |
-
RAREUSDT
|
| 445 |
-
RAVEUSDT
|
| 446 |
-
RAYDIUMUSDT
|
| 447 |
-
RDNTUSDT
|
| 448 |
-
RECALLUSDT
|
| 449 |
-
REDUSDT
|
| 450 |
-
RENDERUSDT
|
| 451 |
-
REQUSDT
|
| 452 |
-
RESOLVPERP
|
| 453 |
-
RESOLVUSDT
|
| 454 |
-
REZUSDT
|
| 455 |
-
RIVERUSDT
|
| 456 |
-
RLCUSDT
|
| 457 |
-
RLUSDUSDT
|
| 458 |
-
ROAMUSDT
|
| 459 |
-
ROBOUSDT
|
| 460 |
-
RONINUSDT
|
| 461 |
-
ROSEUSDT
|
| 462 |
-
RPLUSDT
|
| 463 |
-
RSRUSDT
|
| 464 |
-
RUNEUSDT
|
| 465 |
-
RVNUSDT
|
| 466 |
-
SAFEUSDT
|
| 467 |
-
SAGAUSDT
|
| 468 |
-
SAHARAPERP
|
| 469 |
-
SAHARAUSDT
|
| 470 |
-
SANDUSDT
|
| 471 |
-
SAPIENUSDT
|
| 472 |
-
SCRTUSDT
|
| 473 |
-
SCRUSDT
|
| 474 |
-
SCUSDT
|
| 475 |
-
SEIPERP
|
| 476 |
-
SEIUSDT
|
| 477 |
-
SENTUSDT
|
| 478 |
-
SHELLUSDT
|
| 479 |
-
SHIB1000PERP
|
| 480 |
-
SHIB1000USDT
|
| 481 |
-
SIGNUSDT
|
| 482 |
-
SIRENUSDT
|
| 483 |
-
SKLUSDT
|
| 484 |
-
SKRUSDT
|
| 485 |
-
SKYUSDT
|
| 486 |
-
SLPUSDT
|
| 487 |
-
SNTUSDT
|
| 488 |
-
SNXUSDT
|
| 489 |
-
SOLAYERUSDT
|
| 490 |
-
SOLPERP
|
| 491 |
-
SOLUSDT
|
| 492 |
-
SOLUSDT-01MAY26
|
| 493 |
-
SOLUSDT-08MAY26
|
| 494 |
-
SOLUSDT-24APR26
|
| 495 |
-
SOLUSDT-29MAY26
|
| 496 |
-
SOLVUSDT
|
| 497 |
-
SOMIUSDT
|
| 498 |
-
SONICUSDT
|
| 499 |
-
SOONUSDT
|
| 500 |
-
SOPHUSDT
|
| 501 |
-
SOSOUSDT
|
| 502 |
-
SPACEUSDT
|
| 503 |
-
SPKUSDT
|
| 504 |
-
SPORTFUNUSDT
|
| 505 |
-
SPXPERP
|
| 506 |
-
SPXUSDT
|
| 507 |
-
SQDUSDT
|
| 508 |
-
SSVUSDT
|
| 509 |
-
STABLEUSDT
|
| 510 |
-
STBLUSDT
|
| 511 |
-
STEEMUSDT
|
| 512 |
-
STGUSDT
|
| 513 |
-
STORJUSDT
|
| 514 |
-
STOUSDT
|
| 515 |
-
STRKPERP
|
| 516 |
-
STRKUSDT
|
| 517 |
-
STXUSDT
|
| 518 |
-
SUIPERP
|
| 519 |
-
SUIUSDT
|
| 520 |
-
SUNUSDT
|
| 521 |
-
SUPERUSDT
|
| 522 |
-
SUSDT
|
| 523 |
-
SUSHIUSDT
|
| 524 |
-
SWARMSUSDT
|
| 525 |
-
SXTUSDT
|
| 526 |
-
SYNUSDT
|
| 527 |
-
SYRUPUSDT
|
| 528 |
-
TACUSDT
|
| 529 |
-
TAIKOUSDT
|
| 530 |
-
TAOPERP
|
| 531 |
-
TAOUSDT
|
| 532 |
-
TAUSDT
|
| 533 |
-
THETAUSDT
|
| 534 |
-
THEUSDT
|
| 535 |
-
TIAPERP
|
| 536 |
-
TIAUSDT
|
| 537 |
-
TLMUSDT
|
| 538 |
-
TNSRUSDT
|
| 539 |
-
TONPERP
|
| 540 |
-
TONUSDT
|
| 541 |
-
TOWNSUSDT
|
| 542 |
-
TRBUSDT
|
| 543 |
-
TREEUSDT
|
| 544 |
-
TRIAUSDT
|
| 545 |
-
TRUMPPERP
|
| 546 |
-
TRUMPUSDT
|
| 547 |
-
TRUSTUSDT
|
| 548 |
-
TRUTHUSDT
|
| 549 |
-
TRUUSDT
|
| 550 |
-
TRXPERP
|
| 551 |
-
TRXUSDT
|
| 552 |
-
TSLAUSDT
|
| 553 |
-
TSMUSDT
|
| 554 |
-
TSTBSCUSDT
|
| 555 |
-
TURTLEUSDT
|
| 556 |
-
TUSDT
|
| 557 |
-
TUTUSDT
|
| 558 |
-
TWTPERP
|
| 559 |
-
TWTUSDT
|
| 560 |
-
UAIUSDT
|
| 561 |
-
UBUSDT
|
| 562 |
-
UMAUSDT
|
| 563 |
-
UNIPERP
|
| 564 |
-
UNIUSDT
|
| 565 |
-
USD1USDT
|
| 566 |
-
USDCUSDT
|
| 567 |
-
USDEUSDT
|
| 568 |
-
USELESSUSDT
|
| 569 |
-
USTCUSDT
|
| 570 |
-
USUALUSDT
|
| 571 |
-
USUSDT
|
| 572 |
-
VANAUSDT
|
| 573 |
-
VANRYUSDT
|
| 574 |
-
VELODROMEUSDT
|
| 575 |
-
VELOUSDT
|
| 576 |
-
VELVETUSDT
|
| 577 |
-
VETUSDT
|
| 578 |
-
VINEUSDT
|
| 579 |
-
VIRTUALPERP
|
| 580 |
-
VIRTUALUSDT
|
| 581 |
-
VVVUSDT
|
| 582 |
-
WALUSDT
|
| 583 |
-
WAVESUSDT
|
| 584 |
-
WAXPUSDT
|
| 585 |
-
WCTUSDT
|
| 586 |
-
WETUSDT
|
| 587 |
-
WHITEWHALEUSDT
|
| 588 |
-
WIFPERP
|
| 589 |
-
WIFUSDT
|
| 590 |
-
WLDPERP
|
| 591 |
-
WLDUSDT
|
| 592 |
-
WLFIPERP
|
| 593 |
-
WLFIUSDT
|
| 594 |
-
WOOUSDT
|
| 595 |
-
WUSDT
|
| 596 |
-
XAGUSDT
|
| 597 |
-
XAIUSDT
|
| 598 |
-
XANUSDT
|
| 599 |
-
XAUTPERP
|
| 600 |
-
XAUTUSDT
|
| 601 |
-
XAUTUSDT-01MAY26
|
| 602 |
-
XAUTUSDT-08MAY26
|
| 603 |
-
XAUTUSDT-24APR26
|
| 604 |
-
XAUTUSDT-29MAY26
|
| 605 |
-
XAUUSDT
|
| 606 |
-
XCNUSDT
|
| 607 |
-
XDCUSDT
|
| 608 |
-
XIONUSDT
|
| 609 |
-
XLMPERP
|
| 610 |
-
XLMUSDT
|
| 611 |
-
XMRPERP
|
| 612 |
-
XMRUSDT
|
| 613 |
-
XNYUSDT
|
| 614 |
-
XPINUSDT
|
| 615 |
-
XPLPERP
|
| 616 |
-
XPLUSDT
|
| 617 |
-
XRPPERP
|
| 618 |
-
XRPUSDT
|
| 619 |
-
XRPUSDT-01MAY26
|
| 620 |
-
XRPUSDT-08MAY26
|
| 621 |
-
XRPUSDT-24APR26
|
| 622 |
-
XRPUSDT-29MAY26
|
| 623 |
-
XTZUSDT
|
| 624 |
-
XVGUSDT
|
| 625 |
-
XVSUSDT
|
| 626 |
-
YBUSDT
|
| 627 |
-
YFIUSDT
|
| 628 |
-
YGGUSDT
|
| 629 |
-
YZYUSDT
|
| 630 |
-
ZAMAUSDT
|
| 631 |
-
ZBCNUSDT
|
| 632 |
-
ZBTUSDT
|
| 633 |
-
ZECUSDT
|
| 634 |
-
ZENUSDT
|
| 635 |
-
ZEREBROUSDT
|
| 636 |
-
ZETAUSDT
|
| 637 |
-
ZILUSDT
|
| 638 |
-
ZKCUSDT
|
| 639 |
-
ZKPUSDT
|
| 640 |
-
ZKUSDT
|
| 641 |
-
ZORAUSDT
|
| 642 |
-
ZROUSDT
|
| 643 |
-
ZRXUSDT
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
all_symbols_3.txt → backend/__init__.py
RENAMED
|
File without changes
|
backend/check_eurusd.py
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
|
| 4 |
-
async def check_eurusd_no_plus():
|
| 5 |
-
symbol = "EURUSD"
|
| 6 |
-
category = "linear"
|
| 7 |
-
url = f"https://api.bybit.com/v5/market/instruments-info?category={category}&symbol={symbol}"
|
| 8 |
-
async with httpx.AsyncClient() as client:
|
| 9 |
-
resp = await client.get(url)
|
| 10 |
-
data = resp.json()
|
| 11 |
-
print(f"Result for {symbol} in {category}: {data}")
|
| 12 |
-
|
| 13 |
-
if __name__ == "__main__":
|
| 14 |
-
asyncio.run(check_eurusd_no_plus())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/check_wallet.py
DELETED
|
@@ -1,39 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
import os
|
| 4 |
-
import time
|
| 5 |
-
import hmac
|
| 6 |
-
import hashlib
|
| 7 |
-
from dotenv import load_dotenv
|
| 8 |
-
|
| 9 |
-
load_dotenv()
|
| 10 |
-
|
| 11 |
-
async def check_wallet():
|
| 12 |
-
api_key = os.getenv("BYBIT_API_KEY")
|
| 13 |
-
api_secret = os.getenv("BYBIT_API_SECRET")
|
| 14 |
-
|
| 15 |
-
url = "https://api.bybit.com/v5/account/wallet-balance"
|
| 16 |
-
params = {"accountType": "UNIFIED"}
|
| 17 |
-
|
| 18 |
-
timestamp = str(int(time.time() * 1000))
|
| 19 |
-
recv_window = "5000"
|
| 20 |
-
sorted_params = "&".join([f"{k}={v}" for k, v in sorted(params.items())])
|
| 21 |
-
raw_str = timestamp + api_key + recv_window + sorted_params
|
| 22 |
-
signature = hmac.new(api_secret.encode('utf-8'),
|
| 23 |
-
raw_str.encode('utf-8'), hashlib.sha256).hexdigest()
|
| 24 |
-
|
| 25 |
-
headers = {
|
| 26 |
-
'X-BAPI-API-KEY': api_key,
|
| 27 |
-
'X-BAPI-TIMESTAMP': timestamp,
|
| 28 |
-
'X-BAPI-SIGN-TYPE': '2',
|
| 29 |
-
'X-BAPI-RECV-WINDOW': recv_window,
|
| 30 |
-
'X-BAPI-SIGN': signature
|
| 31 |
-
}
|
| 32 |
-
|
| 33 |
-
async with httpx.AsyncClient() as client:
|
| 34 |
-
resp = await client.get(url, params=params, headers=headers)
|
| 35 |
-
data = resp.json()
|
| 36 |
-
print(f"Wallet Balance: {data}")
|
| 37 |
-
|
| 38 |
-
if __name__ == "__main__":
|
| 39 |
-
asyncio.run(check_wallet())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/check_wallet_sync.py
DELETED
|
@@ -1,44 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
import os
|
| 4 |
-
import time
|
| 5 |
-
import hmac
|
| 6 |
-
import hashlib
|
| 7 |
-
from dotenv import load_dotenv
|
| 8 |
-
|
| 9 |
-
load_dotenv()
|
| 10 |
-
|
| 11 |
-
async def check_wallet_fixed():
|
| 12 |
-
api_key = os.getenv("BYBIT_API_KEY")
|
| 13 |
-
api_secret = os.getenv("BYBIT_API_SECRET")
|
| 14 |
-
|
| 15 |
-
# Get server time first
|
| 16 |
-
async with httpx.AsyncClient() as client:
|
| 17 |
-
time_resp = await client.get("https://api.bybit.com/v5/market/time")
|
| 18 |
-
server_time = time_resp.json()["result"]["timeNano"][:13]
|
| 19 |
-
|
| 20 |
-
url = "https://api.bybit.com/v5/account/wallet-balance"
|
| 21 |
-
params = {"accountType": "UNIFIED"}
|
| 22 |
-
|
| 23 |
-
timestamp = server_time
|
| 24 |
-
recv_window = "20000" # Larger window
|
| 25 |
-
sorted_params = "&".join([f"{k}={v}" for k, v in sorted(params.items())])
|
| 26 |
-
raw_str = timestamp + api_key + recv_window + sorted_params
|
| 27 |
-
signature = hmac.new(api_secret.encode('utf-8'),
|
| 28 |
-
raw_str.encode('utf-8'), hashlib.sha256).hexdigest()
|
| 29 |
-
|
| 30 |
-
headers = {
|
| 31 |
-
'X-BAPI-API-KEY': api_key,
|
| 32 |
-
'X-BAPI-TIMESTAMP': timestamp,
|
| 33 |
-
'X-BAPI-SIGN-TYPE': '2',
|
| 34 |
-
'X-BAPI-RECV-WINDOW': recv_window,
|
| 35 |
-
'X-BAPI-SIGN': signature
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
async with httpx.AsyncClient() as client:
|
| 39 |
-
resp = await client.get(url, params=params, headers=headers)
|
| 40 |
-
data = resp.json()
|
| 41 |
-
print(f"Wallet Balance: {data}")
|
| 42 |
-
|
| 43 |
-
if __name__ == "__main__":
|
| 44 |
-
asyncio.run(check_wallet_fixed())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/discover_bybit.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
|
| 4 |
-
async def list_bybit_instruments():
|
| 5 |
-
categories = ["spot", "linear", "inverse", "option"]
|
| 6 |
-
async with httpx.AsyncClient() as client:
|
| 7 |
-
for cat in categories:
|
| 8 |
-
url = f"https://api.bybit.com/v5/market/instruments-info?category={cat}"
|
| 9 |
-
print(f"Fetching category: {cat}...")
|
| 10 |
-
try:
|
| 11 |
-
resp = await client.get(url)
|
| 12 |
-
data = resp.json()
|
| 13 |
-
if data.get("retCode") == 0:
|
| 14 |
-
symbols = [i["symbol"] for i in data["result"]["list"]]
|
| 15 |
-
print(f"Found {len(symbols)} symbols in {cat}")
|
| 16 |
-
# Look for anything non-crypto
|
| 17 |
-
tradfi_like = [s for s in symbols if "+" in s or any(x in s for x in ["EUR", "GBP", "JPY", "XAU", "XAG", "WTI", "UKO"])]
|
| 18 |
-
if tradfi_like:
|
| 19 |
-
print(f"TradFi-like symbols in {cat}: {tradfi_like[:20]}...")
|
| 20 |
-
else:
|
| 21 |
-
print(f"Failed to fetch {cat}: {data.get('retMsg')}")
|
| 22 |
-
except Exception as e:
|
| 23 |
-
print(f"Error fetching {cat}: {e}")
|
| 24 |
-
|
| 25 |
-
if __name__ == "__main__":
|
| 26 |
-
asyncio.run(list_bybit_instruments())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/find_any_suffix.py
DELETED
|
@@ -1,36 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
import os
|
| 4 |
-
import time
|
| 5 |
-
import hmac
|
| 6 |
-
import hashlib
|
| 7 |
-
from dotenv import load_dotenv
|
| 8 |
-
|
| 9 |
-
load_dotenv()
|
| 10 |
-
|
| 11 |
-
async def find_any_dot_s():
|
| 12 |
-
api_key = os.getenv("BYBIT_API_KEY")
|
| 13 |
-
api_secret = os.getenv("BYBIT_API_SECRET")
|
| 14 |
-
|
| 15 |
-
url = "https://api.bybit.com/v5/market/instruments-info"
|
| 16 |
-
params = {"category": "linear", "limit": 1000}
|
| 17 |
-
|
| 18 |
-
async with httpx.AsyncClient() as client:
|
| 19 |
-
resp = await client.get(url, params=params)
|
| 20 |
-
data = resp.json()
|
| 21 |
-
if data.get("retCode") == 0:
|
| 22 |
-
symbols = [item["symbol"] for item in data["result"]["list"]]
|
| 23 |
-
dot_s = [s for s in symbols if ".s" in s]
|
| 24 |
-
plus = [s for s in symbols if "+" in s]
|
| 25 |
-
print(f"Total symbols: {len(symbols)}")
|
| 26 |
-
print(f"Symbols with .s: {dot_s}")
|
| 27 |
-
print(f"Symbols with +: {plus}")
|
| 28 |
-
|
| 29 |
-
# Search for XAU specifically
|
| 30 |
-
xau = [s for s in symbols if "XAU" in s]
|
| 31 |
-
print(f"XAU matches: {xau}")
|
| 32 |
-
else:
|
| 33 |
-
print(f"Error: {data}")
|
| 34 |
-
|
| 35 |
-
if __name__ == "__main__":
|
| 36 |
-
asyncio.run(find_any_dot_s())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{Kronos-master → backend/kronos_core}/.gitignore
RENAMED
|
File without changes
|
{Kronos-master → backend/kronos_core}/LICENSE
RENAMED
|
File without changes
|
{Kronos-master → backend/kronos_core}/README.md
RENAMED
|
File without changes
|
{Kronos-master → backend/kronos_core}/model/__init__.py
RENAMED
|
File without changes
|
{Kronos-master → backend/kronos_core}/model/kronos.py
RENAMED
|
File without changes
|
{Kronos-master → backend/kronos_core}/model/module.py
RENAMED
|
File without changes
|
{Kronos-master → backend/kronos_core}/requirements.txt
RENAMED
|
File without changes
|
backend/list_linear.py
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
|
| 4 |
-
async def list_bybit_linear():
|
| 5 |
-
url = f"https://api.bybit.com/v5/market/instruments-info?category=linear"
|
| 6 |
-
async with httpx.AsyncClient() as client:
|
| 7 |
-
resp = await client.get(url)
|
| 8 |
-
data = resp.json()
|
| 9 |
-
if data.get("retCode") == 0:
|
| 10 |
-
symbols = [i["symbol"] for i in data["result"]["list"]]
|
| 11 |
-
print(f"First 50 linear symbols: {symbols[:50]}")
|
| 12 |
-
|
| 13 |
-
if __name__ == "__main__":
|
| 14 |
-
asyncio.run(list_bybit_linear())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/list_pybit_final.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
from pybit.unified_trading import HTTP
|
| 2 |
-
import os
|
| 3 |
-
from dotenv import load_dotenv
|
| 4 |
-
|
| 5 |
-
load_dotenv()
|
| 6 |
-
|
| 7 |
-
def list_linear_pybit():
|
| 8 |
-
api_key = os.getenv("BYBIT_API_KEY")
|
| 9 |
-
api_secret = os.getenv("BYBIT_API_SECRET")
|
| 10 |
-
|
| 11 |
-
session = HTTP(
|
| 12 |
-
api_key=api_key,
|
| 13 |
-
api_secret=api_secret,
|
| 14 |
-
)
|
| 15 |
-
|
| 16 |
-
try:
|
| 17 |
-
data = session.get_instruments_info(
|
| 18 |
-
category="linear",
|
| 19 |
-
limit=1000
|
| 20 |
-
)
|
| 21 |
-
if data.get("retCode") == 0:
|
| 22 |
-
symbols = [item["symbol"] for item in data["result"]["list"]]
|
| 23 |
-
print(f"Found {len(symbols)} symbols.")
|
| 24 |
-
with open("all_symbols_final.txt", "w") as f:
|
| 25 |
-
for s in symbols:
|
| 26 |
-
f.write(s + "\n")
|
| 27 |
-
|
| 28 |
-
# Search for specific substrings
|
| 29 |
-
for search in ["XAU", "EUR", "SPX", "AAPL", "TSLA"]:
|
| 30 |
-
matches = [s for s in symbols if search in s]
|
| 31 |
-
print(f"Matches for {search}: {matches}")
|
| 32 |
-
else:
|
| 33 |
-
print(f"Error: {data}")
|
| 34 |
-
except Exception as e:
|
| 35 |
-
print(f"Error: {e}")
|
| 36 |
-
|
| 37 |
-
if __name__ == "__main__":
|
| 38 |
-
list_linear_pybit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/main.py
CHANGED
|
@@ -64,6 +64,8 @@ from dotenv import load_dotenv
|
|
| 64 |
|
| 65 |
# D-1: Load environment variables from .env file (v6.0)
|
| 66 |
load_dotenv()
|
|
|
|
|
|
|
| 67 |
import re
|
| 68 |
import sys
|
| 69 |
import time
|
|
@@ -201,7 +203,7 @@ else:
|
|
| 201 |
PROJECT_ROOT = os.path.dirname(CURRENT_DIR)
|
| 202 |
logger.info("Running in DEV mode. PROJECT_ROOT: %s", PROJECT_ROOT)
|
| 203 |
|
| 204 |
-
KRONOS_PATH = os.path.join(
|
| 205 |
|
| 206 |
if KRONOS_PATH not in sys.path:
|
| 207 |
sys.path.append(KRONOS_PATH)
|
|
@@ -314,7 +316,7 @@ class PersistentCache:
|
|
| 314 |
except Exception as ex:
|
| 315 |
logger.error("[Persistence] Eviction error: %s", ex)
|
| 316 |
|
| 317 |
-
persistent_cache = PersistentCache(os.path.join(PROJECT_ROOT, "kronos_v5.db"))
|
| 318 |
|
| 319 |
|
| 320 |
# B-12: Global Configuration Instances
|
|
@@ -1409,6 +1411,26 @@ def _stoch_rsi(close: np.ndarray, rsi_period=14, stoch_period=14,
|
|
| 1409 |
return k_smooth.values, d_smooth.values
|
| 1410 |
|
| 1411 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1412 |
def _sma(arr: np.ndarray, period: int) -> np.ndarray:
|
| 1413 |
"""Vectorized Simple Moving Average."""
|
| 1414 |
if len(arr) == 0: return np.array([], dtype=float)
|
|
@@ -1562,6 +1584,22 @@ def _ichimoku_base(high: np.ndarray, low: np.ndarray, period: int = 26) -> np.nd
|
|
| 1562 |
return ((hh + ll) / 2.0).values
|
| 1563 |
|
| 1564 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1565 |
def _vwma(close: np.ndarray, volume: np.ndarray, period: int = 20) -> np.ndarray:
|
| 1566 |
"""Vectorized Volume Weighted Moving Average."""
|
| 1567 |
cv = pd.Series(close * volume)
|
|
@@ -1680,6 +1718,11 @@ def _osc_action(name: str, value: float, **kw) -> str:
|
|
| 1680 |
"""Classify oscillator value as 'Mua' / 'Bán' / 'Trung lập'."""
|
| 1681 |
if value is None or math.isnan(value):
|
| 1682 |
return "Trung lập"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1683 |
if name == "roc":
|
| 1684 |
return "Mua" if value > 1.0 else "Bán" if value < -1.0 else "Trung lập"
|
| 1685 |
if name == "trix":
|
|
@@ -1689,13 +1732,19 @@ def _osc_action(name: str, value: float, **kw) -> str:
|
|
| 1689 |
if name == "cmo":
|
| 1690 |
return "Mua" if value > 20 else "Bán" if value < -20 else "Trung lập"
|
| 1691 |
if name == "dpo":
|
| 1692 |
-
return "
|
| 1693 |
if name == "aroon":
|
| 1694 |
return "Mua" if value > 25 else "Bán" if value < -25 else "Trung lập"
|
| 1695 |
if name == "tsi":
|
| 1696 |
return "Mua" if value > 5 else "Bán" if value < -5 else "Trung lập"
|
| 1697 |
if name == "rsi":
|
| 1698 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1699 |
if name == "stoch":
|
| 1700 |
return "Bán" if value > 80 else "Mua" if value < 20 else "Trung lập"
|
| 1701 |
if name == "cci":
|
|
@@ -1707,7 +1756,12 @@ def _osc_action(name: str, value: float, **kw) -> str:
|
|
| 1707 |
return "Trung lập"
|
| 1708 |
return "Mua" if plus_di > minus_di else "Bán"
|
| 1709 |
if name == "ao":
|
| 1710 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1711 |
if name == "momentum":
|
| 1712 |
return "Mua" if value > 0 else "Bán" if value < 0 else "Trung lập"
|
| 1713 |
if name == "macd":
|
|
@@ -1718,17 +1772,52 @@ def _osc_action(name: str, value: float, **kw) -> str:
|
|
| 1718 |
if name == "williams":
|
| 1719 |
return "Mua" if value < -80 else "Bán" if value > -20 else "Trung lập"
|
| 1720 |
if name == "bbp":
|
| 1721 |
-
|
|
|
|
| 1722 |
if name == "ultimate":
|
| 1723 |
return "Bán" if value > 70 else "Mua" if value < 30 else "Trung lập"
|
| 1724 |
return "Trung lập"
|
| 1725 |
|
| 1726 |
|
| 1727 |
-
def _ma_action(price: float, ma_val: float) -> str:
|
| 1728 |
-
"""Classify MA
|
| 1729 |
if ma_val is None or math.isnan(ma_val):
|
| 1730 |
return "Trung lập"
|
| 1731 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1732 |
|
| 1733 |
|
| 1734 |
def _osc_signal_score(name: str, value: float, **kw) -> float:
|
|
@@ -1739,7 +1828,12 @@ def _osc_signal_score(name: str, value: float, **kw) -> float:
|
|
| 1739 |
scale = max(float(kw.get("scale", 1.0) or 1.0), 1e-8)
|
| 1740 |
|
| 1741 |
if name == "rsi":
|
| 1742 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1743 |
if name in {"stoch", "stoch_rsi"}:
|
| 1744 |
return _clamp((50.0 - value) / 30.0, -1.0, 1.0)
|
| 1745 |
if name == "cci":
|
|
@@ -1750,13 +1844,18 @@ def _osc_signal_score(name: str, value: float, **kw) -> float:
|
|
| 1750 |
strength = _clamp((value - 18.0) / 22.0, 0.0, 1.0)
|
| 1751 |
direction = math.tanh((plus_di - minus_di) / max(plus_di + minus_di, 10.0) * 3.0)
|
| 1752 |
return _clamp(direction * strength, -1.0, 1.0)
|
| 1753 |
-
if name in {"ao", "momentum"
|
| 1754 |
return _clamp(math.tanh(value / scale), -1.0, 1.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1755 |
if name == "macd":
|
| 1756 |
signal = float(kw.get("signal", 0.0) or 0.0)
|
| 1757 |
return _clamp(math.tanh((value - signal) / scale), -1.0, 1.0)
|
| 1758 |
if name == "williams":
|
| 1759 |
-
return _clamp(((-
|
| 1760 |
if name == "ultimate":
|
| 1761 |
return _clamp((50.0 - value) / 25.0, -1.0, 1.0)
|
| 1762 |
if name == "roc":
|
|
@@ -2542,43 +2641,43 @@ def _build_reasoning(
|
|
| 2542 |
|
| 2543 |
# ── Technical Analysis Weights & Constants (Dashboard Rework v6.1) ────────────
|
| 2544 |
OSC_WEIGHTS = {
|
| 2545 |
-
"rsi": 2.
|
| 2546 |
-
"macd": 2.
|
| 2547 |
-
"stoch_rsi": 1.
|
| 2548 |
"stoch": 1.3, # Classic momentum
|
| 2549 |
-
"cci": 1.
|
| 2550 |
-
"adx": 1.
|
| 2551 |
-
"williams": 1.
|
| 2552 |
"ultimate": 1.2, # Multi-period, less noise
|
| 2553 |
"bbp": 1.0, # Trend-following
|
| 2554 |
"ao": 0.9, # Noisy, short-term only
|
| 2555 |
"momentum": 0.8, # Lagging, lowest weight
|
| 2556 |
-
"roc": 1.
|
| 2557 |
-
"trix": 1.
|
| 2558 |
-
"ppo": 1.
|
| 2559 |
"cmo": 1.3, # Momentum regime strength
|
| 2560 |
-
"dpo":
|
| 2561 |
-
"aroon": 1.
|
| 2562 |
-
"tsi": 1.
|
| 2563 |
}
|
| 2564 |
|
| 2565 |
MA_WEIGHT_MAP = {
|
| 2566 |
-
"ema_200":
|
| 2567 |
"ema_100": 2.3, "sma_100": 2.1,
|
| 2568 |
-
"ema_50":
|
| 2569 |
"ema_30": 1.3, "sma_30": 1.2,
|
| 2570 |
-
"ema_20": 1.
|
| 2571 |
"ema_10": 0.8, "sma_10": 0.7,
|
| 2572 |
-
"
|
| 2573 |
}
|
| 2574 |
|
| 2575 |
def _extract_osc_key(label: str) -> str:
|
| 2576 |
l = label.lower()
|
| 2577 |
-
if "rsi" in l and "nhanh" not in l: return "rsi"
|
| 2578 |
if "macd" in l: return "macd"
|
| 2579 |
if "stochastic %k" in l: return "stoch"
|
| 2580 |
if "nhanh" in l or "stoch_rsi" in l: return "stoch_rsi"
|
| 2581 |
-
if "cci" in l: return "cci"
|
| 2582 |
if "định hướng" in l or "adx" in l: return "adx"
|
| 2583 |
if "williams" in l: return "williams"
|
| 2584 |
if "ultimate" in l: return "ultimate"
|
|
@@ -2596,6 +2695,8 @@ def _extract_osc_key(label: str) -> str:
|
|
| 2596 |
|
| 2597 |
def _get_ma_weight(label: str) -> float:
|
| 2598 |
l = label.lower()
|
|
|
|
|
|
|
| 2599 |
if "hàm mũ" in l:
|
| 2600 |
p = re.findall(r"\d+", l)
|
| 2601 |
if p: return MA_WEIGHT_MAP.get(f"ema_{p[0]}", 1.0)
|
|
@@ -2603,17 +2704,26 @@ def _get_ma_weight(label: str) -> float:
|
|
| 2603 |
p = re.findall(r"\d+", l)
|
| 2604 |
if p: return MA_WEIGHT_MAP.get(f"sma_{p[0]}", 1.0)
|
| 2605 |
if "ichimoku" in l: return MA_WEIGHT_MAP["ichimoku"]
|
| 2606 |
-
if "vwma" in l or "tỷ trọng tuyến tính" in l: return MA_WEIGHT_MAP["vwma_20"]
|
| 2607 |
if "hull" in l: return MA_WEIGHT_MAP["hull_9"]
|
| 2608 |
return 1.0
|
| 2609 |
|
| 2610 |
-
def _gauge_to_signal(gauge: float) -> str:
|
| 2611 |
-
"""
|
| 2612 |
-
|
| 2613 |
-
|
| 2614 |
-
|
| 2615 |
-
|
| 2616 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2617 |
|
| 2618 |
|
| 2619 |
def _gauge_to_normalized_score(gauge: float) -> float:
|
|
@@ -2671,24 +2781,28 @@ def _forecast_path_metrics(p50_path: np.ndarray, last_close: float) -> Dict[str,
|
|
| 2671 |
"mean_step_return_pct": round(mean_step_return_pct, 3),
|
| 2672 |
}
|
| 2673 |
|
| 2674 |
-
def _calc_osc_score(osc_data: list) -> dict:
|
| 2675 |
"""MODULE 1: Weighted scoring for oscillators."""
|
| 2676 |
total_weight = 0.0
|
| 2677 |
weighted_score = 0.0
|
| 2678 |
buy = sell = neutral = 0
|
|
|
|
| 2679 |
|
| 2680 |
for item in osc_data:
|
| 2681 |
-
key = _extract_osc_key(item["name"])
|
| 2682 |
w = OSC_WEIGHTS.get(key, 1.0)
|
| 2683 |
v = float(item.get("score", 0.0) or 0.0)
|
| 2684 |
# Robust case-insensitive check
|
| 2685 |
act = str(item.get("action", "")).strip().lower()
|
| 2686 |
if act == "mua":
|
| 2687 |
buy += 1
|
|
|
|
| 2688 |
elif act == "bán":
|
| 2689 |
sell += 1
|
|
|
|
| 2690 |
else:
|
| 2691 |
neutral += 1
|
|
|
|
| 2692 |
|
| 2693 |
weighted_score += w * v
|
| 2694 |
total_weight += w
|
|
@@ -2698,28 +2812,33 @@ def _calc_osc_score(osc_data: list) -> dict:
|
|
| 2698 |
return {
|
| 2699 |
"gauge": round(gauge, 1),
|
| 2700 |
"normalized_score": round(normalized, 4),
|
| 2701 |
-
"signal": _gauge_to_signal(gauge),
|
| 2702 |
-
"buy": buy, "sell": sell, "neutral": neutral
|
|
|
|
|
|
|
|
|
|
| 2703 |
}
|
| 2704 |
|
| 2705 |
-
def _calc_ma_score(ma_data: list, closes: np.ndarray) -> dict:
|
| 2706 |
"""MODULE 2: Period-weighted scoring for MAs + Cross Bonus."""
|
| 2707 |
total_weight = 0.0
|
| 2708 |
weighted_score = 0.0
|
| 2709 |
buy = sell = neutral = 0
|
|
|
|
| 2710 |
|
| 2711 |
for item in ma_data:
|
| 2712 |
-
w = _get_ma_weight(item["name"])
|
| 2713 |
-
v = 0.0
|
| 2714 |
act = str(item.get("action", "")).strip().lower()
|
| 2715 |
if act == "mua":
|
| 2716 |
-
v = 1.0
|
| 2717 |
buy += 1
|
|
|
|
| 2718 |
elif act == "bán":
|
| 2719 |
-
v = -1.0
|
| 2720 |
sell += 1
|
|
|
|
| 2721 |
else:
|
| 2722 |
neutral += 1
|
|
|
|
| 2723 |
|
| 2724 |
weighted_score += w * v
|
| 2725 |
total_weight += w
|
|
@@ -2740,8 +2859,11 @@ def _calc_ma_score(ma_data: list, closes: np.ndarray) -> dict:
|
|
| 2740 |
return {
|
| 2741 |
"gauge": round(gauge, 1),
|
| 2742 |
"normalized_score": round(normalized, 4),
|
| 2743 |
-
"signal": _gauge_to_signal(gauge),
|
| 2744 |
"buy": buy, "sell": sell, "neutral": neutral,
|
|
|
|
|
|
|
|
|
|
| 2745 |
"golden_cross": cross_bonus > 0,
|
| 2746 |
"death_cross": cross_bonus < 0
|
| 2747 |
}
|
|
@@ -2844,7 +2966,7 @@ def _calc_ai_forecast_score(
|
|
| 2844 |
"monotonicity": round(monotonicity * 100.0, 1),
|
| 2845 |
"max_adverse_excursion_pct": round(adverse_excursion_pct, 2),
|
| 2846 |
"path_metrics": path_metrics,
|
| 2847 |
-
"signal": _gauge_to_signal(gauge)
|
| 2848 |
}
|
| 2849 |
|
| 2850 |
def _calc_summary_score(osc_score: dict, ma_score: dict, ai_score: dict) -> dict:
|
|
@@ -2885,9 +3007,18 @@ def _calc_summary_score(osc_score: dict, ma_score: dict, ai_score: dict) -> dict
|
|
| 2885 |
}
|
| 2886 |
}
|
| 2887 |
|
| 2888 |
-
def _calc_technical_score_v2(osc_score: dict, ma_score: dict) -> dict:
|
| 2889 |
-
"""Blend oscillators and moving averages
|
| 2890 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2891 |
osc_delta = osc_score["gauge"] - 50.0
|
| 2892 |
ma_delta = ma_score["gauge"] - 50.0
|
| 2893 |
osc_dir = math.copysign(1.0, osc_delta) if abs(osc_delta) >= 1.0 else 0.0
|
|
@@ -2906,10 +3037,13 @@ def _calc_technical_score_v2(osc_score: dict, ma_score: dict) -> dict:
|
|
| 2906 |
return {
|
| 2907 |
"gauge": round(final_gauge, 1),
|
| 2908 |
"normalized_score": round(_gauge_to_normalized_score(final_gauge), 4),
|
| 2909 |
-
"signal": _gauge_to_signal(final_gauge),
|
| 2910 |
"buy": osc_score["buy"] + ma_score["buy"],
|
| 2911 |
"sell": osc_score["sell"] + ma_score["sell"],
|
| 2912 |
"neutral": osc_score["neutral"] + ma_score["neutral"],
|
|
|
|
|
|
|
|
|
|
| 2913 |
"alignment": osc_dir == ma_dir and osc_dir != 0.0,
|
| 2914 |
"components": {
|
| 2915 |
"oscillators": round(osc_score["gauge"], 1),
|
|
@@ -2917,10 +3051,12 @@ def _calc_technical_score_v2(osc_score: dict, ma_score: dict) -> dict:
|
|
| 2917 |
}
|
| 2918 |
}
|
| 2919 |
|
| 2920 |
-
def _calc_summary_score_v2(tech_score: dict, ai_score: dict) -> dict:
|
| 2921 |
-
"""Final decision score
|
| 2922 |
-
|
| 2923 |
-
|
|
|
|
|
|
|
| 2924 |
dist = abs(final_gauge - 50.0)
|
| 2925 |
conviction = "Rất mạnh" if dist >= 25 else "Mạnh" if dist >= 15 else "Trung bình" if dist >= 8 else "Yếu"
|
| 2926 |
|
|
@@ -2933,17 +3069,22 @@ def _calc_summary_score_v2(tech_score: dict, ai_score: dict) -> dict:
|
|
| 2933 |
return {
|
| 2934 |
"gauge": round(final_gauge, 1),
|
| 2935 |
"normalized_score": round(_gauge_to_normalized_score(final_gauge), 4),
|
| 2936 |
-
"signal": _gauge_to_signal(final_gauge),
|
| 2937 |
"conviction": conviction,
|
| 2938 |
"bias": bias,
|
| 2939 |
"buy": tech_score["buy"],
|
| 2940 |
"sell": tech_score["sell"],
|
| 2941 |
"neutral": tech_score["neutral"],
|
|
|
|
|
|
|
|
|
|
| 2942 |
"components": {
|
| 2943 |
"technical": round(tech_score["gauge"], 1),
|
| 2944 |
"oscillators": round(tech_score["components"]["oscillators"], 1),
|
| 2945 |
"moving_averages": round(tech_score["components"]["moving_averages"], 1),
|
| 2946 |
-
"ai_forecast": round(ai_score["gauge"], 1)
|
|
|
|
|
|
|
| 2947 |
}
|
| 2948 |
}
|
| 2949 |
|
|
@@ -2968,6 +3109,9 @@ def _build_dashboard_payload(
|
|
| 2968 |
"buy": technical_score["buy"],
|
| 2969 |
"sell": technical_score["sell"],
|
| 2970 |
"neutral": technical_score["neutral"],
|
|
|
|
|
|
|
|
|
|
| 2971 |
},
|
| 2972 |
"ai": {
|
| 2973 |
"gauge": ai_score["gauge"],
|
|
@@ -2989,6 +3133,9 @@ def _build_dashboard_payload(
|
|
| 2989 |
"signal": summary["signal"],
|
| 2990 |
"conviction": summary["conviction"],
|
| 2991 |
"bias": summary["bias"],
|
|
|
|
|
|
|
|
|
|
| 2992 |
},
|
| 2993 |
}
|
| 2994 |
|
|
@@ -3032,58 +3179,89 @@ def _build_trade_analysis(
|
|
| 3032 |
last_close = closes[-1]
|
| 3033 |
|
| 3034 |
def _lv(arr):
|
|
|
|
|
|
|
| 3035 |
if isinstance(arr, pd.Series): arr = arr.values
|
| 3036 |
-
v = arr[-1] if len(arr) else float('nan')
|
| 3037 |
-
|
|
|
|
|
|
|
|
|
|
| 3038 |
|
| 3039 |
# ── 1. Oscillators ──
|
| 3040 |
osc_data = []
|
| 3041 |
atr_scale = max(float(indicators.get("atr", {}).get("value") or last_close * 0.01), max(last_close * 0.0015, 1e-6))
|
| 3042 |
-
def _add_osc(label, val, action_name, **kw):
|
| 3043 |
if isinstance(val, (np.ndarray, pd.Series, list)): v = _lv(val)
|
| 3044 |
else: v = round(float(val), 2) if val is not None else None
|
| 3045 |
-
|
| 3046 |
-
|
| 3047 |
-
|
| 3048 |
-
|
| 3049 |
-
|
| 3050 |
-
|
| 3051 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3052 |
adx_vals = _adx(highs, lows, closes, 14)
|
| 3053 |
-
_add_osc("Chỉ số Định hướng Trung bình (14)", adx_vals[0], "adx", plus_di=_lv(adx_vals[1]) or 0, minus_di=_lv(adx_vals[2]) or 0)
|
| 3054 |
-
|
| 3055 |
-
|
|
|
|
|
|
|
| 3056 |
macd_vals = _macd(closes, 12, 26, 9)
|
| 3057 |
-
_add_osc("Cấp độ MACD (12, 26)", macd_vals[0], "macd", signal=_lv(macd_vals[1]) or 0, scale=atr_scale * 0.18)
|
| 3058 |
-
_add_osc("Đường RSI Nhanh (3, 3, 14, 14)",
|
| 3059 |
-
_add_osc("Vùng Phần trăm Williams (14)", _williams_r(highs, lows, closes, 14), "williams")
|
| 3060 |
-
|
| 3061 |
-
_add_osc("
|
| 3062 |
-
_add_osc("
|
| 3063 |
-
_add_osc("
|
| 3064 |
-
_add_osc("
|
| 3065 |
-
_add_osc("
|
| 3066 |
-
_add_osc("
|
| 3067 |
-
_add_osc("
|
| 3068 |
-
_add_osc("
|
| 3069 |
-
|
| 3070 |
-
|
|
|
|
| 3071 |
|
| 3072 |
# ── 2. Moving Averages ──
|
| 3073 |
ma_data = []
|
| 3074 |
-
def _add_ma(label, val_arr):
|
| 3075 |
v = _lv(val_arr)
|
| 3076 |
-
|
| 3077 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3078 |
|
| 3079 |
for p in [10, 20, 30, 50, 100, 200]:
|
| 3080 |
-
_add_ma(f"Trung bình Trượt Hàm mũ ({p})", _ema(closes, p))
|
| 3081 |
-
_add_ma(f"Đường Trung bình trượt Đơn giản ({p})", _sma(closes, p))
|
| 3082 |
-
|
| 3083 |
-
|
| 3084 |
-
|
| 3085 |
-
|
| 3086 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3087 |
|
| 3088 |
# ── 3. AI Forecast Gauge ──
|
| 3089 |
if not blended:
|
|
@@ -3104,8 +3282,8 @@ def _build_trade_analysis(
|
|
| 3104 |
)
|
| 3105 |
|
| 3106 |
# ── 4. Summary ──
|
| 3107 |
-
technical_score = _calc_technical_score_v2(osc_score, ma_score)
|
| 3108 |
-
summary = _calc_summary_score_v2(technical_score, ai_score)
|
| 3109 |
|
| 3110 |
# Pivot Points
|
| 3111 |
last_h = float(highs[-2]) if len(highs) > 1 else float(highs[-1])
|
|
@@ -3116,6 +3294,7 @@ def _build_trade_analysis(
|
|
| 3116 |
|
| 3117 |
return {
|
| 3118 |
"style": "tradingview",
|
|
|
|
| 3119 |
"dashboard": dashboard,
|
| 3120 |
"summary": summary,
|
| 3121 |
"technicals": technical_score,
|
|
@@ -3125,6 +3304,9 @@ def _build_trade_analysis(
|
|
| 3125 |
"buy": osc_score["buy"],
|
| 3126 |
"sell": osc_score["sell"],
|
| 3127 |
"neutral": osc_score["neutral"],
|
|
|
|
|
|
|
|
|
|
| 3128 |
"data": osc_data
|
| 3129 |
},
|
| 3130 |
"moving_averages": {
|
|
@@ -3133,6 +3315,9 @@ def _build_trade_analysis(
|
|
| 3133 |
"buy": ma_score["buy"],
|
| 3134 |
"sell": ma_score["sell"],
|
| 3135 |
"neutral": ma_score["neutral"],
|
|
|
|
|
|
|
|
|
|
| 3136 |
"golden_cross": ma_score["golden_cross"],
|
| 3137 |
"death_cross": ma_score["death_cross"],
|
| 3138 |
"data": ma_data
|
|
@@ -3581,7 +3766,7 @@ async def _source_selftest():
|
|
| 3581 |
("bybit", "https://api.bybit.com/v5/market/time"),
|
| 3582 |
("coingecko", "https://api.coingecko.com/api/v3/ping"),
|
| 3583 |
("twelvedata", f"https://api.twelvedata.com/api_usage?apikey={TWELVEDATA_API_KEY}"),
|
| 3584 |
-
("finnhub", f"https://finnhub.io/api/v1/
|
| 3585 |
]
|
| 3586 |
async with httpx.AsyncClient(timeout=8) as c:
|
| 3587 |
for name, url in tests:
|
|
|
|
| 64 |
|
| 65 |
# D-1: Load environment variables from .env file (v6.0)
|
| 66 |
load_dotenv()
|
| 67 |
+
os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"
|
| 68 |
+
os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1"
|
| 69 |
import re
|
| 70 |
import sys
|
| 71 |
import time
|
|
|
|
| 203 |
PROJECT_ROOT = os.path.dirname(CURRENT_DIR)
|
| 204 |
logger.info("Running in DEV mode. PROJECT_ROOT: %s", PROJECT_ROOT)
|
| 205 |
|
| 206 |
+
KRONOS_PATH = os.path.join(CURRENT_DIR, "kronos_core")
|
| 207 |
|
| 208 |
if KRONOS_PATH not in sys.path:
|
| 209 |
sys.path.append(KRONOS_PATH)
|
|
|
|
| 316 |
except Exception as ex:
|
| 317 |
logger.error("[Persistence] Eviction error: %s", ex)
|
| 318 |
|
| 319 |
+
persistent_cache = PersistentCache(os.path.join(PROJECT_ROOT, "data", "kronos_v5.db"))
|
| 320 |
|
| 321 |
|
| 322 |
# B-12: Global Configuration Instances
|
|
|
|
| 1411 |
return k_smooth.values, d_smooth.values
|
| 1412 |
|
| 1413 |
|
| 1414 |
+
def _stoch_kd(
|
| 1415 |
+
high: np.ndarray,
|
| 1416 |
+
low: np.ndarray,
|
| 1417 |
+
close: np.ndarray,
|
| 1418 |
+
k_period: int = 14,
|
| 1419 |
+
smooth_k: int = 3,
|
| 1420 |
+
d_period: int = 3,
|
| 1421 |
+
) -> Tuple[np.ndarray, np.ndarray]:
|
| 1422 |
+
"""Standard stochastic oscillator computed from price high/low/close."""
|
| 1423 |
+
high_s = pd.Series(high)
|
| 1424 |
+
low_s = pd.Series(low)
|
| 1425 |
+
close_s = pd.Series(close)
|
| 1426 |
+
hh = high_s.rolling(window=k_period).max()
|
| 1427 |
+
ll = low_s.rolling(window=k_period).min()
|
| 1428 |
+
raw_k = 100.0 * (close_s - ll) / (hh - ll).replace(0, np.nan)
|
| 1429 |
+
k = raw_k.rolling(window=smooth_k).mean()
|
| 1430 |
+
d = k.rolling(window=d_period).mean()
|
| 1431 |
+
return k.values, d.values
|
| 1432 |
+
|
| 1433 |
+
|
| 1434 |
def _sma(arr: np.ndarray, period: int) -> np.ndarray:
|
| 1435 |
"""Vectorized Simple Moving Average."""
|
| 1436 |
if len(arr) == 0: return np.array([], dtype=float)
|
|
|
|
| 1584 |
return ((hh + ll) / 2.0).values
|
| 1585 |
|
| 1586 |
|
| 1587 |
+
def _ichimoku_cloud(high: np.ndarray, low: np.ndarray) -> Dict[str, np.ndarray]:
|
| 1588 |
+
"""Basic Ichimoku lines for cloud-position scoring."""
|
| 1589 |
+
hs = pd.Series(high)
|
| 1590 |
+
ls = pd.Series(low)
|
| 1591 |
+
tenkan = ((hs.rolling(9).max() + ls.rolling(9).min()) / 2.0).values
|
| 1592 |
+
kijun = ((hs.rolling(26).max() + ls.rolling(26).min()) / 2.0).values
|
| 1593 |
+
span_a = ((pd.Series(tenkan) + pd.Series(kijun)) / 2.0).values
|
| 1594 |
+
span_b = ((hs.rolling(52).max() + ls.rolling(52).min()) / 2.0).values
|
| 1595 |
+
return {
|
| 1596 |
+
"tenkan": tenkan,
|
| 1597 |
+
"kijun": kijun,
|
| 1598 |
+
"span_a": span_a,
|
| 1599 |
+
"span_b": span_b,
|
| 1600 |
+
}
|
| 1601 |
+
|
| 1602 |
+
|
| 1603 |
def _vwma(close: np.ndarray, volume: np.ndarray, period: int = 20) -> np.ndarray:
|
| 1604 |
"""Vectorized Volume Weighted Moving Average."""
|
| 1605 |
cv = pd.Series(close * volume)
|
|
|
|
| 1718 |
"""Classify oscillator value as 'Mua' / 'Bán' / 'Trung lập'."""
|
| 1719 |
if value is None or math.isnan(value):
|
| 1720 |
return "Trung lập"
|
| 1721 |
+
last_close = float(kw.get("last_close", 0.0) or 0.0)
|
| 1722 |
+
ema50 = float(kw.get("ema50", last_close) or last_close)
|
| 1723 |
+
ema200 = float(kw.get("ema200", last_close) or last_close)
|
| 1724 |
+
ema50_prev = float(kw.get("ema50_prev", ema50) or ema50)
|
| 1725 |
+
atr = max(float(kw.get("atr", 0.0) or 0.0), 1e-8)
|
| 1726 |
if name == "roc":
|
| 1727 |
return "Mua" if value > 1.0 else "Bán" if value < -1.0 else "Trung lập"
|
| 1728 |
if name == "trix":
|
|
|
|
| 1732 |
if name == "cmo":
|
| 1733 |
return "Mua" if value > 20 else "Bán" if value < -20 else "Trung lập"
|
| 1734 |
if name == "dpo":
|
| 1735 |
+
return "Trung lập"
|
| 1736 |
if name == "aroon":
|
| 1737 |
return "Mua" if value > 25 else "Bán" if value < -25 else "Trung lập"
|
| 1738 |
if name == "tsi":
|
| 1739 |
return "Mua" if value > 5 else "Bán" if value < -5 else "Trung lập"
|
| 1740 |
if name == "rsi":
|
| 1741 |
+
ema50_rising = ema50 >= ema50_prev
|
| 1742 |
+
above_ema200 = last_close >= ema200
|
| 1743 |
+
if value > 70:
|
| 1744 |
+
return "Trung lập" if above_ema200 and ema50_rising else "Bán"
|
| 1745 |
+
if value < 30:
|
| 1746 |
+
return "Mua" if above_ema200 else "Trung lập"
|
| 1747 |
+
return "Mua" if value >= 50 else "Bán"
|
| 1748 |
if name == "stoch":
|
| 1749 |
return "Bán" if value > 80 else "Mua" if value < 20 else "Trung lập"
|
| 1750 |
if name == "cci":
|
|
|
|
| 1756 |
return "Trung lập"
|
| 1757 |
return "Mua" if plus_di > minus_di else "Bán"
|
| 1758 |
if name == "ao":
|
| 1759 |
+
prev = float(kw.get("prev", value) or value)
|
| 1760 |
+
if value > 0 and value > prev:
|
| 1761 |
+
return "Mua"
|
| 1762 |
+
if value < 0 and value < prev:
|
| 1763 |
+
return "Bán"
|
| 1764 |
+
return "Trung lập"
|
| 1765 |
if name == "momentum":
|
| 1766 |
return "Mua" if value > 0 else "Bán" if value < 0 else "Trung lập"
|
| 1767 |
if name == "macd":
|
|
|
|
| 1772 |
if name == "williams":
|
| 1773 |
return "Mua" if value < -80 else "Bán" if value > -20 else "Trung lập"
|
| 1774 |
if name == "bbp":
|
| 1775 |
+
bbp_norm = float(kw.get("bbp_norm", value / atr))
|
| 1776 |
+
return "Mua" if bbp_norm > 0.5 else "Bán" if bbp_norm < -0.5 else "Trung lập"
|
| 1777 |
if name == "ultimate":
|
| 1778 |
return "Bán" if value > 70 else "Mua" if value < 30 else "Trung lập"
|
| 1779 |
return "Trung lập"
|
| 1780 |
|
| 1781 |
|
| 1782 |
+
def _ma_action(price: float, ma_val: float, threshold_pct: float = 0.3) -> str:
|
| 1783 |
+
"""Classify MA with a neutral zone when price is too close to the average."""
|
| 1784 |
if ma_val is None or math.isnan(ma_val):
|
| 1785 |
return "Trung lập"
|
| 1786 |
+
dist_pct = _pct(price, ma_val)
|
| 1787 |
+
if abs(dist_pct) < threshold_pct:
|
| 1788 |
+
return "Trung lập"
|
| 1789 |
+
return "Mua" if dist_pct > 0 else "Bán"
|
| 1790 |
+
|
| 1791 |
+
|
| 1792 |
+
def _ma_threshold_pct(ma_key: str) -> float:
|
| 1793 |
+
thresholds = {
|
| 1794 |
+
"ema_200": 3.0, "sma_200": 3.0,
|
| 1795 |
+
"ema_100": 2.2, "sma_100": 2.2,
|
| 1796 |
+
"ema_50": 1.5, "sma_50": 1.5,
|
| 1797 |
+
"ema_30": 1.2, "sma_30": 1.2,
|
| 1798 |
+
"ema_20": 1.0, "sma_20": 1.0,
|
| 1799 |
+
"ema_10": 0.8, "sma_10": 0.8,
|
| 1800 |
+
"ichimoku": 1.5, "hull_9": 1.0,
|
| 1801 |
+
}
|
| 1802 |
+
return thresholds.get(ma_key, 1.2)
|
| 1803 |
+
|
| 1804 |
+
|
| 1805 |
+
def _ma_signal_score(price: float, ma_val: float, ma_prev: Optional[float], threshold_pct: float) -> float:
|
| 1806 |
+
if ma_val is None or math.isnan(ma_val):
|
| 1807 |
+
return 0.0
|
| 1808 |
+
dist_pct = _pct(price, ma_val)
|
| 1809 |
+
score = math.tanh(dist_pct / max(threshold_pct, 0.1))
|
| 1810 |
+
if ma_prev is not None and not math.isnan(ma_prev):
|
| 1811 |
+
slope_pct = _pct(ma_val, ma_prev)
|
| 1812 |
+
if score > 0 and slope_pct > 0:
|
| 1813 |
+
score *= 1.2
|
| 1814 |
+
elif score > 0 and slope_pct < 0:
|
| 1815 |
+
score *= 0.8
|
| 1816 |
+
elif score < 0 and slope_pct < 0:
|
| 1817 |
+
score *= 1.2
|
| 1818 |
+
elif score < 0 and slope_pct > 0:
|
| 1819 |
+
score *= 0.8
|
| 1820 |
+
return _clamp(score, -1.0, 1.0)
|
| 1821 |
|
| 1822 |
|
| 1823 |
def _osc_signal_score(name: str, value: float, **kw) -> float:
|
|
|
|
| 1828 |
scale = max(float(kw.get("scale", 1.0) or 1.0), 1e-8)
|
| 1829 |
|
| 1830 |
if name == "rsi":
|
| 1831 |
+
trend_bias = 0.0
|
| 1832 |
+
if kw.get("last_close", 0.0) >= kw.get("ema200", kw.get("last_close", 0.0)):
|
| 1833 |
+
trend_bias = 0.15 if value >= 50 else -0.05
|
| 1834 |
+
else:
|
| 1835 |
+
trend_bias = -0.15 if value <= 50 else 0.05
|
| 1836 |
+
return _clamp((value - 50.0) / 25.0 + trend_bias, -1.0, 1.0)
|
| 1837 |
if name in {"stoch", "stoch_rsi"}:
|
| 1838 |
return _clamp((50.0 - value) / 30.0, -1.0, 1.0)
|
| 1839 |
if name == "cci":
|
|
|
|
| 1844 |
strength = _clamp((value - 18.0) / 22.0, 0.0, 1.0)
|
| 1845 |
direction = math.tanh((plus_di - minus_di) / max(plus_di + minus_di, 10.0) * 3.0)
|
| 1846 |
return _clamp(direction * strength, -1.0, 1.0)
|
| 1847 |
+
if name in {"ao", "momentum"}:
|
| 1848 |
return _clamp(math.tanh(value / scale), -1.0, 1.0)
|
| 1849 |
+
if name == "bbp":
|
| 1850 |
+
bbp_norm = float(kw.get("bbp_norm", value / max(float(kw.get("atr", scale) or scale), 1e-8)))
|
| 1851 |
+
return _clamp(math.tanh(bbp_norm / 1.0), -1.0, 1.0)
|
| 1852 |
+
if name == "dpo":
|
| 1853 |
+
return 0.0
|
| 1854 |
if name == "macd":
|
| 1855 |
signal = float(kw.get("signal", 0.0) or 0.0)
|
| 1856 |
return _clamp(math.tanh((value - signal) / scale), -1.0, 1.0)
|
| 1857 |
if name == "williams":
|
| 1858 |
+
return _clamp(((-value) - 50.0) / 30.0, -1.0, 1.0)
|
| 1859 |
if name == "ultimate":
|
| 1860 |
return _clamp((50.0 - value) / 25.0, -1.0, 1.0)
|
| 1861 |
if name == "roc":
|
|
|
|
| 2641 |
|
| 2642 |
# ── Technical Analysis Weights & Constants (Dashboard Rework v6.1) ────────────
|
| 2643 |
OSC_WEIGHTS = {
|
| 2644 |
+
"rsi": 2.0,
|
| 2645 |
+
"macd": 2.5,
|
| 2646 |
+
"stoch_rsi": 1.2,
|
| 2647 |
"stoch": 1.3, # Classic momentum
|
| 2648 |
+
"cci": 1.6,
|
| 2649 |
+
"adx": 1.8,
|
| 2650 |
+
"williams": 1.4,
|
| 2651 |
"ultimate": 1.2, # Multi-period, less noise
|
| 2652 |
"bbp": 1.0, # Trend-following
|
| 2653 |
"ao": 0.9, # Noisy, short-term only
|
| 2654 |
"momentum": 0.8, # Lagging, lowest weight
|
| 2655 |
+
"roc": 1.3,
|
| 2656 |
+
"trix": 1.6,
|
| 2657 |
+
"ppo": 1.5,
|
| 2658 |
"cmo": 1.3, # Momentum regime strength
|
| 2659 |
+
"dpo": 0.3,
|
| 2660 |
+
"aroon": 1.6,
|
| 2661 |
+
"tsi": 1.8,
|
| 2662 |
}
|
| 2663 |
|
| 2664 |
MA_WEIGHT_MAP = {
|
| 2665 |
+
"ema_200": 2.5, "sma_200": 2.3,
|
| 2666 |
"ema_100": 2.3, "sma_100": 2.1,
|
| 2667 |
+
"ema_50": 2.0, "sma_50": 1.8,
|
| 2668 |
"ema_30": 1.3, "sma_30": 1.2,
|
| 2669 |
+
"ema_20": 1.4, "sma_20": 1.2,
|
| 2670 |
"ema_10": 0.8, "sma_10": 0.7,
|
| 2671 |
+
"ichimoku": 1.6, "hull_9": 1.3
|
| 2672 |
}
|
| 2673 |
|
| 2674 |
def _extract_osc_key(label: str) -> str:
|
| 2675 |
l = label.lower()
|
| 2676 |
+
if "sức mạnh tương đối" in l or ("rsi" in l and "nhanh" not in l): return "rsi"
|
| 2677 |
if "macd" in l: return "macd"
|
| 2678 |
if "stochastic %k" in l: return "stoch"
|
| 2679 |
if "nhanh" in l or "stoch_rsi" in l: return "stoch_rsi"
|
| 2680 |
+
if "kênh hàng hóa" in l or "cci" in l: return "cci"
|
| 2681 |
if "định hướng" in l or "adx" in l: return "adx"
|
| 2682 |
if "williams" in l: return "williams"
|
| 2683 |
if "ultimate" in l: return "ultimate"
|
|
|
|
| 2695 |
|
| 2696 |
def _get_ma_weight(label: str) -> float:
|
| 2697 |
l = label.lower()
|
| 2698 |
+
if l in MA_WEIGHT_MAP:
|
| 2699 |
+
return MA_WEIGHT_MAP[l]
|
| 2700 |
if "hàm mũ" in l:
|
| 2701 |
p = re.findall(r"\d+", l)
|
| 2702 |
if p: return MA_WEIGHT_MAP.get(f"ema_{p[0]}", 1.0)
|
|
|
|
| 2704 |
p = re.findall(r"\d+", l)
|
| 2705 |
if p: return MA_WEIGHT_MAP.get(f"sma_{p[0]}", 1.0)
|
| 2706 |
if "ichimoku" in l: return MA_WEIGHT_MAP["ichimoku"]
|
|
|
|
| 2707 |
if "hull" in l: return MA_WEIGHT_MAP["hull_9"]
|
| 2708 |
return 1.0
|
| 2709 |
|
| 2710 |
+
def _gauge_to_signal(gauge: float, interval: str = "1h") -> str:
|
| 2711 |
+
"""Interval-aware 5-level signal converter."""
|
| 2712 |
+
thresholds = {
|
| 2713 |
+
"1m": (80, 62, 38, 20),
|
| 2714 |
+
"5m": (80, 62, 38, 20),
|
| 2715 |
+
"15m": (77, 60, 40, 23),
|
| 2716 |
+
"1h": (77, 60, 40, 23),
|
| 2717 |
+
"4h": (75, 58, 42, 25),
|
| 2718 |
+
"1d": (75, 58, 42, 25),
|
| 2719 |
+
"1w": (72, 56, 44, 28),
|
| 2720 |
+
}
|
| 2721 |
+
strong_buy, buy, sell, strong_sell = thresholds.get(interval, thresholds["1h"])
|
| 2722 |
+
if gauge >= strong_buy: return "Mua mạnh"
|
| 2723 |
+
if gauge >= buy: return "Mua"
|
| 2724 |
+
if gauge > sell: return "Trung lập"
|
| 2725 |
+
if gauge > strong_sell: return "Bán"
|
| 2726 |
+
return "Bán mạnh"
|
| 2727 |
|
| 2728 |
|
| 2729 |
def _gauge_to_normalized_score(gauge: float) -> float:
|
|
|
|
| 2781 |
"mean_step_return_pct": round(mean_step_return_pct, 3),
|
| 2782 |
}
|
| 2783 |
|
| 2784 |
+
def _calc_osc_score(osc_data: list, interval: str = "1h") -> dict:
|
| 2785 |
"""MODULE 1: Weighted scoring for oscillators."""
|
| 2786 |
total_weight = 0.0
|
| 2787 |
weighted_score = 0.0
|
| 2788 |
buy = sell = neutral = 0
|
| 2789 |
+
buy_weight = sell_weight = neutral_weight = 0.0
|
| 2790 |
|
| 2791 |
for item in osc_data:
|
| 2792 |
+
key = item.get("key") or _extract_osc_key(item["name"])
|
| 2793 |
w = OSC_WEIGHTS.get(key, 1.0)
|
| 2794 |
v = float(item.get("score", 0.0) or 0.0)
|
| 2795 |
# Robust case-insensitive check
|
| 2796 |
act = str(item.get("action", "")).strip().lower()
|
| 2797 |
if act == "mua":
|
| 2798 |
buy += 1
|
| 2799 |
+
buy_weight += w
|
| 2800 |
elif act == "bán":
|
| 2801 |
sell += 1
|
| 2802 |
+
sell_weight += w
|
| 2803 |
else:
|
| 2804 |
neutral += 1
|
| 2805 |
+
neutral_weight += w
|
| 2806 |
|
| 2807 |
weighted_score += w * v
|
| 2808 |
total_weight += w
|
|
|
|
| 2812 |
return {
|
| 2813 |
"gauge": round(gauge, 1),
|
| 2814 |
"normalized_score": round(normalized, 4),
|
| 2815 |
+
"signal": _gauge_to_signal(gauge, interval),
|
| 2816 |
+
"buy": buy, "sell": sell, "neutral": neutral,
|
| 2817 |
+
"buy_weight": round(buy_weight, 2),
|
| 2818 |
+
"sell_weight": round(sell_weight, 2),
|
| 2819 |
+
"neutral_weight": round(neutral_weight, 2),
|
| 2820 |
}
|
| 2821 |
|
| 2822 |
+
def _calc_ma_score(ma_data: list, closes: np.ndarray, interval: str = "1h") -> dict:
|
| 2823 |
"""MODULE 2: Period-weighted scoring for MAs + Cross Bonus."""
|
| 2824 |
total_weight = 0.0
|
| 2825 |
weighted_score = 0.0
|
| 2826 |
buy = sell = neutral = 0
|
| 2827 |
+
buy_weight = sell_weight = neutral_weight = 0.0
|
| 2828 |
|
| 2829 |
for item in ma_data:
|
| 2830 |
+
w = _get_ma_weight(item.get("key") or item["name"])
|
| 2831 |
+
v = float(item.get("score", 0.0) or 0.0)
|
| 2832 |
act = str(item.get("action", "")).strip().lower()
|
| 2833 |
if act == "mua":
|
|
|
|
| 2834 |
buy += 1
|
| 2835 |
+
buy_weight += w
|
| 2836 |
elif act == "bán":
|
|
|
|
| 2837 |
sell += 1
|
| 2838 |
+
sell_weight += w
|
| 2839 |
else:
|
| 2840 |
neutral += 1
|
| 2841 |
+
neutral_weight += w
|
| 2842 |
|
| 2843 |
weighted_score += w * v
|
| 2844 |
total_weight += w
|
|
|
|
| 2859 |
return {
|
| 2860 |
"gauge": round(gauge, 1),
|
| 2861 |
"normalized_score": round(normalized, 4),
|
| 2862 |
+
"signal": _gauge_to_signal(gauge, interval),
|
| 2863 |
"buy": buy, "sell": sell, "neutral": neutral,
|
| 2864 |
+
"buy_weight": round(buy_weight, 2),
|
| 2865 |
+
"sell_weight": round(sell_weight, 2),
|
| 2866 |
+
"neutral_weight": round(neutral_weight, 2),
|
| 2867 |
"golden_cross": cross_bonus > 0,
|
| 2868 |
"death_cross": cross_bonus < 0
|
| 2869 |
}
|
|
|
|
| 2966 |
"monotonicity": round(monotonicity * 100.0, 1),
|
| 2967 |
"max_adverse_excursion_pct": round(adverse_excursion_pct, 2),
|
| 2968 |
"path_metrics": path_metrics,
|
| 2969 |
+
"signal": _gauge_to_signal(gauge, interval)
|
| 2970 |
}
|
| 2971 |
|
| 2972 |
def _calc_summary_score(osc_score: dict, ma_score: dict, ai_score: dict) -> dict:
|
|
|
|
| 3007 |
}
|
| 3008 |
}
|
| 3009 |
|
| 3010 |
+
def _calc_technical_score_v2(osc_score: dict, ma_score: dict, regime: str = "sideways", interval: str = "1h") -> dict:
|
| 3011 |
+
"""Blend oscillators and moving averages using regime-aware weighting."""
|
| 3012 |
+
if regime in {"stable_bull", "stable_bear"}:
|
| 3013 |
+
osc_weight, ma_weight = 0.45, 0.55
|
| 3014 |
+
elif regime in {"tight_range", "sideways"}:
|
| 3015 |
+
osc_weight, ma_weight = 0.60, 0.40
|
| 3016 |
+
elif regime in {"overextended_bull", "capitulation"}:
|
| 3017 |
+
osc_weight, ma_weight = 0.55, 0.45
|
| 3018 |
+
else:
|
| 3019 |
+
osc_weight, ma_weight = 0.50, 0.50
|
| 3020 |
+
|
| 3021 |
+
base = (osc_score["gauge"] * osc_weight) + (ma_score["gauge"] * ma_weight)
|
| 3022 |
osc_delta = osc_score["gauge"] - 50.0
|
| 3023 |
ma_delta = ma_score["gauge"] - 50.0
|
| 3024 |
osc_dir = math.copysign(1.0, osc_delta) if abs(osc_delta) >= 1.0 else 0.0
|
|
|
|
| 3037 |
return {
|
| 3038 |
"gauge": round(final_gauge, 1),
|
| 3039 |
"normalized_score": round(_gauge_to_normalized_score(final_gauge), 4),
|
| 3040 |
+
"signal": _gauge_to_signal(final_gauge, interval),
|
| 3041 |
"buy": osc_score["buy"] + ma_score["buy"],
|
| 3042 |
"sell": osc_score["sell"] + ma_score["sell"],
|
| 3043 |
"neutral": osc_score["neutral"] + ma_score["neutral"],
|
| 3044 |
+
"buy_weight": round(osc_score.get("buy_weight", 0.0) + ma_score.get("buy_weight", 0.0), 2),
|
| 3045 |
+
"sell_weight": round(osc_score.get("sell_weight", 0.0) + ma_score.get("sell_weight", 0.0), 2),
|
| 3046 |
+
"neutral_weight": round(osc_score.get("neutral_weight", 0.0) + ma_score.get("neutral_weight", 0.0), 2),
|
| 3047 |
"alignment": osc_dir == ma_dir and osc_dir != 0.0,
|
| 3048 |
"components": {
|
| 3049 |
"oscillators": round(osc_score["gauge"], 1),
|
|
|
|
| 3051 |
}
|
| 3052 |
}
|
| 3053 |
|
| 3054 |
+
def _calc_summary_score_v2(tech_score: dict, ai_score: dict, interval: str = "1h") -> dict:
|
| 3055 |
+
"""Final decision score: simple average of Technical and AI gauges."""
|
| 3056 |
+
# TỔNG KẾT = (PHÂN TÍCH KỸ THUẬT + DỰ BÁO AI)/2
|
| 3057 |
+
ai_weight, tech_weight = 0.5, 0.5
|
| 3058 |
+
final_gauge = (float(tech_score["gauge"]) + float(ai_score["gauge"])) / 2.0
|
| 3059 |
+
final_gauge = max(5.0, min(95.0, final_gauge))
|
| 3060 |
dist = abs(final_gauge - 50.0)
|
| 3061 |
conviction = "Rất mạnh" if dist >= 25 else "Mạnh" if dist >= 15 else "Trung bình" if dist >= 8 else "Yếu"
|
| 3062 |
|
|
|
|
| 3069 |
return {
|
| 3070 |
"gauge": round(final_gauge, 1),
|
| 3071 |
"normalized_score": round(_gauge_to_normalized_score(final_gauge), 4),
|
| 3072 |
+
"signal": _gauge_to_signal(final_gauge, interval),
|
| 3073 |
"conviction": conviction,
|
| 3074 |
"bias": bias,
|
| 3075 |
"buy": tech_score["buy"],
|
| 3076 |
"sell": tech_score["sell"],
|
| 3077 |
"neutral": tech_score["neutral"],
|
| 3078 |
+
"buy_weight": tech_score.get("buy_weight", 0.0),
|
| 3079 |
+
"sell_weight": tech_score.get("sell_weight", 0.0),
|
| 3080 |
+
"neutral_weight": tech_score.get("neutral_weight", 0.0),
|
| 3081 |
"components": {
|
| 3082 |
"technical": round(tech_score["gauge"], 1),
|
| 3083 |
"oscillators": round(tech_score["components"]["oscillators"], 1),
|
| 3084 |
"moving_averages": round(tech_score["components"]["moving_averages"], 1),
|
| 3085 |
+
"ai_forecast": round(ai_score["gauge"], 1),
|
| 3086 |
+
"ai_weight": round(ai_weight, 2),
|
| 3087 |
+
"technical_weight": round(tech_weight, 2),
|
| 3088 |
}
|
| 3089 |
}
|
| 3090 |
|
|
|
|
| 3109 |
"buy": technical_score["buy"],
|
| 3110 |
"sell": technical_score["sell"],
|
| 3111 |
"neutral": technical_score["neutral"],
|
| 3112 |
+
"buy_weight": technical_score.get("buy_weight", 0.0),
|
| 3113 |
+
"sell_weight": technical_score.get("sell_weight", 0.0),
|
| 3114 |
+
"neutral_weight": technical_score.get("neutral_weight", 0.0),
|
| 3115 |
},
|
| 3116 |
"ai": {
|
| 3117 |
"gauge": ai_score["gauge"],
|
|
|
|
| 3133 |
"signal": summary["signal"],
|
| 3134 |
"conviction": summary["conviction"],
|
| 3135 |
"bias": summary["bias"],
|
| 3136 |
+
"buy_weight": summary.get("buy_weight", 0.0),
|
| 3137 |
+
"sell_weight": summary.get("sell_weight", 0.0),
|
| 3138 |
+
"neutral_weight": summary.get("neutral_weight", 0.0),
|
| 3139 |
},
|
| 3140 |
}
|
| 3141 |
|
|
|
|
| 3179 |
last_close = closes[-1]
|
| 3180 |
|
| 3181 |
def _lv(arr):
|
| 3182 |
+
if arr is None: return None
|
| 3183 |
+
if isinstance(arr, (float, int)): return round(float(arr), 2)
|
| 3184 |
if isinstance(arr, pd.Series): arr = arr.values
|
| 3185 |
+
v = arr[-1] if (hasattr(arr, '__len__') and len(arr) > 0) else float('nan')
|
| 3186 |
+
if v is None or (isinstance(v, float) and math.isnan(v)): return None
|
| 3187 |
+
return round(float(v), 2)
|
| 3188 |
+
|
| 3189 |
+
regime, regime_label = _classify_regime(indicators, last_close, _safe(indicators.get("atr", {}).get("pct"), 1.0))
|
| 3190 |
|
| 3191 |
# ── 1. Oscillators ──
|
| 3192 |
osc_data = []
|
| 3193 |
atr_scale = max(float(indicators.get("atr", {}).get("value") or last_close * 0.01), max(last_close * 0.0015, 1e-6))
|
| 3194 |
+
def _add_osc(label, val, action_name, key=None, **kw):
|
| 3195 |
if isinstance(val, (np.ndarray, pd.Series, list)): v = _lv(val)
|
| 3196 |
else: v = round(float(val), 2) if val is not None else None
|
| 3197 |
+
osc_key = key or action_name
|
| 3198 |
+
osc_kwargs = {
|
| 3199 |
+
"last_close": last_close,
|
| 3200 |
+
"ema50": _lv(indicators["ema"].get("ema50")) or last_close,
|
| 3201 |
+
"ema200": _lv(indicators["ema"].get("ema200")) or last_close,
|
| 3202 |
+
"ema50_prev": float(_ema(closes, 50)[-6]) if len(closes) >= 6 and not math.isnan(_ema(closes, 50)[-6]) else (_lv(indicators["ema"].get("ema50")) or last_close),
|
| 3203 |
+
"atr": atr_scale,
|
| 3204 |
+
**kw,
|
| 3205 |
+
}
|
| 3206 |
+
act = _osc_action(action_name, v if v is not None else 0, **osc_kwargs)
|
| 3207 |
+
score = _osc_signal_score(action_name, v if v is not None else 0.0, **osc_kwargs)
|
| 3208 |
+
osc_data.append({"key": osc_key, "name": label, "value": v, "action": act, "score": round(score, 4)})
|
| 3209 |
+
|
| 3210 |
+
stoch_price_k, stoch_price_d = _stoch_kd(highs, lows, closes, 14, 3, 3)
|
| 3211 |
+
stoch_rsi_k, stoch_rsi_d = _stoch_rsi(closes, 14, 14, 3, 3)
|
| 3212 |
+
_add_osc("Chỉ số Sức mạnh tương đối (14)", _rsi(closes, 14), "rsi", key="rsi")
|
| 3213 |
+
_add_osc("Stochastic %K (14, 3, 3)", stoch_price_k, "stoch", key="stoch")
|
| 3214 |
+
_add_osc("Chỉ số Kênh hàng hóa (20)", _cci(highs, lows, closes, 20), "cci", key="cci")
|
| 3215 |
adx_vals = _adx(highs, lows, closes, 14)
|
| 3216 |
+
_add_osc("Chỉ số Định hướng Trung bình (14)", adx_vals[0], "adx", key="adx", plus_di=_lv(adx_vals[1]) or 0, minus_di=_lv(adx_vals[2]) or 0)
|
| 3217 |
+
ao_vals = _awesome_oscillator(highs, lows)
|
| 3218 |
+
ao_prev = _lv(ao_vals[:-1]) if len(ao_vals) > 1 else _lv(ao_vals)
|
| 3219 |
+
_add_osc("Chỉ số Dao động AO", ao_vals, "ao", key="ao", scale=atr_scale * 0.8, prev=ao_prev or 0.0)
|
| 3220 |
+
_add_osc("Xung lượng (10)", _momentum(closes, 10), "momentum", key="momentum", scale=atr_scale * 1.15)
|
| 3221 |
macd_vals = _macd(closes, 12, 26, 9)
|
| 3222 |
+
_add_osc("Cấp độ MACD (12, 26)", macd_vals[0], "macd", key="macd", signal=_lv(macd_vals[1]) or 0, scale=atr_scale * 0.18)
|
| 3223 |
+
_add_osc("Đường RSI Nhanh (3, 3, 14, 14)", stoch_rsi_k, "stoch_rsi", key="stoch_rsi")
|
| 3224 |
+
_add_osc("Vùng Phần trăm Williams (14)", _williams_r(highs, lows, closes, 14), "williams", key="williams")
|
| 3225 |
+
bbp_vals = _bull_bear_power(highs, lows, closes, 13)
|
| 3226 |
+
_add_osc("Sức Mạnh Giá Lên và Giá Xuống", bbp_vals, "bbp", key="bbp", scale=atr_scale * 0.9, bbp_norm=(_lv(bbp_vals) or 0.0) / atr_scale)
|
| 3227 |
+
_add_osc("Dao động Ultimate (7, 14, 28)", _ultimate_oscillator(highs, lows, closes, 7, 14, 28), "ultimate", key="ultimate")
|
| 3228 |
+
_add_osc("Tốc độ biến động ROC (12)", _roc(closes, 12), "roc", key="roc")
|
| 3229 |
+
_add_osc("TRIX (18)", _trix(closes, 18), "trix", key="trix")
|
| 3230 |
+
_add_osc("PPO (12, 26)", _ppo(closes, 12, 26), "ppo", key="ppo")
|
| 3231 |
+
_add_osc("CMO (14)", _cmo(closes, 14), "cmo", key="cmo")
|
| 3232 |
+
_add_osc("DPO (20)", _dpo(closes, 20), "dpo", key="dpo", scale=atr_scale * 0.75)
|
| 3233 |
+
_add_osc("Aroon Oscillator (25)", _aroon_oscillator(highs, lows, 25), "aroon", key="aroon")
|
| 3234 |
+
_add_osc("TSI (25, 13)", _tsi(closes, 25, 13), "tsi", key="tsi")
|
| 3235 |
+
|
| 3236 |
+
osc_score = _calc_osc_score(osc_data, interval)
|
| 3237 |
|
| 3238 |
# ── 2. Moving Averages ──
|
| 3239 |
ma_data = []
|
| 3240 |
+
def _add_ma(label, val_arr, key=None, threshold_pct=None):
|
| 3241 |
v = _lv(val_arr)
|
| 3242 |
+
ma_key = key or label.lower()
|
| 3243 |
+
threshold = threshold_pct if threshold_pct is not None else _ma_threshold_pct(ma_key)
|
| 3244 |
+
prev_val = None
|
| 3245 |
+
if isinstance(val_arr, (np.ndarray, list, pd.Series)) and len(val_arr) >= 6:
|
| 3246 |
+
prev_raw = val_arr[-6]
|
| 3247 |
+
prev_val = None if prev_raw is None or (isinstance(prev_raw, float) and math.isnan(prev_raw)) else float(prev_raw)
|
| 3248 |
+
act = _ma_action(last_close, v if v is not None else last_close, min(0.5, threshold * 0.25))
|
| 3249 |
+
score = _ma_signal_score(last_close, v if v is not None else last_close, prev_val, threshold)
|
| 3250 |
+
ma_data.append({"key": ma_key, "name": label, "value": v, "action": act, "score": round(score, 4)})
|
| 3251 |
|
| 3252 |
for p in [10, 20, 30, 50, 100, 200]:
|
| 3253 |
+
_add_ma(f"Trung bình Trượt Hàm mũ ({p})", _ema(closes, p), key=f"ema_{p}")
|
| 3254 |
+
_add_ma(f"Đường Trung bình trượt Đơn giản ({p})", _sma(closes, p), key=f"sma_{p}")
|
| 3255 |
+
cloud = _ichimoku_cloud(highs, lows)
|
| 3256 |
+
span_a = _lv(cloud["span_a"])
|
| 3257 |
+
span_b = _lv(cloud["span_b"])
|
| 3258 |
+
cloud_top = max(span_a or last_close, span_b or last_close)
|
| 3259 |
+
cloud_bot = min(span_a or last_close, span_b or last_close)
|
| 3260 |
+
cloud_proxy = cloud_top if last_close >= cloud_top else cloud_bot if last_close <= cloud_bot else last_close
|
| 3261 |
+
_add_ma("Đám mây Ichimoku (9, 26, 52, 26)", np.array([cloud_proxy]), key="ichimoku", threshold_pct=1.5)
|
| 3262 |
+
_add_ma("Đường trung bình trượt Hull (9)", _hull_ma(closes, 9), key="hull_9")
|
| 3263 |
+
|
| 3264 |
+
ma_score = _calc_ma_score(ma_data, closes, interval)
|
| 3265 |
|
| 3266 |
# ── 3. AI Forecast Gauge ──
|
| 3267 |
if not blended:
|
|
|
|
| 3282 |
)
|
| 3283 |
|
| 3284 |
# ── 4. Summary ──
|
| 3285 |
+
technical_score = _calc_technical_score_v2(osc_score, ma_score, regime, interval)
|
| 3286 |
+
summary = _calc_summary_score_v2(technical_score, ai_score, interval)
|
| 3287 |
|
| 3288 |
# Pivot Points
|
| 3289 |
last_h = float(highs[-2]) if len(highs) > 1 else float(highs[-1])
|
|
|
|
| 3294 |
|
| 3295 |
return {
|
| 3296 |
"style": "tradingview",
|
| 3297 |
+
"regime": {"key": regime, "label": regime_label},
|
| 3298 |
"dashboard": dashboard,
|
| 3299 |
"summary": summary,
|
| 3300 |
"technicals": technical_score,
|
|
|
|
| 3304 |
"buy": osc_score["buy"],
|
| 3305 |
"sell": osc_score["sell"],
|
| 3306 |
"neutral": osc_score["neutral"],
|
| 3307 |
+
"buy_weight": osc_score.get("buy_weight", 0.0),
|
| 3308 |
+
"sell_weight": osc_score.get("sell_weight", 0.0),
|
| 3309 |
+
"neutral_weight": osc_score.get("neutral_weight", 0.0),
|
| 3310 |
"data": osc_data
|
| 3311 |
},
|
| 3312 |
"moving_averages": {
|
|
|
|
| 3315 |
"buy": ma_score["buy"],
|
| 3316 |
"sell": ma_score["sell"],
|
| 3317 |
"neutral": ma_score["neutral"],
|
| 3318 |
+
"buy_weight": ma_score.get("buy_weight", 0.0),
|
| 3319 |
+
"sell_weight": ma_score.get("sell_weight", 0.0),
|
| 3320 |
+
"neutral_weight": ma_score.get("neutral_weight", 0.0),
|
| 3321 |
"golden_cross": ma_score["golden_cross"],
|
| 3322 |
"death_cross": ma_score["death_cross"],
|
| 3323 |
"data": ma_data
|
|
|
|
| 3766 |
("bybit", "https://api.bybit.com/v5/market/time"),
|
| 3767 |
("coingecko", "https://api.coingecko.com/api/v3/ping"),
|
| 3768 |
("twelvedata", f"https://api.twelvedata.com/api_usage?apikey={TWELVEDATA_API_KEY}"),
|
| 3769 |
+
("finnhub", f"https://finnhub.io/api/v1/quote?symbol=AAPL&token={FINNHUB_API_KEY}"),
|
| 3770 |
]
|
| 3771 |
async with httpx.AsyncClient(timeout=8) as c:
|
| 3772 |
for name, url in tests:
|
backend/recovery.py
DELETED
|
@@ -1,129 +0,0 @@
|
|
| 1 |
-
import sys
|
| 2 |
-
import os
|
| 3 |
-
|
| 4 |
-
# RECOVERY SCRIPT FOR KRONOS BACKEND v6.0
|
| 5 |
-
# Restores the missing functions and fixes the structure
|
| 6 |
-
|
| 7 |
-
def get_part1(): # Imports to SYMBOLS
|
| 8 |
-
# We can read this from current file as it's likely safe (lines 1-1220)
|
| 9 |
-
with open('backend/main.py', 'r', encoding='utf-8', errors='replace') as f:
|
| 10 |
-
lines = f.readlines()
|
| 11 |
-
return lines[:1220]
|
| 12 |
-
|
| 13 |
-
def get_correct_fetchers():
|
| 14 |
-
return [
|
| 15 |
-
"def _get_source_priority(symbol: str) -> List[str]:\n",
|
| 16 |
-
" cfg = SYMBOLS[symbol]\n",
|
| 17 |
-
" priority = CATEGORY_SOURCE_PRIORITY.get(cfg.category, DEFAULT_SOURCE_PRIORITY)\n",
|
| 18 |
-
" return [s for s in priority if s in cfg.mappings]\n",
|
| 19 |
-
"\n",
|
| 20 |
-
"\n",
|
| 21 |
-
"async def fetch_historical(\n",
|
| 22 |
-
" symbol: str, interval: str, limit: int\n",
|
| 23 |
-
") -> Tuple[List[Dict[str, Any]], str]:\n",
|
| 24 |
-
" prefix = _cache_prefix(symbol, interval)\n",
|
| 25 |
-
" key = f'hist_{prefix}'\n",
|
| 26 |
-
" cached = historical_cache.get(key)\n",
|
| 27 |
-
" if cached is not None:\n",
|
| 28 |
-
" return cached[-limit:], 'cache'\n",
|
| 29 |
-
" priority = _get_source_priority(symbol)\n",
|
| 30 |
-
" errors: List[str] = []\n",
|
| 31 |
-
" fetch_limit = max(limit, 1000)\n",
|
| 32 |
-
" for source in priority:\n",
|
| 33 |
-
" try:\n",
|
| 34 |
-
" if source == 'binance': data = await fetch_binance(symbol, interval, fetch_limit)\n",
|
| 35 |
-
" elif source == 'bybit': data = await fetch_bybit(symbol, interval, fetch_limit)\n",
|
| 36 |
-
" elif source == 'coingecko': data = await fetch_coingecko(symbol, interval, fetch_limit)\n",
|
| 37 |
-
" elif source == 'twelvedata': data = await fetch_twelvedata(symbol, interval, fetch_limit)\n",
|
| 38 |
-
" elif source == 'finnhub': data = await fetch_finnhub(symbol, interval, fetch_limit)\n",
|
| 39 |
-
" elif source == 'yfinance': data = await fetch_yfinance(symbol, interval, fetch_limit)\n",
|
| 40 |
-
" else: continue\n",
|
| 41 |
-
" if len(data) >= 20:\n",
|
| 42 |
-
" historical_cache.set(key, data, ttl_seconds=interval_ttl(interval))\n",
|
| 43 |
-
" return data[-limit:], source\n",
|
| 44 |
-
" except Exception as ex: errors.append(f'{source}: {ex}')\n",
|
| 45 |
-
" raise HTTPException(status_code=502, detail={'message': 'All sources failed', 'errors': errors})\n"
|
| 46 |
-
]
|
| 47 |
-
|
| 48 |
-
# Indicators part (Vectorized)
|
| 49 |
-
def get_vectorized_indicators():
|
| 50 |
-
return [
|
| 51 |
-
"def _ema(arr: np.ndarray, period: int) -> np.ndarray:\n",
|
| 52 |
-
" if len(arr) == 0: return np.array([], dtype=float)\n",
|
| 53 |
-
" return pd.Series(arr).ewm(alpha=2.0/(period+1), adjust=False).mean().values\n",
|
| 54 |
-
"\n",
|
| 55 |
-
"def _rsi(close: np.ndarray, period: int = 14) -> np.ndarray:\n",
|
| 56 |
-
" delta = np.diff(close)\n",
|
| 57 |
-
" gain = np.where(delta > 0, delta, 0.0)\n",
|
| 58 |
-
" loss = np.where(delta < 0, -delta, 0.0)\n",
|
| 59 |
-
" avg_gain = pd.Series(gain).ewm(alpha=1.0/period, adjust=False).mean()\n",
|
| 60 |
-
" avg_loss = pd.Series(loss).ewm(alpha=1.0/period, adjust=False).mean()\n",
|
| 61 |
-
" rs = avg_gain / avg_loss.replace(0, np.inf)\n",
|
| 62 |
-
" rsi = 100 - (100 / (1 + rs))\n",
|
| 63 |
-
" return np.concatenate([[np.nan], rsi.values])\n",
|
| 64 |
-
"\n",
|
| 65 |
-
"def _bollinger(close: np.ndarray, period=20, k=2.0):\n",
|
| 66 |
-
" s = pd.Series(close)\n",
|
| 67 |
-
" mid = s.rolling(window=period).mean()\n",
|
| 68 |
-
" std = s.rolling(window=period).std()\n",
|
| 69 |
-
" return (mid + k*std).values, mid.values, (mid - k*std).values\n",
|
| 70 |
-
"\n",
|
| 71 |
-
"def _macd(close, fast=12, slow=26, signal=9):\n",
|
| 72 |
-
" f, s = _ema(close, fast), _ema(close, slow)\n",
|
| 73 |
-
" line = f - s\n",
|
| 74 |
-
" sig = _ema(np.where(np.isnan(line), 0, line), signal)\n",
|
| 75 |
-
" return line, sig, line - sig\n",
|
| 76 |
-
"\n",
|
| 77 |
-
"def _atr(high, low, close, period=14):\n",
|
| 78 |
-
" tr = np.maximum(high[1:]-low[1:], np.maximum(np.abs(high[1:]-close[:-1]), np.abs(low[1:]-close[:-1])))\n",
|
| 79 |
-
" tr = np.concatenate([[np.nan], tr])\n",
|
| 80 |
-
" return pd.Series(tr).ewm(alpha=1.0/period, adjust=False).mean().values\n",
|
| 81 |
-
"\n",
|
| 82 |
-
"def _stoch_rsi(close, rsi_p=14, stoch_p=14, k_p=3, d_p=3):\n",
|
| 83 |
-
" rsi = pd.Series(_rsi(close, rsi_p))\n",
|
| 84 |
-
" mn, mx = rsi.rolling(stoch_p).min(), rsi.rolling(stoch_p).max()\n",
|
| 85 |
-
" k = 100 * (rsi - mn) / (mx - mn).replace(0, np.inf)\n",
|
| 86 |
-
" ks = k.rolling(k_p).mean()\n",
|
| 87 |
-
" return ks.values, ks.rolling(d_p).mean().values\n",
|
| 88 |
-
"\n",
|
| 89 |
-
"def _sma(arr, p): return pd.Series(arr).rolling(p).mean().values if len(arr) else arr\n",
|
| 90 |
-
"\n",
|
| 91 |
-
"def _cci(h, l, c, p=20):\n",
|
| 92 |
-
" tp = (h+l+c)/3.0; s = pd.Series(tp)\n",
|
| 93 |
-
" sma = s.rolling(p).mean()\n",
|
| 94 |
-
" mad = s.rolling(p).apply(lambda x: np.abs(x-x.mean()).mean(), raw=False)\n",
|
| 95 |
-
" return (s - sma) / (0.015 * mad.replace(0, np.inf))\n",
|
| 96 |
-
"\n",
|
| 97 |
-
"def _adx(h, l, c, p=14):\n",
|
| 98 |
-
" up = h[1:]-h[:-1]; dn = l[:-1]-l[1:]\n",
|
| 99 |
-
" p_dm = np.concatenate([[0], np.where((up>dn)&(up>0), up, 0)])\n",
|
| 100 |
-
" m_dm = np.concatenate([[0], np.where((dn>up)&(dn>0), dn, 0)])\n",
|
| 101 |
-
" tr = _atr(h, l, c, p) # simplified TR for vectorization\n",
|
| 102 |
-
" tr_s = pd.Series(tr).rolling(p).sum().replace(0, np.inf)\n",
|
| 103 |
-
" p_di = 100 * pd.Series(p_dm).rolling(p).sum() / tr_s\n",
|
| 104 |
-
" m_di = 100 * pd.Series(m_dm).rolling(p).sum() / tr_s\n",
|
| 105 |
-
" dx = 100 * np.abs(p_di - m_di) / (p_di + m_di).replace(0, np.inf)\n",
|
| 106 |
-
" return dx.rolling(p).mean().values, p_di.values, m_di.values\n",
|
| 107 |
-
"\n",
|
| 108 |
-
"def _awesome_oscillator(h, l): return _sma((h+l)/2, 5) - _sma((h+l)/2, 34)\n",
|
| 109 |
-
"def _momentum(c, p): return np.concatenate([np.full(p, np.nan), c[p:] - c[:-p]])\n",
|
| 110 |
-
"def _williams_r(h, l, c, p=14):\n",
|
| 111 |
-
" hh, ll = pd.Series(h).rolling(p).max(), pd.Series(l).rolling(p).min()\n",
|
| 112 |
-
" return -100 * (hh - c) / (hh - ll).replace(0, np.inf)\n",
|
| 113 |
-
"def _bull_bear_power(h, l, c, p=13): ema = _ema(c, p); return (h - ema) + (l - ema)\n",
|
| 114 |
-
"def _ultimate_oscillator(h, l, c, p1=7, p2=14, p3=28):\n",
|
| 115 |
-
" cp = pd.Series(c).shift(1); tr = np.maximum(h, cp) - np.minimum(l, cp); bp = pd.Series(c) - np.minimum(l, cp)\n",
|
| 116 |
-
" a1, a2, a3 = bp.rolling(p1).sum()/tr.rolling(p1).sum().replace(0,np.inf), bp.rolling(p2).sum()/tr.rolling(p2).sum().replace(0,np.inf), bp.rolling(p3).sum()/tr.rolling(p3).sum().replace(0,np.inf)\n",
|
| 117 |
-
" return 100 * (4*a1 + 2*a2 + a3) / 7.0\n",
|
| 118 |
-
"def _vwma(c, v, p=20): return (pd.Series(c*v).rolling(p).sum() / pd.Series(v).rolling(p).sum().replace(0, np.inf)).values\n",
|
| 119 |
-
"def _hull_ma(c, p=9):\n",
|
| 120 |
-
" h, s = max(p//2, 1), int(p**0.5)\n",
|
| 121 |
-
" d = 2*_sma(c, h) - _sma(c, p)\n",
|
| 122 |
-
" return _sma(np.where(np.isnan(d), c, d), s)\n"
|
| 123 |
-
]
|
| 124 |
-
|
| 125 |
-
# Add analytical engine back
|
| 126 |
-
# (Omitted here for brevity in script creation, will insert in actual write)
|
| 127 |
-
|
| 128 |
-
# ... Reconstruct and write ...
|
| 129 |
-
print("Recovery logic ready (truncated here for brevity)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/sign_agreement.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
import os
|
| 4 |
-
import time
|
| 5 |
-
import hmac
|
| 6 |
-
import hashlib
|
| 7 |
-
import json
|
| 8 |
-
from dotenv import load_dotenv
|
| 9 |
-
|
| 10 |
-
load_dotenv()
|
| 11 |
-
|
| 12 |
-
async def sign_tradfi_agreement():
|
| 13 |
-
api_key = os.getenv("BYBIT_API_KEY")
|
| 14 |
-
api_secret = os.getenv("BYBIT_API_SECRET")
|
| 15 |
-
|
| 16 |
-
url = "https://api.bybit.com/v5/user/sign-agreement"
|
| 17 |
-
|
| 18 |
-
timestamp = str(int(time.time() * 1000))
|
| 19 |
-
recv_window = "5000"
|
| 20 |
-
payload = json.dumps({"agreementId": "1"})
|
| 21 |
-
|
| 22 |
-
raw_str = timestamp + api_key + recv_window + payload
|
| 23 |
-
signature = hmac.new(api_secret.encode('utf-8'),
|
| 24 |
-
raw_str.encode('utf-8'), hashlib.sha256).hexdigest()
|
| 25 |
-
|
| 26 |
-
headers = {
|
| 27 |
-
'X-BAPI-API-KEY': api_key,
|
| 28 |
-
'X-BAPI-TIMESTAMP': timestamp,
|
| 29 |
-
'X-BAPI-SIGN-TYPE': '2',
|
| 30 |
-
'X-BAPI-RECV-WINDOW': recv_window,
|
| 31 |
-
'X-BAPI-SIGN': signature,
|
| 32 |
-
'Content-Type': 'application/json'
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
async with httpx.AsyncClient() as client:
|
| 36 |
-
resp = await client.post(url, data=payload, headers=headers)
|
| 37 |
-
print(f"Status: {resp.status_code}")
|
| 38 |
-
print(f"Content: {resp.text}")
|
| 39 |
-
if resp.text:
|
| 40 |
-
print(f"Sign Agreement Result: {resp.json()}")
|
| 41 |
-
|
| 42 |
-
if __name__ == "__main__":
|
| 43 |
-
asyncio.run(sign_tradfi_agreement())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/test_basecoin.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
import os
|
| 4 |
-
import time
|
| 5 |
-
import hmac
|
| 6 |
-
import hashlib
|
| 7 |
-
from dotenv import load_dotenv
|
| 8 |
-
|
| 9 |
-
load_dotenv()
|
| 10 |
-
|
| 11 |
-
async def test_basecoin_usd():
|
| 12 |
-
api_key = os.getenv("BYBIT_API_KEY")
|
| 13 |
-
api_secret = os.getenv("BYBIT_API_SECRET")
|
| 14 |
-
|
| 15 |
-
url = "https://api.bybit.com/v5/market/instruments-info"
|
| 16 |
-
# Try filtering by baseCoin=USD for TradFi
|
| 17 |
-
params = {"category": "linear", "baseCoin": "USD"}
|
| 18 |
-
|
| 19 |
-
async with httpx.AsyncClient() as client:
|
| 20 |
-
resp = await client.get(url, params=params)
|
| 21 |
-
data = resp.json()
|
| 22 |
-
if data.get("retCode") == 0:
|
| 23 |
-
symbols = [item["symbol"] for item in data["result"]["list"]]
|
| 24 |
-
print(f"Found {len(symbols)} symbols with baseCoin=USD")
|
| 25 |
-
print(f"Sample: {symbols[:10]}")
|
| 26 |
-
else:
|
| 27 |
-
print(f"Error: {data}")
|
| 28 |
-
|
| 29 |
-
if __name__ == "__main__":
|
| 30 |
-
asyncio.run(test_basecoin_usd())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/test_bytick.py
DELETED
|
@@ -1,45 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
import os
|
| 4 |
-
import time
|
| 5 |
-
import hmac
|
| 6 |
-
import hashlib
|
| 7 |
-
from dotenv import load_dotenv
|
| 8 |
-
|
| 9 |
-
load_dotenv()
|
| 10 |
-
|
| 11 |
-
async def test_bytick():
|
| 12 |
-
api_key = os.getenv("BYBIT_API_KEY")
|
| 13 |
-
api_secret = os.getenv("BYBIT_API_SECRET")
|
| 14 |
-
|
| 15 |
-
symbol = "XAUUSD+"
|
| 16 |
-
url = "https://api.bytick.com/v5/market/tickers"
|
| 17 |
-
|
| 18 |
-
async with httpx.AsyncClient() as client:
|
| 19 |
-
params = {"category": "linear", "symbol": symbol}
|
| 20 |
-
|
| 21 |
-
headers = {}
|
| 22 |
-
if api_key and api_secret:
|
| 23 |
-
timestamp = str(int(time.time() * 1000))
|
| 24 |
-
recv_window = "5000"
|
| 25 |
-
sorted_params = "&".join([f"{k}={v}" for k, v in sorted(params.items())])
|
| 26 |
-
raw_str = timestamp + api_key + recv_window + sorted_params
|
| 27 |
-
signature = hmac.new(api_secret.encode('utf-8'),
|
| 28 |
-
raw_str.encode('utf-8'), hashlib.sha256).hexdigest()
|
| 29 |
-
|
| 30 |
-
headers = {
|
| 31 |
-
'X-BAPI-API-KEY': api_key,
|
| 32 |
-
'X-BAPI-TIMESTAMP': timestamp,
|
| 33 |
-
'X-BAPI-SIGN-TYPE': '2',
|
| 34 |
-
'X-BAPI-RECV-WINDOW': recv_window,
|
| 35 |
-
'X-BAPI-SIGN': signature
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
try:
|
| 39 |
-
resp = await client.get(url, params=params, headers=headers)
|
| 40 |
-
print(f"Bytick Result: {resp.json()}")
|
| 41 |
-
except Exception as e:
|
| 42 |
-
print(f"Error: {e}")
|
| 43 |
-
|
| 44 |
-
if __name__ == "__main__":
|
| 45 |
-
asyncio.run(test_bytick())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/test_cat.py
DELETED
|
@@ -1,49 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
import os
|
| 4 |
-
import time
|
| 5 |
-
import hmac
|
| 6 |
-
import hashlib
|
| 7 |
-
from dotenv import load_dotenv
|
| 8 |
-
|
| 9 |
-
load_dotenv()
|
| 10 |
-
|
| 11 |
-
async def test_tradfi_category():
|
| 12 |
-
api_key = os.getenv("BYBIT_API_KEY")
|
| 13 |
-
api_secret = os.getenv("BYBIT_API_SECRET")
|
| 14 |
-
|
| 15 |
-
# Try different categories
|
| 16 |
-
test_categories = ["tradfi", "mt5", "cfd", "linear"]
|
| 17 |
-
symbol = "XAUUSD+"
|
| 18 |
-
url = "https://api.bybit.com/v5/market/tickers"
|
| 19 |
-
|
| 20 |
-
async with httpx.AsyncClient() as client:
|
| 21 |
-
for cat in test_categories:
|
| 22 |
-
params = {"category": cat, "symbol": symbol}
|
| 23 |
-
|
| 24 |
-
headers = {}
|
| 25 |
-
if api_key and api_secret:
|
| 26 |
-
timestamp = str(int(time.time() * 1000))
|
| 27 |
-
recv_window = "5000"
|
| 28 |
-
sorted_params = "&".join([f"{k}={v}" for k, v in sorted(params.items())])
|
| 29 |
-
raw_str = timestamp + api_key + recv_window + sorted_params
|
| 30 |
-
signature = hmac.new(api_secret.encode('utf-8'),
|
| 31 |
-
raw_str.encode('utf-8'), hashlib.sha256).hexdigest()
|
| 32 |
-
|
| 33 |
-
headers = {
|
| 34 |
-
'X-BAPI-API-KEY': api_key,
|
| 35 |
-
'X-BAPI-TIMESTAMP': timestamp,
|
| 36 |
-
'X-BAPI-SIGN-TYPE': '2',
|
| 37 |
-
'X-BAPI-RECV-WINDOW': recv_window,
|
| 38 |
-
'X-BAPI-SIGN': signature
|
| 39 |
-
}
|
| 40 |
-
|
| 41 |
-
try:
|
| 42 |
-
resp = await client.get(url, params=params, headers=headers)
|
| 43 |
-
data = resp.json()
|
| 44 |
-
print(f"Category: {cat} -> {data.get('retMsg')} ({data.get('retCode')})")
|
| 45 |
-
except Exception as e:
|
| 46 |
-
print(f"Error for {cat}: {e}")
|
| 47 |
-
|
| 48 |
-
if __name__ == "__main__":
|
| 49 |
-
asyncio.run(test_tradfi_category())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/test_indices.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
import os
|
| 4 |
-
import time
|
| 5 |
-
import hmac
|
| 6 |
-
import hashlib
|
| 7 |
-
from dotenv import load_dotenv
|
| 8 |
-
|
| 9 |
-
load_dotenv()
|
| 10 |
-
|
| 11 |
-
async def test_indices():
|
| 12 |
-
api_key = os.getenv("BYBIT_API_KEY")
|
| 13 |
-
api_secret = os.getenv("BYBIT_API_SECRET")
|
| 14 |
-
|
| 15 |
-
symbols = ["US30+", "SPX500+", "NAS100+", "DXY+", "AAPL+", "TSLA+"]
|
| 16 |
-
url = "https://api.bybit.com/v5/market/tickers"
|
| 17 |
-
|
| 18 |
-
async with httpx.AsyncClient() as client:
|
| 19 |
-
for symbol in symbols:
|
| 20 |
-
params = {"category": "linear", "symbol": symbol}
|
| 21 |
-
|
| 22 |
-
headers = {}
|
| 23 |
-
if api_key and api_secret:
|
| 24 |
-
timestamp = str(int(time.time() * 1000))
|
| 25 |
-
recv_window = "5000"
|
| 26 |
-
sorted_params = "&".join([f"{k}={v}" for k, v in sorted(params.items())])
|
| 27 |
-
raw_str = timestamp + api_key + recv_window + sorted_params
|
| 28 |
-
signature = hmac.new(api_secret.encode('utf-8'),
|
| 29 |
-
raw_str.encode('utf-8'), hashlib.sha256).hexdigest()
|
| 30 |
-
|
| 31 |
-
headers = {
|
| 32 |
-
'X-BAPI-API-KEY': api_key,
|
| 33 |
-
'X-BAPI-TIMESTAMP': timestamp,
|
| 34 |
-
'X-BAPI-SIGN-TYPE': '2',
|
| 35 |
-
'X-BAPI-RECV-WINDOW': recv_window,
|
| 36 |
-
'X-BAPI-SIGN': signature
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
resp = await client.get(url, params=params, headers=headers)
|
| 40 |
-
print(f"Symbol: {symbol} -> {resp.json().get('retMsg')} ({resp.json().get('retCode')})")
|
| 41 |
-
|
| 42 |
-
if __name__ == "__main__":
|
| 43 |
-
asyncio.run(test_indices())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/test_no_auth.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
|
| 4 |
-
async def test_no_auth():
|
| 5 |
-
url = "https://api.bybit.com/v5/market/kline"
|
| 6 |
-
symbols = ["XAUUSD+", "XAUUSD.s", "XAUUSD", "EURUSD+", "EURUSD.s", "EURUSD"]
|
| 7 |
-
|
| 8 |
-
async with httpx.AsyncClient() as client:
|
| 9 |
-
for symbol in symbols:
|
| 10 |
-
params = {"category": "linear", "symbol": symbol, "interval": "60", "limit": "1"}
|
| 11 |
-
resp = await client.get(url, params=params)
|
| 12 |
-
print(f"Symbol: {symbol} -> {resp.json().get('retMsg')} ({resp.json().get('retCode')})")
|
| 13 |
-
|
| 14 |
-
if __name__ == "__main__":
|
| 15 |
-
asyncio.run(test_no_auth())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/test_s.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
import os
|
| 4 |
-
import time
|
| 5 |
-
import hmac
|
| 6 |
-
import hashlib
|
| 7 |
-
from dotenv import load_dotenv
|
| 8 |
-
|
| 9 |
-
load_dotenv()
|
| 10 |
-
|
| 11 |
-
async def test_s_suffix():
|
| 12 |
-
api_key = os.getenv("BYBIT_API_KEY")
|
| 13 |
-
api_secret = os.getenv("BYBIT_API_SECRET")
|
| 14 |
-
|
| 15 |
-
test_symbols = ["XAUUSD.s", "EURUSD.s", "SPX500.s"]
|
| 16 |
-
url = "https://api.bybit.com/v5/market/tickers"
|
| 17 |
-
|
| 18 |
-
async with httpx.AsyncClient() as client:
|
| 19 |
-
for symbol in test_symbols:
|
| 20 |
-
params = {"category": "linear", "symbol": symbol}
|
| 21 |
-
|
| 22 |
-
headers = {}
|
| 23 |
-
if api_key and api_secret:
|
| 24 |
-
timestamp = str(int(time.time() * 1000))
|
| 25 |
-
recv_window = "5000"
|
| 26 |
-
sorted_params = "&".join([f"{k}={v}" for k, v in sorted(params.items())])
|
| 27 |
-
raw_str = timestamp + api_key + recv_window + sorted_params
|
| 28 |
-
signature = hmac.new(api_secret.encode('utf-8'),
|
| 29 |
-
raw_str.encode('utf-8'), hashlib.sha256).hexdigest()
|
| 30 |
-
|
| 31 |
-
headers = {
|
| 32 |
-
'X-BAPI-API-KEY': api_key,
|
| 33 |
-
'X-BAPI-TIMESTAMP': timestamp,
|
| 34 |
-
'X-BAPI-SIGN-TYPE': '2',
|
| 35 |
-
'X-BAPI-RECV-WINDOW': recv_window,
|
| 36 |
-
'X-BAPI-SIGN': signature
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
resp = await client.get(url, params=params, headers=headers)
|
| 40 |
-
print(f"Symbol: {symbol} -> {resp.json()}")
|
| 41 |
-
|
| 42 |
-
if __name__ == "__main__":
|
| 43 |
-
asyncio.run(test_s_suffix())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/verify_bybit.py
DELETED
|
@@ -1,31 +0,0 @@
|
|
| 1 |
-
import sys
|
| 2 |
-
import os
|
| 3 |
-
|
| 4 |
-
# Mock the imports or setup path to import main
|
| 5 |
-
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 6 |
-
from main import SYMBOLS, CATEGORY_SOURCE_PRIORITY, _get_source_priority
|
| 7 |
-
|
| 8 |
-
def verify_bybit_priority():
|
| 9 |
-
test_symbols = ["EURUSD", "XAUUSD", "WTI", "CORN", "SP500", "BTCUSD"]
|
| 10 |
-
print("Verifying Bybit Priority for key symbols:")
|
| 11 |
-
print("-" * 50)
|
| 12 |
-
|
| 13 |
-
all_ok = True
|
| 14 |
-
for sym in test_symbols:
|
| 15 |
-
priority = _get_source_priority(sym)
|
| 16 |
-
first_source = priority[0] if priority else "NONE"
|
| 17 |
-
bybit_mapped = "bybit" in SYMBOLS[sym].mappings
|
| 18 |
-
|
| 19 |
-
status = "OK" if first_source == "bybit" and bybit_mapped else "FAIL"
|
| 20 |
-
if status == "FAIL": all_ok = False
|
| 21 |
-
|
| 22 |
-
print(f"Symbol: {sym:10} | Priority: {str(priority):30} | Bybit Mapped: {str(bybit_mapped):5} | Status: {status}")
|
| 23 |
-
|
| 24 |
-
print("-" * 50)
|
| 25 |
-
if all_ok:
|
| 26 |
-
print("Verification SUCCESS: Bybit is prioritized correctly.")
|
| 27 |
-
else:
|
| 28 |
-
print("Verification FAILED: Some symbols do not prioritize Bybit.")
|
| 29 |
-
|
| 30 |
-
if __name__ == "__main__":
|
| 31 |
-
verify_bybit_priority()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/verify_new_key.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import asyncio
|
| 3 |
-
import os
|
| 4 |
-
import time
|
| 5 |
-
import hmac
|
| 6 |
-
import hashlib
|
| 7 |
-
from dotenv import load_dotenv
|
| 8 |
-
|
| 9 |
-
load_dotenv()
|
| 10 |
-
|
| 11 |
-
async def verify_new_key():
|
| 12 |
-
api_key = os.getenv("BYBIT_API_KEY")
|
| 13 |
-
api_secret = os.getenv("BYBIT_API_SECRET")
|
| 14 |
-
|
| 15 |
-
test_symbols = ["XAUUSD+", "EURUSD+", "SPX500+", "BTCUSDT"]
|
| 16 |
-
url = "https://api.bybit.com/v5/market/tickers"
|
| 17 |
-
|
| 18 |
-
async with httpx.AsyncClient() as client:
|
| 19 |
-
for symbol in test_symbols:
|
| 20 |
-
params = {"category": "linear", "symbol": symbol}
|
| 21 |
-
|
| 22 |
-
headers = {}
|
| 23 |
-
if api_key and api_secret:
|
| 24 |
-
timestamp = str(int(time.time() * 1000))
|
| 25 |
-
recv_window = "5000"
|
| 26 |
-
sorted_params = "&".join([f"{k}={v}" for k, v in sorted(params.items())])
|
| 27 |
-
raw_str = timestamp + api_key + recv_window + sorted_params
|
| 28 |
-
signature = hmac.new(api_secret.encode('utf-8'),
|
| 29 |
-
raw_str.encode('utf-8'), hashlib.sha256).hexdigest()
|
| 30 |
-
|
| 31 |
-
headers = {
|
| 32 |
-
'X-BAPI-API-KEY': api_key,
|
| 33 |
-
'X-BAPI-TIMESTAMP': timestamp,
|
| 34 |
-
'X-BAPI-SIGN-TYPE': '2',
|
| 35 |
-
'X-BAPI-RECV-WINDOW': recv_window,
|
| 36 |
-
'X-BAPI-SIGN': signature
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
resp = await client.get(url, params=params, headers=headers)
|
| 40 |
-
print(f"Symbol: {symbol} -> {resp.json()}")
|
| 41 |
-
|
| 42 |
-
if __name__ == "__main__":
|
| 43 |
-
asyncio.run(verify_new_key())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/Dark_BG.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
frontend/Light_BG.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
frontend/index.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
|
| 2 |
<html lang="vi">
|
| 3 |
|
| 4 |
<head>
|
|
@@ -100,8 +100,8 @@
|
|
| 100 |
--logo-primary: #0ea5e9;
|
| 101 |
--logo-secondary: #8b5cf6;
|
| 102 |
--logo-glow: rgba(14, 165, 233, 0.3);
|
| 103 |
-
--light-bg-image: url("Light_BG.png?v=
|
| 104 |
-
--dark-bg-image: url("Dark_BG.png?v=
|
| 105 |
}
|
| 106 |
|
| 107 |
body.dark-theme {
|
|
@@ -184,8 +184,8 @@
|
|
| 184 |
background-size: cover;
|
| 185 |
background-position: center;
|
| 186 |
background-repeat: no-repeat;
|
| 187 |
-
opacity:
|
| 188 |
-
/*
|
| 189 |
transition: background 0.3s ease, opacity 0.3s ease;
|
| 190 |
}
|
| 191 |
|
|
@@ -194,7 +194,7 @@
|
|
| 194 |
background-size: cover;
|
| 195 |
background-position: center;
|
| 196 |
background-repeat: no-repeat;
|
| 197 |
-
opacity:
|
| 198 |
}
|
| 199 |
|
| 200 |
body {
|
|
@@ -350,7 +350,7 @@
|
|
| 350 |
/* Search Omnibox (Shrunk 50% horizontally from original 320px) */
|
| 351 |
.omnibox {
|
| 352 |
position: relative;
|
| 353 |
-
width:
|
| 354 |
}
|
| 355 |
|
| 356 |
.omnibox input {
|
|
@@ -816,8 +816,9 @@
|
|
| 816 |
|
| 817 |
.gauge-hero-svg-wrap {
|
| 818 |
position: relative;
|
| 819 |
-
width:
|
| 820 |
-
|
|
|
|
| 821 |
display: flex;
|
| 822 |
align-items: center;
|
| 823 |
justify-content: center;
|
|
@@ -886,6 +887,8 @@
|
|
| 886 |
letter-spacing: 0.15em;
|
| 887 |
text-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
| 888 |
margin-top: 5px;
|
|
|
|
|
|
|
| 889 |
}
|
| 890 |
|
| 891 |
.gauge-hero-signal.strong-buy {
|
|
@@ -1588,6 +1591,8 @@
|
|
| 1588 |
font-weight: 800;
|
| 1589 |
text-transform: uppercase;
|
| 1590 |
letter-spacing: 0.02em;
|
|
|
|
|
|
|
| 1591 |
}
|
| 1592 |
|
| 1593 |
.compact-gauge-card.hero {
|
|
@@ -2088,7 +2093,7 @@
|
|
| 2088 |
}
|
| 2089 |
|
| 2090 |
.omnibox {
|
| 2091 |
-
width:
|
| 2092 |
}
|
| 2093 |
|
| 2094 |
.omnibox input,
|
|
@@ -3196,7 +3201,8 @@
|
|
| 3196 |
const prev = points[i - 1];
|
| 3197 |
const curr = points[i];
|
| 3198 |
const diff = (curr?.value ?? 0) - (prev?.value ?? 0);
|
| 3199 |
-
|
|
|
|
| 3200 |
const segSeries = chart.addLineSeries({
|
| 3201 |
color,
|
| 3202 |
lineWidth: 2,
|
|
@@ -3458,8 +3464,9 @@
|
|
| 3458 |
</div>
|
| 3459 |
</div>
|
| 3460 |
|
| 3461 |
-
<!-- Dashboard Body -->
|
| 3462 |
-
<div class="dash-body">
|
|
|
|
| 3463 |
|
| 3464 |
<!-- TOP ROW: 3 Big Gauges -->
|
| 3465 |
<div class="dash-gauges-hero">
|
|
@@ -3554,9 +3561,10 @@
|
|
| 3554 |
|
| 3555 |
<div class="summary-disclaimer">
|
| 3556 |
<strong>⚠ Cảnh báo</strong> — Thông tin phân tích kỹ thuật này không phải lời khuyên đầu tư. Hãy luôn quản lý rủi ro.
|
| 3557 |
-
</div>
|
| 3558 |
|
| 3559 |
-
</div>
|
|
|
|
| 3560 |
`;
|
| 3561 |
|
| 3562 |
// Close logic
|
|
@@ -3742,6 +3750,7 @@
|
|
| 3742 |
if (currentSymbol !== symbol) return; // Discard if user switched
|
| 3743 |
|
| 3744 |
renderAnalysisPanel(symbol, interval, fData);
|
|
|
|
| 3745 |
renderCompactGauges(symbol, interval, fData);
|
| 3746 |
|
| 3747 |
let isBull = false;
|
|
@@ -3912,6 +3921,7 @@
|
|
| 3912 |
if (lastAnalysisPayload && lastAnalysisSymbol === currentSymbol) {
|
| 3913 |
renderAnalysisPanel(lastAnalysisSymbol, lastAnalysisInterval, lastAnalysisPayload);
|
| 3914 |
analysisPanel.classList.add('active');
|
|
|
|
| 3915 |
} else {
|
| 3916 |
// No cached data: fetch from API without reloading chart
|
| 3917 |
analysisPanel.classList.add('active');
|
|
@@ -3922,6 +3932,7 @@
|
|
| 3922 |
</div>
|
| 3923 |
`;
|
| 3924 |
fetchAIAnalysis(currentSymbol, timeframeSelect.value);
|
|
|
|
| 3925 |
}
|
| 3926 |
} else {
|
| 3927 |
// Hide dashboard
|
|
@@ -3949,14 +3960,44 @@
|
|
| 3949 |
}
|
| 3950 |
});
|
| 3951 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3952 |
/* ── Resize handler ────────────────────────── */
|
| 3953 |
const ro = new ResizeObserver(() => {
|
| 3954 |
chart.applyOptions({
|
| 3955 |
width: chartEl.clientWidth,
|
| 3956 |
height: chartEl.clientHeight,
|
| 3957 |
});
|
|
|
|
| 3958 |
});
|
| 3959 |
ro.observe(chartEl);
|
|
|
|
| 3960 |
|
| 3961 |
/* ── Theme Management ── */
|
| 3962 |
function applyTheme(theme) {
|
|
@@ -4023,4 +4064,4 @@
|
|
| 4023 |
</script>
|
| 4024 |
</body>
|
| 4025 |
|
| 4026 |
-
</html>
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
<html lang="vi">
|
| 3 |
|
| 4 |
<head>
|
|
|
|
| 100 |
--logo-primary: #0ea5e9;
|
| 101 |
--logo-secondary: #8b5cf6;
|
| 102 |
--logo-glow: rgba(14, 165, 233, 0.3);
|
| 103 |
+
--light-bg-image: url("Light_BG.png?v=1");
|
| 104 |
+
--dark-bg-image: url("Dark_BG.png?v=1");
|
| 105 |
}
|
| 106 |
|
| 107 |
body.dark-theme {
|
|
|
|
| 184 |
background-size: cover;
|
| 185 |
background-position: center;
|
| 186 |
background-repeat: no-repeat;
|
| 187 |
+
opacity: 1;
|
| 188 |
+
/* Main background set to full visibility as requested */
|
| 189 |
transition: background 0.3s ease, opacity 0.3s ease;
|
| 190 |
}
|
| 191 |
|
|
|
|
| 194 |
background-size: cover;
|
| 195 |
background-position: center;
|
| 196 |
background-repeat: no-repeat;
|
| 197 |
+
opacity: 1;
|
| 198 |
}
|
| 199 |
|
| 200 |
body {
|
|
|
|
| 350 |
/* Search Omnibox (Shrunk 50% horizontally from original 320px) */
|
| 351 |
.omnibox {
|
| 352 |
position: relative;
|
| 353 |
+
width: 80px;
|
| 354 |
}
|
| 355 |
|
| 356 |
.omnibox input {
|
|
|
|
| 816 |
|
| 817 |
.gauge-hero-svg-wrap {
|
| 818 |
position: relative;
|
| 819 |
+
width: 100%;
|
| 820 |
+
max-width: 320px;
|
| 821 |
+
aspect-ratio: 320 / 220;
|
| 822 |
display: flex;
|
| 823 |
align-items: center;
|
| 824 |
justify-content: center;
|
|
|
|
| 887 |
letter-spacing: 0.15em;
|
| 888 |
text-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
| 889 |
margin-top: 5px;
|
| 890 |
+
background: none !important;
|
| 891 |
+
padding: 0 !important;
|
| 892 |
}
|
| 893 |
|
| 894 |
.gauge-hero-signal.strong-buy {
|
|
|
|
| 1591 |
font-weight: 800;
|
| 1592 |
text-transform: uppercase;
|
| 1593 |
letter-spacing: 0.02em;
|
| 1594 |
+
background: none !important;
|
| 1595 |
+
padding: 0 !important;
|
| 1596 |
}
|
| 1597 |
|
| 1598 |
.compact-gauge-card.hero {
|
|
|
|
| 2093 |
}
|
| 2094 |
|
| 2095 |
.omnibox {
|
| 2096 |
+
width: 125px;
|
| 2097 |
}
|
| 2098 |
|
| 2099 |
.omnibox input,
|
|
|
|
| 3201 |
const prev = points[i - 1];
|
| 3202 |
const curr = points[i];
|
| 3203 |
const diff = (curr?.value ?? 0) - (prev?.value ?? 0);
|
| 3204 |
+
// Tăng: xanh lá, Giảm: đỏ, Đi ngang: vàng nhạt
|
| 3205 |
+
const color = diff > EPSILON ? '#10b981' : diff < -EPSILON ? '#f43f5e' : '#fef08a';
|
| 3206 |
const segSeries = chart.addLineSeries({
|
| 3207 |
color,
|
| 3208 |
lineWidth: 2,
|
|
|
|
| 3464 |
</div>
|
| 3465 |
</div>
|
| 3466 |
|
| 3467 |
+
<!-- Dashboard Body Scaler -->
|
| 3468 |
+
<div class="dash-body-scaler" style="flex:1; overflow-y:auto; overflow-x:hidden; display:flex; flex-direction:column;">
|
| 3469 |
+
<div class="dash-body" style="transform-origin: top center; transition: transform 0.2s ease;">
|
| 3470 |
|
| 3471 |
<!-- TOP ROW: 3 Big Gauges -->
|
| 3472 |
<div class="dash-gauges-hero">
|
|
|
|
| 3561 |
|
| 3562 |
<div class="summary-disclaimer">
|
| 3563 |
<strong>⚠ Cảnh báo</strong> — Thông tin phân tích kỹ thuật này không phải lời khuyên đầu tư. Hãy luôn quản lý rủi ro.
|
| 3564 |
+
</div> <!-- End summary-disclaimer -->
|
| 3565 |
|
| 3566 |
+
</div> <!-- End dash-body -->
|
| 3567 |
+
</div> <!-- End dash-body-scaler -->
|
| 3568 |
`;
|
| 3569 |
|
| 3570 |
// Close logic
|
|
|
|
| 3750 |
if (currentSymbol !== symbol) return; // Discard if user switched
|
| 3751 |
|
| 3752 |
renderAnalysisPanel(symbol, interval, fData);
|
| 3753 |
+
setTimeout(updateDashboardScale, 10);
|
| 3754 |
renderCompactGauges(symbol, interval, fData);
|
| 3755 |
|
| 3756 |
let isBull = false;
|
|
|
|
| 3921 |
if (lastAnalysisPayload && lastAnalysisSymbol === currentSymbol) {
|
| 3922 |
renderAnalysisPanel(lastAnalysisSymbol, lastAnalysisInterval, lastAnalysisPayload);
|
| 3923 |
analysisPanel.classList.add('active');
|
| 3924 |
+
setTimeout(updateDashboardScale, 10);
|
| 3925 |
} else {
|
| 3926 |
// No cached data: fetch from API without reloading chart
|
| 3927 |
analysisPanel.classList.add('active');
|
|
|
|
| 3932 |
</div>
|
| 3933 |
`;
|
| 3934 |
fetchAIAnalysis(currentSymbol, timeframeSelect.value);
|
| 3935 |
+
setTimeout(updateDashboardScale, 10);
|
| 3936 |
}
|
| 3937 |
} else {
|
| 3938 |
// Hide dashboard
|
|
|
|
| 3960 |
}
|
| 3961 |
});
|
| 3962 |
|
| 3963 |
+
/* ── Dashboard Auto-Scale ────────────────── */
|
| 3964 |
+
function updateDashboardScale() {
|
| 3965 |
+
const panel = document.getElementById('analysisPanel');
|
| 3966 |
+
if (!panel || !panel.classList.contains('active')) return;
|
| 3967 |
+
const body = panel.querySelector('.dash-body');
|
| 3968 |
+
const scaler = panel.querySelector('.dash-body-scaler');
|
| 3969 |
+
if (!body || !scaler) return;
|
| 3970 |
+
|
| 3971 |
+
const windowWidth = window.innerWidth;
|
| 3972 |
+
const windowHeight = window.innerHeight;
|
| 3973 |
+
|
| 3974 |
+
// Target width for 1:1 scale (Full HD standard)
|
| 3975 |
+
const targetWidth = 1400;
|
| 3976 |
+
const targetHeight = 850;
|
| 3977 |
+
|
| 3978 |
+
let scaleW = windowWidth / targetWidth;
|
| 3979 |
+
let scaleH = (windowHeight - 80) / targetHeight; // 80px for header
|
| 3980 |
+
let scale = Math.min(scaleW, scaleH);
|
| 3981 |
+
|
| 3982 |
+
// Clamp scale
|
| 3983 |
+
if (scale > 1.1) scale = 1.1;
|
| 3984 |
+
if (scale < 0.45) scale = 0.45;
|
| 3985 |
+
|
| 3986 |
+
body.style.transform = `scale(${scale})`;
|
| 3987 |
+
body.style.width = `${100 / scale}%`;
|
| 3988 |
+
// body.style.height = `${100 / scale}%`;
|
| 3989 |
+
}
|
| 3990 |
+
|
| 3991 |
/* ── Resize handler ────────────────────────── */
|
| 3992 |
const ro = new ResizeObserver(() => {
|
| 3993 |
chart.applyOptions({
|
| 3994 |
width: chartEl.clientWidth,
|
| 3995 |
height: chartEl.clientHeight,
|
| 3996 |
});
|
| 3997 |
+
updateDashboardScale();
|
| 3998 |
});
|
| 3999 |
ro.observe(chartEl);
|
| 4000 |
+
window.addEventListener('resize', updateDashboardScale);
|
| 4001 |
|
| 4002 |
/* ── Theme Management ── */
|
| 4003 |
function applyTheme(theme) {
|
|
|
|
| 4064 |
</script>
|
| 4065 |
</body>
|
| 4066 |
|
| 4067 |
+
</html>
|
update_symbols.py
DELETED
|
@@ -1,84 +0,0 @@
|
|
| 1 |
-
import re
|
| 2 |
-
import sys
|
| 3 |
-
|
| 4 |
-
def main():
|
| 5 |
-
with open('backend/main.py', 'r', encoding='utf-8') as f:
|
| 6 |
-
content = f.read()
|
| 7 |
-
|
| 8 |
-
# Update priorities
|
| 9 |
-
content = content.replace(
|
| 10 |
-
'"Cổ phiếu Mỹ": ["yfinance", "finnhub", "twelvedata"],',
|
| 11 |
-
'"Cổ phiếu Mỹ": ["bybit", "yfinance", "finnhub", "twelvedata"],'
|
| 12 |
-
)
|
| 13 |
-
content = content.replace(
|
| 14 |
-
'"Trái phiếu": ["yfinance"],',
|
| 15 |
-
'"Trái phiếu": ["bybit", "yfinance"],'
|
| 16 |
-
)
|
| 17 |
-
content = content.replace(
|
| 18 |
-
'"ETF": ["yfinance", "finnhub"],',
|
| 19 |
-
'"ETF": ["bybit", "yfinance", "finnhub"],'
|
| 20 |
-
)
|
| 21 |
-
content = content.replace(
|
| 22 |
-
'DEFAULT_SOURCE_PRIORITY: List[str] = ["yfinance", "twelvedata", "finnhub", "binance"]',
|
| 23 |
-
'DEFAULT_SOURCE_PRIORITY: List[str] = ["bybit", "yfinance", "twelvedata", "finnhub", "binance"]'
|
| 24 |
-
)
|
| 25 |
-
|
| 26 |
-
# We will do regex replacements for specific symbols
|
| 27 |
-
replacements = {
|
| 28 |
-
r'"DXY":\s*_s\("DXY",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"DXY"', '"bybit":"DXY+"'),
|
| 29 |
-
r'"VIX":\s*_s\("VIX",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace(m.group(1), '"bybit":"VIX+", ' + m.group(1)) if '"bybit"' not in m.group(1) else m.group(0),
|
| 30 |
-
r'"US10Y":\s*_s\("US10Y",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace(m.group(1), '"bybit":"US10Y+", ' + m.group(1)) if '"bybit"' not in m.group(1) else m.group(0),
|
| 31 |
-
|
| 32 |
-
# Indices
|
| 33 |
-
r'"DOW30":\s*_s\("DOW30",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"DJ30"', '"bybit":"US30"'),
|
| 34 |
-
r'"NIKKEI225":\s*_s\("NIKKEI225",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"Nikkei225"', '"bybit":"JPN225"'),
|
| 35 |
-
r'"EU50":\s*_s\("EU50",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace(m.group(1), '"bybit":"EU50", ' + m.group(1)) if '"bybit"' not in m.group(1) else m.group(0),
|
| 36 |
-
r'"ASX200":\s*_s\("ASX200",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace(m.group(1), '"bybit":"AUS200", ' + m.group(1)) if '"bybit"' not in m.group(1) else m.group(0),
|
| 37 |
-
|
| 38 |
-
# Energy & Metals & Agri
|
| 39 |
-
r'"NATURAL_GAS":\s*_s\("NATURAL_GAS",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"NG"', '"bybit":"NGAS"'),
|
| 40 |
-
r'"COPPER":\s*_s\("COPPER",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"Copper"', '"bybit":"COPPER"'),
|
| 41 |
-
r'"WHEAT":\s*_s\("WHEAT",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"Wheat"', '"bybit":"WHEAT"'),
|
| 42 |
-
r'"CORN":\s*_s\("CORN",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"Corn"', '"bybit":"CORN"'),
|
| 43 |
-
r'"SOYBEAN":\s*_s\("SOYBEAN",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"Soybean"', '"bybit":"SOYBEAN"'),
|
| 44 |
-
r'"SUGAR_11":\s*_s\("SUGAR_11",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"Sugar"', '"bybit":"SUGAR"'),
|
| 45 |
-
r'"COFFEE_ARABICA":\s*_s\("COFFEE_ARABICA",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"Coffee"', '"bybit":"COFFEE"'),
|
| 46 |
-
r'"COCOA":\s*_s\("COCOA",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"Cocoa"', '"bybit":"COCOA"'),
|
| 47 |
-
r'"COTTON":\s*_s\("COTTON",[^\{]+\{([^}]+)\}': lambda m: m.group(0).replace('"bybit":"Cotton"', '"bybit":"COTTON"'),
|
| 48 |
-
}
|
| 49 |
-
|
| 50 |
-
for pat, rep in replacements.items():
|
| 51 |
-
content = re.sub(pat, rep, content, count=1)
|
| 52 |
-
|
| 53 |
-
# Stocks & ETF (add bybit to existing ones)
|
| 54 |
-
stocks_to_update = ["AAPL", "MSFT", "NVDA", "GOOGL", "AMZN", "META", "TSLA", "JPM", "V", "MA", "WMT", "AMD", "INTC", "NFLX", "DIS"]
|
| 55 |
-
for stock in stocks_to_update:
|
| 56 |
-
pat = r'"XYZ":\s*_s\("XYZ",[^{]+\{([^}]+)\}'.replace('XYZ', stock)
|
| 57 |
-
def repl(m, s=stock):
|
| 58 |
-
if '"bybit"' not in m.group(1):
|
| 59 |
-
return m.group(0).replace(m.group(1), f'"bybit":"{s}", ' + m.group(1))
|
| 60 |
-
return m.group(0)
|
| 61 |
-
content = re.sub(pat, repl, content, count=1)
|
| 62 |
-
|
| 63 |
-
# Insert missing stocks and QQQ ETF
|
| 64 |
-
missing_symbols = """
|
| 65 |
-
"JNJ": _s("JNJ","Johnson & Johnson","JNJ","Cổ phiếu Mỹ",{"bybit":"JNJ","yfinance":"JNJ"}),
|
| 66 |
-
"PG": _s("PG","Procter & Gamble","P&G","Cổ phiếu Mỹ",{"bybit":"PG","yfinance":"PG"}),
|
| 67 |
-
"KO": _s("KO","Coca-Cola","Coca-Cola","Cổ phiếu Mỹ",{"bybit":"KO","yfinance":"KO"}),
|
| 68 |
-
"MCD": _s("MCD","McDonald's","McDonald's","Cổ phiếu Mỹ",{"bybit":"MCD","yfinance":"MCD"}),
|
| 69 |
-
"BA": _s("BA","Boeing","Boeing","Cổ phiếu Mỹ",{"bybit":"BA","yfinance":"BA"}),
|
| 70 |
-
"BABA": _s("BABA","Alibaba","Alibaba","Cổ phiếu Mỹ",{"bybit":"BABA","yfinance":"BABA"}),
|
| 71 |
-
"QQQ": _s("QQQ","Invesco QQQ Trust","QQQ","ETF",{"bybit":"QQQ+","yfinance":"QQQ"}),
|
| 72 |
-
"""
|
| 73 |
-
# Insert right before Cổ phiếu Việt Nam
|
| 74 |
-
if '"JNJ"' not in content:
|
| 75 |
-
content = content.replace(
|
| 76 |
-
' # ══════════════════════════════════════════════════════════════════════\n # 9. CỔ PHIẾU VIỆT NAM',
|
| 77 |
-
missing_symbols + '\n # ══════════════════════════════════════════════════════════════════════\n # 9. CỔ PHIẾU VIỆT NAM'
|
| 78 |
-
)
|
| 79 |
-
|
| 80 |
-
with open('backend/main.py', 'w', encoding='utf-8') as f:
|
| 81 |
-
f.write(content)
|
| 82 |
-
|
| 83 |
-
if __name__ == '__main__':
|
| 84 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|