Fix roll margin validation and SHFE close offset for night positions.
Use CTP account margin for roll cap checks and prefer close-today on SHFE when yesterday close is rejected. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1015,10 +1015,19 @@ class CtpBridge:
|
||||
return Offset.CLOSE
|
||||
vol = int(getattr(pos, "volume", 0) or 0)
|
||||
yd = int(getattr(pos, "yd_volume", 0) or 0)
|
||||
today = max(0, vol - yd)
|
||||
if today >= lots:
|
||||
td = max(0, vol - yd)
|
||||
if td >= lots:
|
||||
return Offset.CLOSETODAY
|
||||
if yd >= lots:
|
||||
# 夜盘仓在 vnpy 常记在 yd,日盘平昨易报「平昨仓位不足」→ 上期所/能源优先平今
|
||||
if ex_u in ("SHFE", "INE"):
|
||||
return Offset.CLOSETODAY
|
||||
return Offset.CLOSEYESTERDAY
|
||||
if td + yd >= lots:
|
||||
return Offset.CLOSETODAY
|
||||
if ex_u in ("SHFE", "INE", "CZCE"):
|
||||
return Offset.CLOSETODAY
|
||||
return Offset.CLOSE
|
||||
|
||||
def _aggressive_limit_price(
|
||||
self,
|
||||
|
||||
@@ -28,6 +28,7 @@ from modules.trading.position_sizing import (
|
||||
calc_lots_by_risk,
|
||||
calc_margin_usage_pct,
|
||||
cap_lots_for_margin_budget,
|
||||
current_margin_usage_pct,
|
||||
calc_order_tick_metrics,
|
||||
normalize_sizing_mode,
|
||||
)
|
||||
@@ -1973,6 +1974,11 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
).fetchone()["n"]
|
||||
if int(pending_n or 0) > 0:
|
||||
return False, "已有监控中的加仓腿"
|
||||
cap_err = _validate_roll_margin(
|
||||
conn, mode=mode, mon=mon, preview=preview, capital=_capital(conn),
|
||||
)
|
||||
if cap_err:
|
||||
return False, cap_err
|
||||
try:
|
||||
result = execute_order(
|
||||
conn,
|
||||
@@ -4508,8 +4514,15 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
conn = get_db()
|
||||
init_strategy_tables(conn)
|
||||
mode = get_trading_mode(get_setting)
|
||||
capital = _capital(conn)
|
||||
preview2, merr = _apply_roll_margin_cap(
|
||||
preview, conn=conn, mode=mode, mon=mon, capital=capital,
|
||||
)
|
||||
if merr:
|
||||
conn.close()
|
||||
return False, merr
|
||||
ok, msg = _commit_roll_fill(
|
||||
conn, mon=mon, preview=preview, add_mode=leg.get("add_mode") or ADD_MODE_MARKET,
|
||||
conn, mon=mon, preview=preview2, add_mode=leg.get("add_mode") or ADD_MODE_MARKET,
|
||||
mode=mode, pending_leg_id=int(leg["id"]),
|
||||
)
|
||||
conn.close()
|
||||
@@ -4536,6 +4549,20 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
|
||||
app._check_roll_monitors = _check_roll_monitors
|
||||
|
||||
def _validate_roll_margin(
|
||||
conn,
|
||||
*,
|
||||
mode: str,
|
||||
mon: dict,
|
||||
preview: dict,
|
||||
capital: float,
|
||||
) -> Optional[str]:
|
||||
"""滚仓提交前:用柜台保证金复核是否超过滚仓上限。"""
|
||||
_, merr = _apply_roll_margin_cap(
|
||||
preview, conn=conn, mode=mode, mon=mon, capital=capital,
|
||||
)
|
||||
return merr
|
||||
|
||||
def _apply_roll_margin_cap(
|
||||
preview: dict,
|
||||
*,
|
||||
@@ -4558,9 +4585,21 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
mult = int(get_contract_spec(sym).get("mult") or 1)
|
||||
roll_pct = get_roll_max_margin_pct(get_setting)
|
||||
add_lots = int(preview.get("add_lots") or 0)
|
||||
positions = _positions_for_monitor_restore(mode, allow_ctp=False)
|
||||
positions = _positions_for_monitor_restore(mode, allow_ctp=True)
|
||||
acct_margin = 0.0
|
||||
if ctp_status(mode).get("connected"):
|
||||
acct_margin = float(ctp_account_margin_used(mode) or 0)
|
||||
current_usage = current_margin_usage_pct(
|
||||
positions, capital, trading_mode=mode, account_margin_used=acct_margin,
|
||||
)
|
||||
if current_usage > roll_pct:
|
||||
return preview, (
|
||||
f"当前保证金占用 {current_usage:g}% 已超过滚仓上限 {roll_pct:g}%,"
|
||||
"请先减仓或提高上限后再加仓"
|
||||
)
|
||||
capped, usage = cap_lots_for_margin_budget(
|
||||
positions, capital, sym, direction, price, add_lots, roll_pct, trading_mode=mode,
|
||||
positions, capital, sym, direction, price, add_lots, roll_pct,
|
||||
trading_mode=mode, account_margin_used=acct_margin,
|
||||
)
|
||||
if capped < 1:
|
||||
return preview, f"滚仓后保证金占用将超过上限 {roll_pct:g}%"
|
||||
|
||||
@@ -233,6 +233,22 @@ def calc_margin_usage_pct(
|
||||
return round(total / cap * 100.0, 2)
|
||||
|
||||
|
||||
def current_margin_usage_pct(
|
||||
positions: list[dict],
|
||||
capital: float,
|
||||
*,
|
||||
trading_mode: str | None = None,
|
||||
account_margin_used: float | None = None,
|
||||
) -> float:
|
||||
"""当前保证金占权益(%);优先采用柜台账户占用,避免本地估算偏低。"""
|
||||
cap = float(capital or 0)
|
||||
usage = calc_margin_usage_pct(positions, cap, trading_mode=trading_mode)
|
||||
acct = float(account_margin_used or 0)
|
||||
if cap > 0 and acct > 0:
|
||||
usage = max(usage, round(acct / cap * 100.0, 2))
|
||||
return usage
|
||||
|
||||
|
||||
def cap_lots_for_margin_budget(
|
||||
positions: list[dict],
|
||||
capital: float,
|
||||
@@ -242,29 +258,28 @@ def cap_lots_for_margin_budget(
|
||||
desired_lots: int,
|
||||
max_margin_pct: float,
|
||||
trading_mode: str | None = None,
|
||||
account_margin_used: float | None = None,
|
||||
) -> tuple[int, float]:
|
||||
"""在保证金上限内,返回可加仓手数及占用比例。"""
|
||||
cap = float(capital or 0)
|
||||
desired = max(0, int(desired_lots or 0))
|
||||
if desired <= 0:
|
||||
return 0, calc_margin_usage_pct(positions, capital, trading_mode=trading_mode)
|
||||
for lots in range(desired, 0, -1):
|
||||
usage = calc_margin_usage_pct(
|
||||
positions,
|
||||
capital,
|
||||
extra_symbol=symbol,
|
||||
extra_lots=lots,
|
||||
extra_price=price,
|
||||
extra_direction=direction,
|
||||
trading_mode=trading_mode,
|
||||
baseline_usage = current_margin_usage_pct(
|
||||
positions, cap, trading_mode=trading_mode, account_margin_used=account_margin_used,
|
||||
)
|
||||
if desired <= 0:
|
||||
return 0, baseline_usage
|
||||
if cap <= 0:
|
||||
return 0, baseline_usage
|
||||
baseline_margin = baseline_usage / 100.0 * cap
|
||||
per_lot = 0.0
|
||||
for lots in range(desired, 0, -1):
|
||||
per_lot, _, _ = margin_one_lot(
|
||||
symbol, price, direction=direction, trading_mode=trading_mode,
|
||||
)
|
||||
if per_lot <= 0:
|
||||
spec = get_contract_spec(symbol)
|
||||
per_lot = price * spec["mult"] * spec["margin_rate"]
|
||||
usage = round((baseline_margin + per_lot * lots) / cap * 100.0, 2)
|
||||
if usage <= max_margin_pct:
|
||||
return lots, usage
|
||||
return 0, calc_margin_usage_pct(
|
||||
positions,
|
||||
capital,
|
||||
extra_symbol=symbol,
|
||||
extra_lots=desired,
|
||||
extra_price=price,
|
||||
extra_direction=direction,
|
||||
trading_mode=trading_mode,
|
||||
)
|
||||
return 0, round((baseline_margin + per_lot * desired) / cap * 100.0, 2)
|
||||
|
||||
Reference in New Issue
Block a user