Harden strategy SoT: fail-closed funds gate, shared open pipeline, LIVE switch guards.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -83,10 +83,17 @@ async def download_backup(
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
except FileNotFoundError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e)) from e
|
||||
from starlette.background import BackgroundTask
|
||||
|
||||
from ..backup import materialize_download_zip
|
||||
|
||||
# 下载包剥离 .env,机内完整备份仍保留供恢复
|
||||
safe = materialize_download_zip(path)
|
||||
return FileResponse(
|
||||
path,
|
||||
safe,
|
||||
media_type="application/zip",
|
||||
filename=path.name,
|
||||
filename=path.name.replace(".zip", "_noenv.zip"),
|
||||
background=BackgroundTask(lambda: safe.unlink(missing_ok=True)),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -230,6 +230,12 @@ async def funds_transfer(
|
||||
)
|
||||
finally:
|
||||
client.close()
|
||||
try:
|
||||
from ..strategy.open_capacity import invalidate_live_balance_cache
|
||||
|
||||
invalidate_live_balance_cache()
|
||||
except Exception:
|
||||
pass
|
||||
if not r.get("ok"):
|
||||
raise HTTPException(status_code=400, detail=r.get("detail") or "划转失败")
|
||||
return r
|
||||
|
||||
@@ -315,6 +315,32 @@ async def put_strategy_settings(
|
||||
detail="有未平仓,无法切换永续保证金模式;请先平仓后再改",
|
||||
)
|
||||
|
||||
# 持仓中禁止改动会影响本组成交/出场的参数(基线:运行中无人工开平仓,策略锁定本组成交)
|
||||
if Matcher(db).has_open_position():
|
||||
locked_keys = (
|
||||
"net_profit_target",
|
||||
"exit_mode",
|
||||
"premium_exit_multiple",
|
||||
"perp_qty_eth",
|
||||
"option_qty_eth",
|
||||
"leverage",
|
||||
"sizing_mode",
|
||||
"risk_perp_unit",
|
||||
"risk_option_unit",
|
||||
"risk_exit_unit",
|
||||
"risk_loss_mode",
|
||||
"risk_loss_pct",
|
||||
"risk_loss_usdt",
|
||||
"risk_capital_source",
|
||||
"risk_manual_capital_usdt",
|
||||
)
|
||||
hit = [k for k in locked_keys if k in data]
|
||||
if hit:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail=f"有未平仓,禁止修改本组成交相关参数:{', '.join(hit)};请先平仓",
|
||||
)
|
||||
|
||||
# 以损定仓 ↔ 手动仓位互斥;开启以损定仓时强制 fixed_usdt,并忽略手填名义/出场
|
||||
sizing_mode = str(
|
||||
data.get(
|
||||
@@ -458,6 +484,12 @@ async def put_runtime_settings(
|
||||
status_code=400,
|
||||
detail="切换到 LIVE 须在 confirm_live_phrase 传入 LIVE",
|
||||
)
|
||||
secret = (s.auth_secret or "").strip()
|
||||
if not secret or secret == "change-me-eth-hedge-sim-secret":
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="切到 LIVE 前请在 .env 设置非默认 AUTH_SECRET",
|
||||
)
|
||||
|
||||
updates: dict[str, str] = {}
|
||||
if body.okx_api_key is not None and body.okx_api_key.strip():
|
||||
@@ -495,6 +527,24 @@ async def put_runtime_settings(
|
||||
detail="切到 LIVE 前请先配置完整币安 API Key/Secret",
|
||||
)
|
||||
|
||||
# 热切 LIVE:强制暂停策略(对齐冷启动护栏;运行中禁止人工开平仓基线)
|
||||
if new_mode == "LIVE" and cur_mode != "LIVE":
|
||||
db.execute(
|
||||
"UPDATE strategy_state SET running=0, phase=?, last_error=? WHERE id=1",
|
||||
("paused", "已切换 LIVE,策略已强制暂停;确认就绪后再启动"),
|
||||
)
|
||||
try:
|
||||
from ..strategy import get_engine
|
||||
|
||||
# 同步停循环标志(pause 为 async,此处只写状态)
|
||||
get_engine()._set_state(
|
||||
running=0,
|
||||
phase="paused",
|
||||
last_error="已切换 LIVE,策略已强制暂停;确认就绪后再启动",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
from ..strategy import get_engine
|
||||
|
||||
|
||||
+4
-30
@@ -105,41 +105,15 @@ async def sim_open_group(
|
||||
|
||||
wkey = window_key()
|
||||
db = get_db()
|
||||
from ..strategy.risk_sizing import apply_risk_sizing_to_ledger
|
||||
from ..strategy.open_pipeline import size_and_gate
|
||||
|
||||
rs = apply_risk_sizing_to_ledger(
|
||||
prep = size_and_gate(
|
||||
index_px=float(pick.underlying_px),
|
||||
option_ask=sizing_ask,
|
||||
db=db,
|
||||
)
|
||||
if not rs.ok:
|
||||
raise HTTPException(status_code=409, detail=rs.detail)
|
||||
|
||||
try:
|
||||
from ..strategy.auto_usdc import ensure_okx_trading_usdc
|
||||
|
||||
ensure_okx_trading_usdc(db)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
from ..strategy.open_capacity import assess_open_capacity
|
||||
|
||||
cap = assess_open_capacity(db)
|
||||
if cap.get("perp_can_open") is False or cap.get("option_can_open") is False:
|
||||
detail = (
|
||||
f"{cap.get('perp_label')} · {cap.get('option_label')};"
|
||||
f"永续需≈{cap.get('perp_need_usdt')}U/有{cap.get('perp_have_usdt')}U,"
|
||||
f"期权需≈{cap.get('option_need_usdc')}U/有{cap.get('option_have_usdc')}U"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail=f"资金不足,暂不可开新仓:{detail}",
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
if not prep.ok:
|
||||
raise HTTPException(status_code=409, detail=prep.detail)
|
||||
|
||||
count = len(
|
||||
db.fetchall("SELECT group_id FROM groups WHERE group_id LIKE ?", (f"G-{wkey}-%",))
|
||||
|
||||
Reference in New Issue
Block a user