1
This commit is contained in:
parent
4a69b42392
commit
ca959c1f8a
72
API_KEY_与IP限频说明.md
Normal file
72
API_KEY_与IP限频说明.md
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
# API Key 使用方式与 IP 限频说明
|
||||||
|
|
||||||
|
## 一、获取余额、市场报价:每个账号用自己的 API Key 吗?
|
||||||
|
|
||||||
|
**是的。每个账号都用自己配置的 API Key。**
|
||||||
|
|
||||||
|
### 1. 后端 Web API(前端请求「我的余额」「持仓」等)
|
||||||
|
|
||||||
|
- 请求会带上当前登录用户/选择的 **account_id**。
|
||||||
|
- 后端用 `Account.get_credentials(account_id)` 取**该账号**的 `BINANCE_API_KEY` / `BINANCE_API_SECRET`。
|
||||||
|
- 用这份 Key 创建 `BinanceClient`,再调 `get_account_balance()`、`get_ticker_24h()`、持仓、下单等。
|
||||||
|
- **结论**:账号 A 的请求用 A 的 Key,账号 B 用 B 的 Key,互不混用。
|
||||||
|
|
||||||
|
### 2. 交易进程(自动交易 / 扫描)
|
||||||
|
|
||||||
|
- 每个账号一个独立进程(由 supervisor 按 account 起多个 `trading_system.main`)。
|
||||||
|
- 每个进程启动时通过环境变量 **`ATS_ACCOUNT_ID`** 指定自己是哪个账号。
|
||||||
|
- 配置通过 `ConfigManager.for_account(account_id)` 读取,其中 **BINANCE_API_KEY / BINANCE_API_SECRET** 来自该 account_id 在 `accounts` 表里配置的密钥。
|
||||||
|
- 该进程内所有调用(**获取余额、获取交易对列表、获取行情 ticker/K 线、下单**)都用**同一个 BinanceClient**,即**该账号的 API Key**。
|
||||||
|
- **结论**:每个账号的扫描与交易都只用自己的 Key,没有“统一一个 Key”的情况。
|
||||||
|
|
||||||
|
### 3. 推荐服务(仅行情、不下单)
|
||||||
|
|
||||||
|
- `recommendations_main` deliberately 使用 **空 API Key**(`BinanceClient(api_key="", api_secret="")`),只调**公开接口**(行情、K 线等),不占用任何账号的 Key,也不读 account 的密钥。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、-1003「IP 被封」和「轮转 API Key」的关系
|
||||||
|
|
||||||
|
错误示例:`APIError(code=-1003): Way too many requests; IP(43.199.103.162) banned until ...`
|
||||||
|
|
||||||
|
- 这里限制的是 **IP**,不是某个 API Key。
|
||||||
|
- 所有从你这台服务器(同一出口 IP)发出的请求,不管用哪个账号的 Key,都会算在**同一个 IP** 上。
|
||||||
|
- 所以:**换用不同账号的 API Key 轮转,不能绕过或解除 IP 封禁**;封禁期间用哪个 Key 都会失败。
|
||||||
|
|
||||||
|
轮转 Key 只有在币安按 **API Key** 维度限频时才有用;当前你遇到的是 **IP 维度** 的封禁,所以轮转无法解决“等多久才能用”的问题,只能等该 IP 解封时间到期。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、为什么多账号时更容易触发 IP 封禁?
|
||||||
|
|
||||||
|
- 每个账号一个进程,**每个进程都会自己做一整轮市场扫描**:
|
||||||
|
- `get_all_usdt_pairs()`
|
||||||
|
- 对大量交易对再请求 K 线、ticker 等
|
||||||
|
- 若有 3 个账号,同一台机器上就是 **3 份完整扫描**,请求量约等于单账号的 3 倍,且都从**同一 IP** 发出,更容易触发「Way too many requests」和 IP 封禁。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、可以怎么做来减少封禁 / 尽快恢复使用?
|
||||||
|
|
||||||
|
1. **解封前**
|
||||||
|
- 等日志里 `banned until` 的时间戳过期(参见 `BINANCE_IP_BAN_1003.md` 里用脚本算「还要等多久」)。
|
||||||
|
|
||||||
|
2. **解封后降低同一 IP 的请求量**
|
||||||
|
- 拉大 **SCAN_INTERVAL**(如 900 → 1200 或 1800 秒)。
|
||||||
|
- 适当减小 **MAX_SCAN_SYMBOLS** / **TOP_N_SYMBOLS**。
|
||||||
|
- 多账号时,考虑**只用一个进程做扫描、结果写入 Redis,各账号进程只读**,这样行情/扫描请求只打一份,从同一 IP 出去的请求会少很多(需改架构,可后续做)。
|
||||||
|
|
||||||
|
3. **轮转 API Key**
|
||||||
|
- 对**解除当前这次 IP 封禁**没有帮助。
|
||||||
|
- 若未来遇到的是「按 Key 限频」而不是「按 IP 封禁」,再考虑按 Key 轮转或分摊请求才有意义。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、总结表
|
||||||
|
|
||||||
|
| 能力 | 谁在调用 | 用的 API Key |
|
||||||
|
|----------------|----------------|----------------------|
|
||||||
|
| 获取余额 | 后端 / 交易进程 | 当前 account_id 的 Key |
|
||||||
|
| 获取市场报价等 | 交易进程 | 当前 account_id 的 Key |
|
||||||
|
| 推荐服务行情 | recommendations_main | 空 Key(仅公开接口) |
|
||||||
|
| -1003 IP 封禁 | - | 轮转 Key **无法**绕过,需等解封并降请求量 |
|
||||||
|
|
@ -105,33 +105,51 @@ async def get_dashboard_data(account_id: int = Depends(get_account_id)):
|
||||||
account_data = None
|
account_data = None
|
||||||
account_error = None
|
account_error = None
|
||||||
|
|
||||||
# [EMERGENCY FIX] 强制使用数据库快照,停止实时API请求以解决 -1003 IP Ban 问题
|
# 优先请求币安实时余额;失败时(如 -1003 IP 封禁)再回退到数据库快照
|
||||||
logger.warning(f"检测到潜在的 API 限流风险,强制使用数据库快照 (account_id={account_id})")
|
|
||||||
|
|
||||||
# 回退到数据库快照
|
|
||||||
try:
|
try:
|
||||||
snapshots = AccountSnapshot.get_recent(1, account_id=account_id)
|
from api.routes.account import get_realtime_account_data
|
||||||
if snapshots:
|
account_data = await get_realtime_account_data(account_id=account_id)
|
||||||
account_data = {
|
if account_data and account_data.get('total_balance') is not None:
|
||||||
"total_balance": snapshots[0].get('total_balance', 0),
|
logger.info("使用币安实时账户数据")
|
||||||
"available_balance": snapshots[0].get('available_balance', 0),
|
|
||||||
"total_position_value": snapshots[0].get('total_position_value', 0),
|
|
||||||
"total_pnl": snapshots[0].get('total_pnl', 0),
|
|
||||||
"open_positions": snapshots[0].get('open_positions', 0)
|
|
||||||
}
|
|
||||||
logger.info("使用数据库快照作为账户数据")
|
|
||||||
else:
|
else:
|
||||||
logger.warning("数据库中没有账户快照数据")
|
account_data = None
|
||||||
# 构造一个空的账户数据结构,避免前端报错
|
account_error = "实时余额返回为空"
|
||||||
account_data = {
|
except Exception as live_err:
|
||||||
"total_balance": 0,
|
account_error = str(live_err)
|
||||||
"available_balance": 0,
|
logger.warning(f"获取实时账户数据失败 (account_id={account_id}),回退到数据库快照: {live_err}")
|
||||||
"total_position_value": 0,
|
|
||||||
"total_pnl": 0,
|
# 实时请求失败或无数据时,使用数据库快照
|
||||||
"open_positions": 0
|
if not account_data or account_data.get('total_balance') is None:
|
||||||
}
|
try:
|
||||||
except Exception as db_error:
|
snapshots = AccountSnapshot.get_recent(1, account_id=account_id)
|
||||||
logger.error(f"从数据库获取账户快照失败: {db_error}")
|
if snapshots:
|
||||||
|
account_data = {
|
||||||
|
"total_balance": snapshots[0].get('total_balance', 0),
|
||||||
|
"available_balance": snapshots[0].get('available_balance', 0),
|
||||||
|
"total_position_value": snapshots[0].get('total_position_value', 0),
|
||||||
|
"total_pnl": snapshots[0].get('total_pnl', 0),
|
||||||
|
"open_positions": snapshots[0].get('open_positions', 0)
|
||||||
|
}
|
||||||
|
logger.info("使用数据库快照作为账户数据")
|
||||||
|
else:
|
||||||
|
if not account_data:
|
||||||
|
account_data = {}
|
||||||
|
account_data.setdefault("total_balance", 0)
|
||||||
|
account_data.setdefault("available_balance", 0)
|
||||||
|
account_data.setdefault("total_position_value", 0)
|
||||||
|
account_data.setdefault("total_pnl", 0)
|
||||||
|
account_data.setdefault("open_positions", 0)
|
||||||
|
logger.warning("数据库中没有账户快照数据,仪表板显示 0;交易进程会定期写入快照")
|
||||||
|
except Exception as db_error:
|
||||||
|
logger.error(f"从数据库获取账户快照失败: {db_error}")
|
||||||
|
if not account_data:
|
||||||
|
account_data = {
|
||||||
|
"total_balance": 0,
|
||||||
|
"available_balance": 0,
|
||||||
|
"total_position_value": 0,
|
||||||
|
"total_pnl": 0,
|
||||||
|
"open_positions": 0
|
||||||
|
}
|
||||||
|
|
||||||
# 获取持仓数据(强制使用数据库记录)
|
# 获取持仓数据(强制使用数据库记录)
|
||||||
open_trades = []
|
open_trades = []
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ class TradingStrategy:
|
||||||
if cache_size > 0:
|
if cache_size > 0:
|
||||||
logger.info(f"价格缓存: {cache_size}个交易对")
|
logger.info(f"价格缓存: {cache_size}个交易对")
|
||||||
|
|
||||||
# 记录账户快照到数据库
|
# 记录账户快照到数据库(带 account_id,仪表板在实时 API 失败时会按账号读快照)
|
||||||
try:
|
try:
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -274,12 +274,20 @@ class TradingStrategy:
|
||||||
if backend_path.exists():
|
if backend_path.exists():
|
||||||
sys.path.insert(0, str(backend_path))
|
sys.path.insert(0, str(backend_path))
|
||||||
from database.models import AccountSnapshot
|
from database.models import AccountSnapshot
|
||||||
|
aid = getattr(self.position_manager, 'account_id', None)
|
||||||
|
if aid is None:
|
||||||
|
import os
|
||||||
|
try:
|
||||||
|
aid = int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1)
|
||||||
|
except Exception:
|
||||||
|
aid = 1
|
||||||
AccountSnapshot.create(
|
AccountSnapshot.create(
|
||||||
total_balance=summary['totalBalance'],
|
total_balance=summary['totalBalance'],
|
||||||
available_balance=summary['availableBalance'],
|
available_balance=summary['availableBalance'],
|
||||||
total_position_value=sum(abs(p['positionAmt'] * p['entryPrice']) for p in summary.get('positions', [])),
|
total_position_value=sum(abs(p['positionAmt'] * p['entryPrice']) for p in summary.get('positions', [])),
|
||||||
total_pnl=summary['totalPnL'],
|
total_pnl=summary['totalPnL'],
|
||||||
open_positions=summary['totalPositions']
|
open_positions=summary['totalPositions'],
|
||||||
|
account_id=aid
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"记录账户快照失败: {e}")
|
logger.debug(f"记录账户快照失败: {e}")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user