Support LIVE exchange funds and perp margin mode.

Hide sim equity when LIVE is selected; OKX funds bar uses exchange balances; add cross/isolated for perp only (options stay cash).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-29 19:57:09 +08:00
parent 50a4540aaf
commit c6e8f6fe1e
12 changed files with 134 additions and 24 deletions
+14 -8
View File
@@ -115,9 +115,13 @@ async def funds_summary(_user: Annotated[str, Depends(require_user)]) -> dict[st
trading_usdt = bal.get("trading_usdt")
funding_usdc = bal.get("funding_usdc")
trading_usdc = bal.get("trading_usdc")
parts = [funding_usdt, trading_usdt, funding_usdc, trading_usdc]
vals = [float(x) for x in parts if x is not None]
total = round(sum(vals), 2) if vals else None
r = float(rate) if rate and rate > 0 else 1.0
total = round(
(funding_usdt or 0.0)
+ (trading_usdt or 0.0)
+ ((funding_usdc or 0.0) + (trading_usdc or 0.0)) * r,
2,
)
except Exception as e:
return {
"ok": False,
@@ -128,11 +132,13 @@ async def funds_summary(_user: Annotated[str, Depends(require_user)]) -> dict[st
finally:
client.close()
else:
# 非 OKX LIVE回退本地账本
led = Ledger(db).snapshot()
trading_usdt = float(led["equity"])
total = trading_usdt
# 非 OKX LIVE暂无统一资金接口,不回退模拟账本(避免实盘显示假资金)
return {
"ok": False,
"mode": mode,
"exchange": exchange,
"detail": f"{exchange} 实盘资金摘要暂未接入,请用 OKX 或交易所 App 查看",
}
return {
"ok": True,
"mode": mode,
+16
View File
@@ -37,6 +37,7 @@ KEYS = (
"skip_weekends",
"initial_equity",
"leverage",
"perp_margin_mode",
"min_option_hours",
"min_option_leverage",
"atm_open_offset_enabled",
@@ -61,6 +62,7 @@ class StrategySettingsBody(BaseModel):
skip_weekends: bool | None = None
initial_equity: float | None = Field(default=None, ge=1000, le=10_000_000)
leverage: float | None = Field(default=None, ge=1, le=125)
perp_margin_mode: str | None = Field(default=None, pattern="^(cross|isolated)$")
min_option_hours: float | None = Field(default=None, ge=1, le=720)
min_option_leverage: float | None = Field(default=None, ge=1, le=10000)
atm_open_offset_enabled: bool | None = None
@@ -117,6 +119,20 @@ def _read_settings() -> dict:
db.get_setting("initial_equity", str(s.initial_equity)) or s.initial_equity
),
"leverage": float(db.get_setting("leverage", str(s.leverage)) or s.leverage),
"perp_margin_mode": (
mm
if (
mm := str(
db.get_setting("perp_margin_mode", s.perp_margin_mode)
or s.perp_margin_mode
or "cross"
)
.strip()
.lower()
)
in ("cross", "isolated")
else "cross"
),
"min_option_hours": float(
db.get_setting("min_option_hours", str(s.min_option_hours))
or s.min_option_hours
+2
View File
@@ -68,6 +68,8 @@ class Settings(BaseSettings):
live_order_interval_sec: float = 1.0 # LIVE 私有下单/查单最小间隔(秒)
skip_weekends: bool = True # 上海时区周六日禁止新开仓(已有仓仍可平)
leverage: float = 3.0 # 永续杠杆
# 永续保证金模式:cross=全仓(默认)| isolated=逐仓;期权仍固定 cashOKX 逐仓/现金)
perp_margin_mode: str = "cross"
min_option_hours: float = 12.0 # 期权最小剩余小时
min_option_leverage: float = 100.0 # 现价/卖一权利金 下限
atm_open_offset_enabled: bool = False # 开仓 ATM 偏差限制开关(默认关)
+15 -4
View File
@@ -37,6 +37,16 @@ class OkxLiveExecutor(Matcher):
self._trade = OkxTradeClient()
return self._trade
def _perp_margin_mode(self) -> str:
"""永续全仓/逐仓;期权始终 cash,不受此设置影响。"""
s = get_settings()
raw = str(
self.ledger.get_setting_str("perp_margin_mode", s.perp_margin_mode)
or s.perp_margin_mode
or "cross"
).strip().lower()
return "isolated" if raw == "isolated" else "cross"
def _guard_live(self) -> str | None:
ok, reason = live_ready()
if not ok:
@@ -145,9 +155,10 @@ class OkxLiveExecutor(Matcher):
else:
side, pos_side = "sell", "short"
leverage = self.ledger.get_setting_float("leverage", s.leverage)
mgn = self._perp_margin_mode()
try:
client.set_leverage(
perp_inst, leverage, mgn_mode="cross", pos_side=pos_side
perp_inst, leverage, mgn_mode=mgn, pos_side=pos_side
)
except Exception as e_lev:
logger.warning("okx set_leverage failed: %s", e_lev)
@@ -155,7 +166,7 @@ class OkxLiveExecutor(Matcher):
inst_id=perp_inst,
side=side,
sz=str(perp_sz),
td_mode="cross",
td_mode=mgn,
pos_side=pos_side,
)
except Exception as e:
@@ -642,7 +653,7 @@ class OkxLiveExecutor(Matcher):
inst_id=perp_inst,
side=side,
sz=str(perp_sz),
td_mode="cross",
td_mode=self._perp_margin_mode(),
pos_side=pos_side,
reduce_only=True,
)
@@ -927,7 +938,7 @@ class OkxLiveExecutor(Matcher):
inst_id=perp_inst,
side=side,
sz=str(perp_sz),
td_mode="cross",
td_mode=self._perp_margin_mode(),
pos_side=pos_side,
reduce_only=True,
)
+1
View File
@@ -228,6 +228,7 @@ class Database:
"skip_weekends": str(s.skip_weekends),
"max_rounds": str(s.max_rounds),
"leverage": str(s.leverage),
"perp_margin_mode": str(s.perp_margin_mode),
"min_option_hours": str(s.min_option_hours),
"min_option_leverage": str(s.min_option_leverage),
"atm_open_offset_enabled": str(s.atm_open_offset_enabled),
+8
View File
@@ -83,6 +83,13 @@ class StrategyEngine:
rest_sec = self.ledger.get_setting_int("rest_seconds", s.rest_seconds)
skip_weekends = self.ledger.get_setting_bool("skip_weekends", s.skip_weekends)
leverage = self.ledger.get_setting_float("leverage", s.leverage)
perp_mm = str(
self.ledger.get_setting_str("perp_margin_mode", s.perp_margin_mode)
or s.perp_margin_mode
or "cross"
).strip().lower()
if perp_mm not in ("cross", "isolated"):
perp_mm = "cross"
min_hours = self.ledger.get_setting_float("min_option_hours", s.min_option_hours)
min_opt_lev = self.ledger.get_setting_float(
"min_option_leverage", s.min_option_leverage
@@ -140,6 +147,7 @@ class StrategyEngine:
"premium_exit_multiple": prem_mult,
"exit_target_usdt": exit_amt,
"leverage": leverage,
"perp_margin_mode": perp_mm,
"perp_qty_eth": perp_qty,
"option_qty_eth": opt_qty,
"min_option_hours": min_hours,