fix: 交易安全审计修复 — 补偿平仓、中控同步、滚仓/趋势防护
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -244,7 +244,7 @@ def _row(cfg, row) -> dict:
|
||||
return cfg["row_to_dict"](row)
|
||||
|
||||
|
||||
def precheck_trend_start(cfg: dict, conn) -> tuple[bool, str]:
|
||||
def precheck_trend_start(cfg: dict, conn, *, symbol: str = "", direction: str = "long") -> tuple[bool, str]:
|
||||
m = _m(cfg)
|
||||
mode = getattr(m, "POSITION_SIZING_MODE", None) or "risk"
|
||||
try:
|
||||
@@ -255,9 +255,41 @@ def precheck_trend_start(cfg: dict, conn) -> tuple[bool, str]:
|
||||
return False, src_msg
|
||||
except Exception:
|
||||
pass
|
||||
now = m.app_now()
|
||||
if not m.trading_day_reset_allows_new_open(now):
|
||||
return False, f"北京时间 {cfg['reset_hour']}:00 前不允许持仓"
|
||||
sym = (symbol or "").strip()
|
||||
dir_l = (direction or "long").strip().lower()
|
||||
if sym and dir_l in ("long", "short") and hasattr(m, "precheck_risk"):
|
||||
ok_risk, risk_msg = m.precheck_risk(conn, sym, dir_l)
|
||||
if not ok_risk:
|
||||
return False, risk_msg
|
||||
else:
|
||||
now = m.app_now()
|
||||
if not m.trading_day_reset_allows_new_open(now):
|
||||
return False, f"北京时间 {cfg['reset_hour']}:00 前不允许持仓"
|
||||
from lib.trade.account_risk_lib import account_risk_blocks_trading, position_limit_reached
|
||||
|
||||
ok_risk, risk_reason = account_risk_blocks_trading(
|
||||
conn,
|
||||
trading_day=m.get_trading_day(now),
|
||||
now=now,
|
||||
fmt_local_ms=getattr(m, "ms_to_app_local_str", lambda _x: ""),
|
||||
)
|
||||
if not ok_risk:
|
||||
return False, risk_reason
|
||||
reached, active_count, mx = position_limit_reached(
|
||||
conn, max_active_positions=cfg["max_active_positions"]
|
||||
)
|
||||
if reached:
|
||||
return False, f"已达最大持仓数({active_count}/{mx})"
|
||||
from lib.trade.daily_open_limit_lib import check_daily_open_hard_limit
|
||||
|
||||
ok_daily, daily_reason, _opens = check_daily_open_hard_limit(
|
||||
conn,
|
||||
m.get_trading_day(now),
|
||||
getattr(m, "DAILY_OPEN_HARD_LIMIT", 0),
|
||||
cfg["reset_hour"],
|
||||
)
|
||||
if not ok_daily:
|
||||
return False, daily_reason
|
||||
active = m.get_active_position_count(conn)
|
||||
if active >= cfg["max_active_positions"]:
|
||||
return (
|
||||
@@ -1604,22 +1636,27 @@ def register_trend_routes(app: Flask, cfg: dict) -> None:
|
||||
def preview_trend_pullback():
|
||||
conn = get_db()
|
||||
init_strategy_tables(conn)
|
||||
okp, msg = precheck_trend_start(cfg, conn)
|
||||
if not okp:
|
||||
conn.close()
|
||||
flash(msg)
|
||||
return _redirect_trend()
|
||||
m = _m(cfg)
|
||||
ok_live, reason = m.ensure_exchange_live_ready()
|
||||
if not ok_live:
|
||||
conn.close()
|
||||
flash(reason)
|
||||
return _redirect_trend()
|
||||
payload, err = parse_trend_plan(cfg, request.form)
|
||||
if err:
|
||||
conn.close()
|
||||
flash(err)
|
||||
return _redirect_trend()
|
||||
okp, msg = precheck_trend_start(
|
||||
cfg,
|
||||
conn,
|
||||
symbol=str(payload.get("symbol") or ""),
|
||||
direction=str(payload.get("direction") or "long"),
|
||||
)
|
||||
if not okp:
|
||||
conn.close()
|
||||
flash(msg)
|
||||
return _redirect_trend()
|
||||
ok_live, reason = m.ensure_exchange_live_ready()
|
||||
if not ok_live:
|
||||
conn.close()
|
||||
flash(reason)
|
||||
return _redirect_trend()
|
||||
pid = str(uuid.uuid4())
|
||||
exp_ms = int(time.time() * 1000) + cfg["preview_ttl"] * 1000
|
||||
created = m.app_now_str()
|
||||
@@ -1678,7 +1715,12 @@ def register_trend_routes(app: Flask, cfg: dict) -> None:
|
||||
conn.close()
|
||||
flash("预览已过期或不存在,请重新生成预览")
|
||||
return _redirect_trend()
|
||||
okp, msg = precheck_trend_start(cfg, conn)
|
||||
okp, msg = precheck_trend_start(
|
||||
cfg,
|
||||
conn,
|
||||
symbol=str(pr["symbol"] or ""),
|
||||
direction=str(pr["direction"] or "long"),
|
||||
)
|
||||
if not okp:
|
||||
conn.close()
|
||||
flash(msg)
|
||||
@@ -1718,7 +1760,18 @@ def register_trend_routes(app: Flask, cfg: dict) -> None:
|
||||
exchange_symbol, direction, first_amt, leverage, stop_loss=None, take_profit=None
|
||||
)
|
||||
fill1 = m.resolve_order_entry_price(o1, exchange_symbol, live_price)
|
||||
trend_refresh_stop_only(cfg, exchange_symbol, direction, stop_loss)
|
||||
try:
|
||||
trend_refresh_stop_only(cfg, exchange_symbol, direction, stop_loss)
|
||||
except Exception as sl_err:
|
||||
from lib.strategy.strategy_trend_exchange import cancel_symbol_orders, trend_market_close
|
||||
|
||||
try:
|
||||
pos_qty = m.get_live_position_contracts(exchange_symbol, direction) or first_amt
|
||||
trend_market_close(cfg, exchange_symbol, direction, float(pos_qty), leverage)
|
||||
cancel_symbol_orders(cfg, exchange_symbol)
|
||||
except Exception as close_err:
|
||||
print(f"[trend_start] compensating close failed: {close_err}", flush=True)
|
||||
raise sl_err
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
fe = getattr(m, "friendly_exchange_error", lambda x, **k: str(x))
|
||||
|
||||
Reference in New Issue
Block a user