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
|
return Offset.CLOSE
|
||||||
vol = int(getattr(pos, "volume", 0) or 0)
|
vol = int(getattr(pos, "volume", 0) or 0)
|
||||||
yd = int(getattr(pos, "yd_volume", 0) or 0)
|
yd = int(getattr(pos, "yd_volume", 0) or 0)
|
||||||
today = max(0, vol - yd)
|
td = max(0, vol - yd)
|
||||||
if today >= lots:
|
if td >= lots:
|
||||||
|
return Offset.CLOSETODAY
|
||||||
|
if yd >= lots:
|
||||||
|
# 夜盘仓在 vnpy 常记在 yd,日盘平昨易报「平昨仓位不足」→ 上期所/能源优先平今
|
||||||
|
if ex_u in ("SHFE", "INE"):
|
||||||
return Offset.CLOSETODAY
|
return Offset.CLOSETODAY
|
||||||
return Offset.CLOSEYESTERDAY
|
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(
|
def _aggressive_limit_price(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from modules.trading.position_sizing import (
|
|||||||
calc_lots_by_risk,
|
calc_lots_by_risk,
|
||||||
calc_margin_usage_pct,
|
calc_margin_usage_pct,
|
||||||
cap_lots_for_margin_budget,
|
cap_lots_for_margin_budget,
|
||||||
|
current_margin_usage_pct,
|
||||||
calc_order_tick_metrics,
|
calc_order_tick_metrics,
|
||||||
normalize_sizing_mode,
|
normalize_sizing_mode,
|
||||||
)
|
)
|
||||||
@@ -1973,6 +1974,11 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
).fetchone()["n"]
|
).fetchone()["n"]
|
||||||
if int(pending_n or 0) > 0:
|
if int(pending_n or 0) > 0:
|
||||||
return False, "已有监控中的加仓腿"
|
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:
|
try:
|
||||||
result = execute_order(
|
result = execute_order(
|
||||||
conn,
|
conn,
|
||||||
@@ -4508,8 +4514,15 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
conn = get_db()
|
conn = get_db()
|
||||||
init_strategy_tables(conn)
|
init_strategy_tables(conn)
|
||||||
mode = get_trading_mode(get_setting)
|
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(
|
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"]),
|
mode=mode, pending_leg_id=int(leg["id"]),
|
||||||
)
|
)
|
||||||
conn.close()
|
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
|
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(
|
def _apply_roll_margin_cap(
|
||||||
preview: dict,
|
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)
|
mult = int(get_contract_spec(sym).get("mult") or 1)
|
||||||
roll_pct = get_roll_max_margin_pct(get_setting)
|
roll_pct = get_roll_max_margin_pct(get_setting)
|
||||||
add_lots = int(preview.get("add_lots") or 0)
|
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(
|
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:
|
if capped < 1:
|
||||||
return preview, f"滚仓后保证金占用将超过上限 {roll_pct:g}%"
|
return preview, f"滚仓后保证金占用将超过上限 {roll_pct:g}%"
|
||||||
|
|||||||
@@ -233,6 +233,22 @@ def calc_margin_usage_pct(
|
|||||||
return round(total / cap * 100.0, 2)
|
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(
|
def cap_lots_for_margin_budget(
|
||||||
positions: list[dict],
|
positions: list[dict],
|
||||||
capital: float,
|
capital: float,
|
||||||
@@ -242,29 +258,28 @@ def cap_lots_for_margin_budget(
|
|||||||
desired_lots: int,
|
desired_lots: int,
|
||||||
max_margin_pct: float,
|
max_margin_pct: float,
|
||||||
trading_mode: str | None = None,
|
trading_mode: str | None = None,
|
||||||
|
account_margin_used: float | None = None,
|
||||||
) -> tuple[int, float]:
|
) -> tuple[int, float]:
|
||||||
"""在保证金上限内,返回可加仓手数及占用比例。"""
|
"""在保证金上限内,返回可加仓手数及占用比例。"""
|
||||||
|
cap = float(capital or 0)
|
||||||
desired = max(0, int(desired_lots or 0))
|
desired = max(0, int(desired_lots or 0))
|
||||||
if desired <= 0:
|
baseline_usage = current_margin_usage_pct(
|
||||||
return 0, calc_margin_usage_pct(positions, capital, trading_mode=trading_mode)
|
positions, cap, trading_mode=trading_mode, account_margin_used=account_margin_used,
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
|
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:
|
if usage <= max_margin_pct:
|
||||||
return lots, usage
|
return lots, usage
|
||||||
return 0, calc_margin_usage_pct(
|
return 0, round((baseline_margin + per_lot * desired) / cap * 100.0, 2)
|
||||||
positions,
|
|
||||||
capital,
|
|
||||||
extra_symbol=symbol,
|
|
||||||
extra_lots=desired,
|
|
||||||
extra_price=price,
|
|
||||||
extra_direction=direction,
|
|
||||||
trading_mode=trading_mode,
|
|
||||||
)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user