fix: 开仓时间读CTP OpenDate,止盈止损持久化且重启不丢失
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+95
-35
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user