Add SIM/LIVE switch with API keys saved to .env and OKX live executor.
Enable settings UI for mode/keys, gate strategy start when LIVE is not ready, and stop PM2 from forcing MODE=SIM. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+115
-1
@@ -1,11 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
from typing import Annotated, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..config import get_settings
|
||||
from ..env_store import (
|
||||
binance_keys_configured,
|
||||
live_ready,
|
||||
mask_secret,
|
||||
okx_keys_configured,
|
||||
upsert_env_keys,
|
||||
)
|
||||
from ..exchange.runtime import (
|
||||
load_runtime_settings,
|
||||
normalize_exchange_name,
|
||||
@@ -195,3 +202,110 @@ async def put_strategy_settings(
|
||||
) from e
|
||||
|
||||
return _read_settings()
|
||||
|
||||
|
||||
class RuntimeSettingsBody(BaseModel):
|
||||
mode: Literal["SIM", "LIVE"] | None = None
|
||||
confirm_live: bool | None = False
|
||||
okx_api_key: str | None = None
|
||||
okx_api_secret: str | None = None
|
||||
okx_api_passphrase: str | None = None
|
||||
binance_api_key: str | None = None
|
||||
binance_api_secret: str | None = None
|
||||
|
||||
|
||||
def _runtime_payload() -> dict:
|
||||
s = get_settings()
|
||||
rt = load_runtime_settings()
|
||||
mode = "SIM" if s.is_sim else "LIVE"
|
||||
ready, reason = live_ready(exchange=rt.exchange)
|
||||
return {
|
||||
"mode": mode,
|
||||
"exchange": rt.exchange,
|
||||
"okx_configured": okx_keys_configured(s),
|
||||
"binance_configured": binance_keys_configured(s),
|
||||
"okx_api_key_masked": mask_secret(s.okx_api_key),
|
||||
"okx_api_secret_masked": mask_secret(s.okx_api_secret),
|
||||
"okx_api_passphrase_masked": mask_secret(s.okx_api_passphrase),
|
||||
"binance_api_key_masked": mask_secret(s.binance_api_key),
|
||||
"binance_api_secret_masked": mask_secret(s.binance_api_secret),
|
||||
"live_ready": bool(ready) if mode == "LIVE" else True,
|
||||
"live_ready_reason": reason if mode == "LIVE" else "sim",
|
||||
"sim": s.is_sim,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/runtime")
|
||||
async def get_runtime_settings(_user: Annotated[str, Depends(require_user)]) -> dict:
|
||||
return _runtime_payload()
|
||||
|
||||
|
||||
@router.put("/runtime")
|
||||
async def put_runtime_settings(
|
||||
body: RuntimeSettingsBody,
|
||||
_user: Annotated[str, Depends(require_user)],
|
||||
) -> dict:
|
||||
db = get_db()
|
||||
s = get_settings()
|
||||
cur_mode = "SIM" if s.is_sim else "LIVE"
|
||||
new_mode = (body.mode or cur_mode).strip().upper()
|
||||
if new_mode not in ("SIM", "LIVE"):
|
||||
raise HTTPException(status_code=400, detail="mode 须为 SIM 或 LIVE")
|
||||
|
||||
if new_mode != cur_mode and Matcher(db).has_open_position():
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="有未平仓,无法切换 SIM/LIVE;请先平仓后再改",
|
||||
)
|
||||
|
||||
if new_mode == "LIVE" and cur_mode != "LIVE":
|
||||
if not body.confirm_live:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="切换到 LIVE 须二次确认(confirm_live=true)",
|
||||
)
|
||||
|
||||
updates: dict[str, str] = {}
|
||||
if body.okx_api_key is not None and body.okx_api_key.strip():
|
||||
updates["OKX_API_KEY"] = body.okx_api_key.strip()
|
||||
if body.okx_api_secret is not None and body.okx_api_secret.strip():
|
||||
updates["OKX_API_SECRET"] = body.okx_api_secret.strip()
|
||||
if body.okx_api_passphrase is not None and body.okx_api_passphrase.strip():
|
||||
updates["OKX_API_PASSPHRASE"] = body.okx_api_passphrase.strip()
|
||||
if body.binance_api_key is not None and body.binance_api_key.strip():
|
||||
updates["BINANCE_API_KEY"] = body.binance_api_key.strip()
|
||||
if body.binance_api_secret is not None and body.binance_api_secret.strip():
|
||||
updates["BINANCE_API_SECRET"] = body.binance_api_secret.strip()
|
||||
|
||||
if new_mode != cur_mode:
|
||||
updates["MODE"] = new_mode
|
||||
|
||||
if updates:
|
||||
upsert_env_keys(updates)
|
||||
|
||||
s2 = get_settings()
|
||||
if new_mode == "LIVE":
|
||||
rt = load_runtime_settings()
|
||||
if rt.exchange == "okx" and not okx_keys_configured(s2):
|
||||
if cur_mode == "SIM":
|
||||
upsert_env_keys({"MODE": "SIM"})
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="切到 LIVE 前请先配置完整 OKX API Key/Secret/Passphrase",
|
||||
)
|
||||
if rt.exchange == "binance" and not binance_keys_configured(s2):
|
||||
if cur_mode == "SIM":
|
||||
upsert_env_keys({"MODE": "SIM"})
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="切到 LIVE 前请先配置完整币安 API Key/Secret",
|
||||
)
|
||||
|
||||
try:
|
||||
from ..strategy import get_engine
|
||||
|
||||
get_engine().refresh_executor()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return _runtime_payload()
|
||||
|
||||
Reference in New Issue
Block a user