feat: 计仓改为固定手数/固定金额,推荐过滤与CTP保证金,下单与持仓UI优化

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-25 15:31:34 +08:00
parent c302e1f3ca
commit 9772f3d986
11 changed files with 387 additions and 119 deletions
+55 -26
View File
@@ -14,9 +14,10 @@ from fee_specs import calc_fee_breakdown
from kline_stream import sse_format
from market_sessions import is_trading_session
from position_sizing import (
MODE_AMOUNT,
MODE_FIXED,
MODE_RISK,
DEFAULT_MAX_ORDER_LOTS,
calc_lots_by_amount,
calc_lots_by_risk,
calc_margin_usage_pct,
calc_order_tick_metrics,
@@ -62,6 +63,8 @@ from trading_context import (
TRADING_MODE_LIVE,
TRADING_MODE_SIM,
get_account_capital,
get_fixed_amount,
get_fixed_lots,
get_max_margin_pct,
get_risk_percent,
get_sizing_mode,
@@ -90,6 +93,23 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
"""注册交易相关路由。"""
_nav = require_nav
def _sizing_mode_label(mode: str) -> str:
m = normalize_sizing_mode(mode)
if m == MODE_AMOUNT:
return "固定金额"
return "固定手数"
def _recommend_payload(conn) -> dict:
mode = get_trading_mode(get_setting)
return recommend_payload(
conn,
live_capital=_capital(conn),
max_margin_pct=get_max_margin_pct(get_setting),
trading_mode=mode,
sizing_mode=get_sizing_mode(get_setting),
fixed_lots=get_fixed_lots(get_setting),
)
def _settings_dict() -> dict:
return {
"trading_mode": get_trading_mode(get_setting),
@@ -847,11 +867,15 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
)
except Exception as exc:
logger.warning("positions recommend refresh failed: %s", exc)
rec_cache = recommend_payload(conn, live_capital=capital, max_margin_pct=max_pct)
rec_cache = _recommend_payload(conn)
if not rec_cache.get("rows") and capital > 0:
try:
from product_recommend import list_product_recommendations
from recommend_store import enrich_recommend_rows, filter_affordable_recommendations
from recommend_store import (
enrich_recommend_rows,
filter_affordable_recommendations,
filter_recommend_by_sizing,
)
live_rows = filter_affordable_recommendations(
list_product_recommendations(
@@ -859,8 +883,13 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
)
)
if live_rows:
rec_cache["rows"] = enrich_recommend_rows(
live_rows, capital, max_margin_pct=max_pct,
enriched = enrich_recommend_rows(
live_rows, capital, max_margin_pct=max_pct, trading_mode=mode,
)
rec_cache["rows"] = filter_recommend_by_sizing(
enriched,
sizing_mode=sizing,
fixed_lots=get_fixed_lots(get_setting),
)
rec_cache["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
except Exception as exc:
@@ -877,7 +906,9 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
monitor_count=monitor_count,
roll_count=roll_count,
sizing_mode=sizing,
sizing_mode_label="以损定仓" if sizing == MODE_RISK else "固定张数",
sizing_mode_label=_sizing_mode_label(sizing),
fixed_lots=get_fixed_lots(get_setting),
fixed_amount=get_fixed_amount(get_setting),
risk_percent=get_risk_percent(get_setting),
max_margin_pct=get_max_margin_pct(get_setting),
recommend_rows=rec_cache.get("rows") or [],
@@ -1262,13 +1293,15 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
conn.close()
sizing = get_sizing_mode(get_setting)
margin_pct = get_max_margin_pct(get_setting)
if sizing == MODE_RISK:
lots, err = calc_lots_by_risk(
entry, sl, direction, capital, get_risk_percent(get_setting), sym,
max_margin_pct=margin_pct,
if sizing == MODE_AMOUNT:
lots, err = calc_lots_by_amount(
entry, sl, direction, get_fixed_amount(get_setting), sym,
capital=capital, max_margin_pct=margin_pct,
)
if err:
return jsonify({"ok": False, "error": err}), 400
elif sizing == MODE_FIXED:
lots = get_fixed_lots(get_setting)
else:
try:
lots = max(1, int(d.get("lots") or 1))
@@ -1322,19 +1355,21 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
return jsonify({"ok": False, "error": "CTP 连接中,请稍候再下单"}), 400
return jsonify({"ok": False, "error": "请先连接 CTP"}), 400
sizing = get_sizing_mode(get_setting)
if offset.startswith("open") and sizing == MODE_RISK:
if offset.startswith("open") and sizing == MODE_AMOUNT:
sl = float(d.get("stop_loss") or 0)
if sl <= 0:
conn.close()
return jsonify({"ok": False, "error": "以损定仓模式须填写止损价"}), 400
lots_calc, err = calc_lots_by_risk(
price, sl, direction, _capital(conn), get_risk_percent(get_setting), sym,
max_margin_pct=get_max_margin_pct(get_setting),
return jsonify({"ok": False, "error": "固定金额模式须填写止损价"}), 400
lots_calc, err = calc_lots_by_amount(
price, sl, direction, get_fixed_amount(get_setting), sym,
capital=_capital(conn), max_margin_pct=get_max_margin_pct(get_setting),
)
if err:
conn.close()
return jsonify({"ok": False, "error": err}), 400
lots = lots_calc or lots
elif offset.startswith("open") and sizing == MODE_FIXED:
lots = get_fixed_lots(get_setting)
margin_pct = get_max_margin_pct(get_setting)
usage = calc_margin_usage_pct(
_ctp_positions(mode),
@@ -1482,11 +1517,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
"""只读数据库缓存,不在请求时拉行情。"""
conn = get_db()
try:
payload = recommend_payload(
conn,
live_capital=_capital(conn),
max_margin_pct=get_max_margin_pct(get_setting),
)
payload = _recommend_payload(conn)
return jsonify({"ok": True, **payload})
finally:
conn.close()
@@ -1501,11 +1532,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
try:
conn = get_db()
try:
payload = recommend_payload(
conn,
live_capital=_capital(conn),
max_margin_pct=get_max_margin_pct(get_setting),
)
payload = _recommend_payload(conn)
finally:
conn.close()
yield sse_format("recommend", {"ok": True, **payload})
@@ -1542,7 +1569,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
max_margin_pct=get_max_margin_pct(get_setting),
)
max_pct = get_max_margin_pct(get_setting)
payload = recommend_payload(conn, live_capital=capital, max_margin_pct=max_pct)
payload = _recommend_payload(conn)
recommend_hub.broadcast("recommend", {"ok": True, **payload})
return jsonify({"ok": True, "count": len(rows), **payload})
finally:
@@ -1876,6 +1903,8 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
init_tables_fn=_init_tables,
get_mode_fn=lambda: get_trading_mode(get_setting),
get_max_margin_pct_fn=lambda: get_max_margin_pct(get_setting),
get_sizing_mode_fn=lambda: get_sizing_mode(get_setting),
get_fixed_lots_fn=lambda: get_fixed_lots(get_setting),
)
start_ctp_reconnect_worker(get_mode_fn=lambda: get_trading_mode(get_setting))
start_ctp_premarket_connect_worker(get_mode_fn=lambda: get_trading_mode(get_setting))