feat: 非交易时段禁开仓、移动保本与交易结果分类。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-25 13:33:17 +08:00
parent 598a1407e1
commit f31164076f
9 changed files with 387 additions and 49 deletions
+99 -15
View File
@@ -38,6 +38,7 @@ from sl_tp_guard import (
place_monitor_exit_orders,
reconcile_monitors_without_position,
start_sl_tp_guard_worker,
write_manual_close_trade_log,
)
from risk.account_risk_lib import (
assert_can_open,
@@ -61,6 +62,7 @@ from trading_context import (
get_max_margin_pct,
get_risk_percent,
get_sizing_mode,
get_trailing_be_tick_buffer,
get_trading_mode,
trading_mode_label,
)
@@ -375,6 +377,8 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
"tick_size": tick.get("tick_size"),
"can_close": True,
"pending_orders": pending_for_row,
"trailing_be": bool(mon.get("trailing_be")) if mon else False,
"trailing_r_locked": int(mon.get("trailing_r_locked") or 0) if mon else 0,
})
return rows
@@ -535,19 +539,35 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
break
codes = ths_to_codes(sym)
now_s = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if "trailing_be" in d:
trailing_be = 1 if d.get("trailing_be") else 0
elif mon:
trailing_be = int(mon.get("trailing_be") or 0)
else:
trailing_be = 0
ensure_monitor_order_columns(conn)
if mon:
initial_sl = mon.get("initial_stop_loss")
if sl is not None and initial_sl is None:
initial_sl = sl
conn.execute(
"""UPDATE trade_order_monitors SET stop_loss=?, take_profit=?, lots=?, entry_price=?
"""UPDATE trade_order_monitors SET stop_loss=?, take_profit=?, lots=?, entry_price=?,
initial_stop_loss=?, trailing_be=?
WHERE id=?""",
(sl, tp, lots, entry or mon.get("entry_price"), mon["id"]),
(
sl, tp, lots, entry or mon.get("entry_price"),
initial_sl, trailing_be,
mon["id"],
),
)
mid = mon["id"]
else:
conn.execute(
"""INSERT INTO trade_order_monitors (
symbol, symbol_name, market_code, direction, lots, entry_price,
stop_loss, take_profit, open_time, monitor_type, status
) VALUES (?,?,?,?,?,?,?,?,?,?, 'active')""",
stop_loss, take_profit, initial_stop_loss, trailing_be,
open_time, monitor_type, status
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?, 'active')""",
(
sym,
codes.get("name", sym) if codes else sym,
@@ -557,6 +577,8 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
entry,
sl,
tp,
sl,
trailing_be,
now_s,
"manual",
),
@@ -667,21 +689,67 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
conn.close()
return jsonify({"ok": False, "error": "品种或价格无效"}), 400
offset = "close_long" if direction == "long" else "close_short"
capital = _capital(conn)
mon = None
mid = int(d.get("monitor_id") or 0)
if mid:
row = conn.execute(
"SELECT * FROM trade_order_monitors WHERE id=? AND status='active'",
(mid,),
).fetchone()
if row:
mon = dict(row)
if not mon:
for r in conn.execute(
"SELECT * FROM trade_order_monitors WHERE status='active'"
).fetchall():
row = dict(r)
if row.get("direction") != direction:
continue
if _match_ctp_symbol(sym, row.get("symbol") or ""):
mon = row
mid = int(row["id"])
break
entry = float(mon.get("entry_price") or 0) if mon else 0.0
if entry <= 0:
for p in _ctp_positions(mode):
if int(p.get("lots") or 0) <= 0:
continue
if (p.get("direction") or "long") != direction:
continue
if _match_ctp_symbol(p.get("symbol") or "", sym):
entry = float(p.get("avg_price") or price)
break
try:
execute_order(
conn, mode=mode, offset=offset, symbol=sym, direction=direction,
lots=lots, price=price, settings=_settings_dict(),
order_type="market",
)
if source == "program":
mid = int(d.get("monitor_id") or 0)
if mid:
conn.execute(
"UPDATE trade_order_monitors SET status='closed' WHERE id=?",
(mid,),
)
write_manual_close_trade_log(
conn,
mon,
symbol=sym,
direction=direction,
lots=lots,
close_price=price,
entry_price=entry or price,
trading_mode=mode,
capital=capital,
stop_loss=float(mon["stop_loss"]) if mon and mon.get("stop_loss") is not None else None,
take_profit=float(mon["take_profit"]) if mon and mon.get("take_profit") is not None else None,
open_time=(mon.get("open_time") or "") if mon else "",
symbol_name=(mon.get("symbol_name") or "") if mon else "",
market_code=(mon.get("market_code") or "") if mon else "",
)
if mid:
conn.execute(
"UPDATE trade_order_monitors SET status='closed' WHERE id=?",
(mid,),
)
conn.commit()
conn.close()
return jsonify({"ok": True})
return jsonify({"ok": True, "message": "已平仓并记入交易记录(手动平仓)"})
except ValueError as exc:
conn.close()
return jsonify({"ok": False, "error": str(exc)}), 400
@@ -830,6 +898,12 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
mode = get_trading_mode(get_setting)
if offset.startswith("open"):
_sync_trade_monitors_with_ctp(conn, mode)
if not is_trading_session():
conn.close()
return jsonify({"ok": False, "error": "不在交易时间段"}), 403
if d.get("trailing_be") and not d.get("stop_loss"):
conn.close()
return jsonify({"ok": False, "error": "开启移动保本须填写止损价"}), 400
err = assert_can_open(conn, active_count=_effective_active_position_count(conn, mode))
if err:
conn.close()
@@ -886,9 +960,13 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
settings=_settings_dict(),
order_type=order_type,
)
if offset.startswith("open") and d.get("trailing_be") and not d.get("stop_loss"):
conn.close()
return jsonify({"ok": False, "error": "开启移动保本须填写止损价"}), 400
if offset.startswith("open"):
sl = d.get("stop_loss")
tp = d.get("take_profit")
trailing_be = 1 if d.get("trailing_be") else 0
import time
time.sleep(2.0)
actual_lots = lots
@@ -904,11 +982,14 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
break
if has_pos:
codes = ths_to_codes(sym)
sl_f = float(sl) if sl else None
ensure_monitor_order_columns(conn)
conn.execute(
"""INSERT INTO trade_order_monitors (
symbol, symbol_name, market_code, direction, lots, entry_price,
stop_loss, take_profit, open_time, monitor_type, status
) VALUES (?,?,?,?,?,?,?,?,?,?, 'active')""",
stop_loss, take_profit, initial_stop_loss, trailing_be,
open_time, monitor_type, status
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?, 'active')""",
(
sym,
codes.get("name", sym) if codes else sym,
@@ -916,8 +997,10 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
direction,
actual_lots,
price,
float(sl) if sl else None,
sl_f,
float(tp) if tp else None,
sl_f,
trailing_be,
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"manual",
),
@@ -1414,6 +1497,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
get_mode_fn=lambda: get_trading_mode(get_setting),
init_tables_fn=_init_tables,
get_capital_fn=_capital,
get_be_tick_buffer_fn=lambda: get_trailing_be_tick_buffer(get_setting),
notify_fn=send_wechat_msg,
interval=1,
)