@@ -1,4 +1,4 @@
""" 开仓资金可开判定:永续保证金 + 期权权利金 。 """
""" 开仓资金可开判定:只看交易账户(永续 USDT / 期权 USDC) 。 """
from __future__ import annotations
@@ -16,9 +16,8 @@ logger = logging.getLogger(__name__)
_live_bal_cache : dict [ str , Any ] = { " ts " : 0.0 , " data " : None }
_LIVE_BAL_TTL_SEC = 8.0
_last_notify_key : str | None = None
_last_notify_ms : float = 0.0
_NOTIFY_DEDUP_SEC = 600.0 # 同状态 10 分钟内不重复推
# 不足期间只推一次;资金恢复后清零,下次再不足可再推一次
_notified_while_short : bool = False
def _f ( v : Any ) - > float | None :
@@ -57,9 +56,7 @@ def _live_balances() -> dict[str, float | None]:
if _live_bal_cache [ " data " ] is not None and now - float ( _live_bal_cache [ " ts " ] ) < _LIVE_BAL_TTL_SEC :
return dict ( _live_bal_cache [ " data " ] )
out : dict [ str , float | None ] = {
" funding_usdt " : None ,
" trading_usdt " : None ,
" funding_usdc " : None ,
" trading_usdc " : None ,
}
try :
@@ -68,8 +65,8 @@ def _live_balances() -> dict[str, float | None]:
client = OkxFundsClient ( )
try :
bal = client . fetch_balances ( )
for k in out :
out [ k ] = _f ( bal . get ( k ) )
out [ " trading_usdt " ] = _f ( bal . get ( " trading_usdt " ) )
out [ " trading_usdc " ] = _f ( bal . get ( " trading_usdc " ) )
finally :
client . close ( )
except Exception as e :
@@ -80,31 +77,21 @@ def _live_balances() -> dict[str, float | None]:
def _sim_balances ( db : Database ) - > dict [ str , float ] :
""" 只看交易账户,不看资金账户。 """
w = SimFundsWallets ( db ) . view ( )
led = Ledger ( db ) . snapshot ( )
avail = float ( led . get ( " available " ) or 0 )
trading_usdt = float ( w . get ( " trading_usdt " ) or 0 )
trading_usdc = float ( w . get ( " trading_usdc " ) or 0 )
# 未划转到交易账户时回退账本可用(兼容旧 SIM)
if trading_usdt < 1e-9 and trading_usdc < 1e-9 and avail > 1e-9 :
return {
" perp_usdt " : avail ,
" option_usdc " : avail ,
" ledger_available " : avail ,
}
return {
" perp_usdt " : trading_usdt ,
" option_usdc " : trading_usdc ,
" ledger_available " : avail ,
" perp_usdt " : float ( w . get ( " trading_usdt " ) or 0 ) ,
" option_usdc " : float ( w . get ( " trading_usdc " ) or 0 ) ,
}
def assess_open_capacity ( db : Database | None = None ) - > dict [ str , Any ] :
"""
返回永续/期权是否有足够资金开新仓。
- 永续:需 USDT >= 名义/杠杆
- 期权:需 USDC(或 SIM 回退可用) >= 卖一×名义×(1+费率)
返回永续/期权是否有足够交易账户 资金开新仓。
- 永续:交易账户 USDT >= 名义/杠杆
- 期权:交易账户 USDC >= 卖一×名义×(1+费率)
"""
global _notified_while_short
db = db or get_db ( )
s = get_settings ( )
ledger = Ledger ( db )
@@ -127,7 +114,6 @@ def assess_open_capacity(db: Database | None = None) -> dict[str, Any]:
have_opt = float ( bal [ " option_usdc " ] )
else :
live = _live_balances ( )
# OKX:永续与期权都在交易账户
t_usdt = live . get ( " trading_usdt " )
t_usdc = live . get ( " trading_usdc " )
have_perp = float ( t_usdt ) if t_usdt is not None else None
@@ -145,6 +131,11 @@ def assess_open_capacity(db: Database | None = None) -> dict[str, Any]:
else :
opt_ok = have_opt + 1e-9 > = premium_need
funds_ok = perp_ok is True and opt_ok is True
# 资金恢复后允许下次不足再通知一次
if funds_ok :
_notified_while_short = False
lev_i = int ( round ( lev ) ) if abs ( lev - round ( lev ) ) < 1e-9 else lev
if perp_ok is True :
perp_label = f " 永续 { lev_i } x 可开 "
@@ -174,43 +165,43 @@ def assess_open_capacity(db: Database | None = None) -> dict[str, Any]:
" option_can_open " : opt_ok ,
" perp_label " : perp_label ,
" option_label " : opt_label ,
" funds_ok " : ( perp_ok is True and opt_ok is True ) ,
" funds_ok " : funds_ok ,
" source " : " trading " ,
}
def maybe_notify_funds_short ( cap : dict [ str , Any ] | None = None ) - > None :
""" 资金不足时企业微信推送(去重) 。 """
global _last_notify_key , _last_notify_ms
""" 交易账户 资金不足时企业微信推送:不足期间只推一次 。"""
global _notified_while_short
cap = cap or assess_open_capacity ( )
short = cap . get ( " perp_can_open " ) is False or cap . get ( " option_can_open " ) is False
if not short :
_notified_while_short = False
return
if _notified_while_short :
return
_notified_while_short = True
parts : list [ str ] = [ ]
if cap . get ( " perp_can_open " ) is False :
parts . append (
f " 永续 不足:需约 { cap . get ( ' perp_need_usdt ' ) } U,现有 { cap . get ( ' perp_have_usdt ' ) } U "
f " 交易账户 USDT 不足:需约 { cap . get ( ' perp_need_usdt ' ) } U,现有 { cap . get ( ' perp_have_usdt ' ) } U "
)
if cap . get ( " option_can_open " ) is False :
parts . append (
f " 期权 不足:需约 { cap . get ( ' option_need_usdc ' ) } U,现有 { cap . get ( ' option_have_usdc ' ) } U "
f " 交易账户 USDC 不足:需约 { cap . get ( ' option_need_usdc ' ) } U,现有 { cap . get ( ' option_have_usdc ' ) } U "
)
if not parts :
return
key = " | " . join ( parts )
now = time . time ( )
if key == _last_notify_key and now - _last_notify_ms < _NOTIFY_DEDUP_SEC :
return
_last_notify_key = key
_last_notify_ms = now
try :
from . . notify import wecom
wecom . notify_async (
wecom . build_markdown (
tag = wecom . TAG_FAULT ,
title = " 资金不足 · 无法开新仓 " ,
title = " 交易账户 资金不足 · 无法开新仓" ,
lines = [
f " **永续**: { cap . get ( ' perp_label ' ) } " ,
f " **期权**: { cap . get ( ' option_label ' ) } " ,
* [ f " **详情**: { p } " for p in parts ] ,
" 请划转/兑换 后重试。 " ,
" 请从资金账户划转到交易账户 后重试。 " ,
] ,
)
)