feat: 非交易时段禁开仓、移动保本与交易结果分类。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+99
-15
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user