fix: 开仓时间读CTP OpenDate,止盈止损持久化且重启不丢失

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-25 15:05:58 +08:00
parent 7daed9bd3a
commit 4d60b958ce
4 changed files with 130 additions and 38 deletions
+95 -35
View File
@@ -165,32 +165,31 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
ths = _ctp_pos_to_ths_code(p)
if not ths:
continue
if _find_active_monitor(conn, ths, direction):
existing = _find_active_monitor(conn, ths, direction)
if existing:
_sync_monitor_lots_from_ctp(
conn, int(existing["id"]), ths, direction, mode, ctp=p,
)
continue
codes = ths_to_codes(ths) or {}
now_s = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
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, initial_stop_loss, trailing_be,
open_time, monitor_type, status
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?, 'active')""",
(
ths,
codes.get("name", ths) if codes else ths,
codes.get("market_code", "") if codes else "",
direction,
lots,
float(p.get("avg_price") or 0),
None,
None,
None,
0,
now_s,
"ctp_sync",
),
sl, tp, trailing_be, initial_sl = _restore_sl_tp_from_closed(conn, ths, direction)
ctp_open = (p.get("open_time") or "").strip()
mid = _upsert_open_monitor(
conn,
sym=ths,
direction=direction,
lots=lots,
price=float(p.get("avg_price") or 0),
sl=sl,
tp=tp,
trailing_be=trailing_be,
ctp_open_time=ctp_open or None,
monitor_type="ctp_sync",
)
if initial_sl is not None and sl is not None:
conn.execute(
"UPDATE trade_order_monitors SET initial_stop_loss=? WHERE id=?",
(initial_sl, mid),
)
def _match_ctp_symbol(ctp_sym: str, ths: str) -> bool:
a = (ctp_sym or "").lower()
@@ -216,10 +215,36 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
def _holding_duration(open_time: str, now_iso: str) -> str:
try:
from app import calc_holding_duration
return calc_holding_duration(open_time, now_iso)
open_s = (open_time or "").strip().replace("T", " ")[:19]
now_s = (now_iso or "").strip().replace("T", " ")[:19]
if not open_s or not now_s:
return ""
return calc_holding_duration(open_s, now_s)
except Exception:
return ""
def _restore_sl_tp_from_closed(conn, sym: str, direction: str) -> tuple:
"""重启后从最近关闭的同品种监控恢复止盈止损。"""
direction = (direction or "long").strip().lower()
for r in conn.execute(
"SELECT symbol, direction, stop_loss, take_profit, trailing_be, initial_stop_loss "
"FROM trade_order_monitors WHERE status='closed' ORDER BY id DESC LIMIT 80"
).fetchall():
row = dict(r)
if (row.get("direction") or "long") != direction:
continue
if not _match_ctp_symbol(sym, row.get("symbol") or ""):
continue
if row.get("stop_loss") is None and row.get("take_profit") is None:
continue
return (
row.get("stop_loss"),
row.get("take_profit"),
int(row.get("trailing_be") or 0),
row.get("initial_stop_loss"),
)
return None, None, 0, None
def _ctp_position_keys(mode: str) -> set[tuple[str, str]]:
keys: set[tuple[str, str]] = set()
for p in _ctp_positions(mode):
@@ -362,6 +387,8 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
sl,
tp,
trailing_be: int,
ctp_open_time: Optional[str] = None,
monitor_type: str = "manual",
) -> int:
ensure_monitor_order_columns(conn)
codes = ths_to_codes(sym) or {}
@@ -372,8 +399,19 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
if existing:
mid = int(existing["id"])
initial_sl = existing.get("initial_stop_loss")
if sl_f is None:
sl_f = float(existing["stop_loss"]) if existing.get("stop_loss") is not None else None
if tp_f is None:
tp_f = float(existing["take_profit"]) if existing.get("take_profit") is not None else None
if sl_f is not None and initial_sl is None:
initial_sl = sl_f
if not trailing_be:
trailing_be = int(existing.get("trailing_be") or 0)
open_time_val = existing.get("open_time") or now_s
if ctp_open_time:
prev = (open_time_val or "")[:19]
if not prev or ctp_open_time < prev:
open_time_val = ctp_open_time
conn.execute(
"""UPDATE trade_order_monitors SET
symbol=?, symbol_name=?, market_code=?, lots=?, entry_price=?,
@@ -389,11 +427,12 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
tp_f,
initial_sl,
trailing_be,
now_s,
open_time_val,
mid,
),
)
else:
open_time_val = ctp_open_time or now_s
conn.execute(
"""INSERT INTO trade_order_monitors (
symbol, symbol_name, market_code, direction, lots, entry_price,
@@ -411,25 +450,45 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
tp_f,
sl_f,
trailing_be,
now_s,
"manual",
open_time_val,
monitor_type,
),
)
mid = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
_close_duplicate_monitors(conn, sym, direction, mid)
return mid
def _sync_monitor_lots_from_ctp(conn, mid: int, sym: str, direction: str, mode: str) -> None:
for p in _ctp_positions(mode):
if int(p.get("lots") or 0) <= 0:
def _sync_monitor_lots_from_ctp(
conn, mid: int, sym: str, direction: str, mode: str, *, ctp: Optional[dict] = None,
) -> None:
positions = [ctp] if ctp else _ctp_positions(mode, refresh_if_empty=False, refresh_margin=False)
for p in positions:
if not p or int(p.get("lots") or 0) <= 0:
continue
if (p.get("direction") or "long") != direction:
continue
if not _match_ctp_symbol(p.get("symbol") or "", sym):
continue
ctp_open = (p.get("open_time") or "").strip() or None
row = conn.execute(
"SELECT open_time FROM trade_order_monitors WHERE id=?", (mid,),
).fetchone()
db_open = (row["open_time"] or "").strip() if row else ""
open_time_val = db_open or ctp_open
if ctp_open and db_open:
if ctp_open < db_open[:19]:
open_time_val = ctp_open
elif ctp_open:
open_time_val = ctp_open
conn.execute(
"UPDATE trade_order_monitors SET lots=?, entry_price=? WHERE id=?",
(int(p.get("lots") or 0), float(p.get("avg_price") or 0), mid),
"""UPDATE trade_order_monitors SET lots=?, entry_price=?,
open_time=? WHERE id=?""",
(
int(p.get("lots") or 0),
float(p.get("avg_price") or 0),
open_time_val,
mid,
),
)
return
@@ -470,7 +529,8 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
tick = calc_order_tick_metrics(sym, lots, entry)
sl = float(mon["stop_loss"]) if mon and mon.get("stop_loss") is not None else None
tp = 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 ""
ctp_open = (ctp.get("open_time") or "").strip() if ctp else ""
open_time = ctp_open or ((mon.get("open_time") or "") if mon else "")
holding = _holding_duration(open_time, now_iso) if open_time else ""
mark = None
@@ -627,7 +687,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
if ctp and mon:
_sync_monitor_lots_from_ctp(
conn, int(mon["id"]), mon.get("symbol") or "",
mon.get("direction") or "long", mode,
mon.get("direction") or "long", mode, ctp=ctp,
)
mon = _find_active_monitor(conn, mon.get("symbol") or "", mon.get("direction") or "long") or mon
try: