交易所 API 改为仅服务器配置:前端去掉密钥、新机示例为空,并防止坏钥反复请求触发 Gate 封 IP。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-25 12:21:43 +08:00
parent f675f9997a
commit 75f50fe083
15 changed files with 350 additions and 60 deletions
+3 -4
View File
@@ -76,10 +76,9 @@ TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true
# 是否开启 Binance 实盘下单(false=只做本地流程,true=真实下单)
LIVE_TRADING_ENABLED=true
# Binance API Key(需开通合约,万向划转等权限)
BINANCE_API_KEY=REPLACE_WITH_BINANCE_API_KEY
# Binance API Secret
BINANCE_API_SECRET=REPLACE_WITH_BINANCE_API_SECRET
# Binance API(仅服务器 .env 配置;新机保持为空,填真钥后 pm2 restart --update-env;勿用占位符以免鉴权狂打)
BINANCE_API_KEY=
BINANCE_API_SECRET=
# 保证金模式:cross=全仓,isolated=逐仓
BINANCE_MARGIN_MODE=cross
# 持仓模式:hedge=双向(需账户开启双向持仓,下单带 positionSide);oneway=单向
+40 -5
View File
@@ -35,6 +35,11 @@ import sys
if _REPO_ROOT not in sys.path:
sys.path.insert(0, _REPO_ROOT)
from lib.paths import common_static_dir
from lib.exchange.api_credentials_lib import (
is_exchange_auth_error,
load_markets_public_fallback,
normalize_api_credential,
)
from lib.ai.ai_client import ai_generate, ai_review, ai_short_advice
from lib.ai.ai_review_lib import (
build_journal_ai_chart_path,
@@ -347,8 +352,8 @@ def _resolve_app_tz():
APP_TZ = _resolve_app_tz()
LIVE_TRADING_ENABLED = os.getenv("LIVE_TRADING_ENABLED", "false").lower() == "true"
BINANCE_API_KEY = (os.getenv("BINANCE_API_KEY") or "").strip()
BINANCE_API_SECRET = (os.getenv("BINANCE_API_SECRET") or "").strip()
BINANCE_API_KEY = normalize_api_credential(os.getenv("BINANCE_API_KEY"))
BINANCE_API_SECRET = normalize_api_credential(os.getenv("BINANCE_API_SECRET"))
BINANCE_MARGIN_MODE = (os.getenv("BINANCE_MARGIN_MODE") or "cross").strip().lower()
# hedge=双向持仓(需 positionSide);oneway / single=单向持仓
_raw_binance_pos = (os.getenv("BINANCE_POSITION_MODE") or "hedge").strip().lower()
@@ -490,6 +495,8 @@ if BINANCE_API_KEY and BINANCE_API_SECRET:
exchange.apiKey = BINANCE_API_KEY
exchange.secret = BINANCE_API_SECRET
MARKETS_LOADED = False
# 鉴权失败后停止私有 API(资金/持仓),避免坏钥反复请求;尤其 Gate 易封 IP
EXCHANGE_AUTH_DISABLED_MSG = ""
ACCOUNT_BALANCE_CACHE = {
"updated_at": 0.0,
"funding_usdt": None,
@@ -2859,6 +2866,8 @@ def enrich_order_item(raw_item, current_capital):
def ensure_exchange_live_ready():
if EXCHANGE_AUTH_DISABLED_MSG:
return False, EXCHANGE_AUTH_DISABLED_MSG
if not LIVE_TRADING_ENABLED:
return False, "未开启实盘下单(LIVE_TRADING_ENABLED=false)"
if not (BINANCE_API_KEY and BINANCE_API_SECRET):
@@ -2893,9 +2902,23 @@ def order_row_key_signal_type(row):
def exchange_private_api_configured():
"""仅表示已配置密钥;与是否允许下单(LIVE_TRADING_ENABLED)无关,用于只读拉仓等."""
if EXCHANGE_AUTH_DISABLED_MSG:
return False
return bool(BINANCE_API_KEY and BINANCE_API_SECRET)
def _disable_private_api_after_auth_error(exc):
global EXCHANGE_AUTH_DISABLED_MSG, BINANCE_API_KEY, BINANCE_API_SECRET
from lib.exchange.api_credentials_lib import strip_ccxt_credentials
strip_ccxt_credentials(exchange)
BINANCE_API_KEY = ""
BINANCE_API_SECRET = ""
EXCHANGE_AUTH_DISABLED_MSG = (
f"API 鉴权失败,已停止私有请求(请在服务器 .env 修正密钥后重启): {exc}"
)
def _float_balance_field(val):
if val is None or val == "":
return None
@@ -3178,11 +3201,15 @@ def get_exchange_capitals(force=False):
return ACCOUNT_BALANCE_CACHE["funding_usdt"], ACCOUNT_BALANCE_CACHE["trading_usdt"]
try:
ACCOUNT_BALANCE_CACHE["funding_usdt"] = _fetch_binance_funding_usdt()
except Exception:
except Exception as e:
if is_exchange_auth_error(e):
_disable_private_api_after_auth_error(e)
ACCOUNT_BALANCE_CACHE["funding_usdt"] = None
try:
ACCOUNT_BALANCE_CACHE["trading_usdt"] = _fetch_binance_swap_usdt_total()
except Exception:
except Exception as e:
if is_exchange_auth_error(e):
_disable_private_api_after_auth_error(e)
# 勿保留上一次成功请求的旧值:鉴权失败时否则会误以为「合约余额仍能读」
ACCOUNT_BALANCE_CACHE["trading_usdt"] = None
ACCOUNT_BALANCE_CACHE["updated_at"] = now_ts
@@ -3585,7 +3612,15 @@ def calc_trend_manual_breakeven_stop(direction, entry_price, offset_pct=None):
def ensure_markets_loaded(force=False):
global MARKETS_LOADED
if force or not MARKETS_LOADED:
exchange.load_markets(reload=force)
try:
exchange.load_markets(reload=force)
except Exception as e:
# 坏钥时立刻去掉签名再拉公开 markets,避免反复鉴权(尤其勿拖累同机 Gate)
if is_exchange_auth_error(e) and (getattr(exchange, "apiKey", None) or getattr(exchange, "secret", None)):
_disable_private_api_after_auth_error(e)
load_markets_public_fallback(exchange, reload=True)
else:
raise
MARKETS_LOADED = True