fix(gate_bot): exclude active trend plans from orphan position warning
Trend pullback plans manage positions before order_monitors handoff; treat them as covered and add a pre-deploy DB backup script. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3632,8 +3632,36 @@ def _active_monitor_position_keys(active_orders):
|
|||||||
return covered
|
return covered
|
||||||
|
|
||||||
|
|
||||||
def collect_orphan_exchange_positions(active_orders):
|
def _active_trend_plan_position_keys(conn):
|
||||||
"""交易所有持仓但未匹配 order_monitors.status=active(与中控 Agent 持仓对齐提示)。"""
|
"""运行中的趋势回调计划已开仓时,持仓由计划表管理而非 order_monitors。"""
|
||||||
|
covered = set()
|
||||||
|
if conn is None:
|
||||||
|
return covered
|
||||||
|
try:
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT symbol, exchange_symbol, direction FROM trend_pullback_plans "
|
||||||
|
"WHERE status='active' AND COALESCE(first_order_done, 0) != 0"
|
||||||
|
).fetchall()
|
||||||
|
except Exception:
|
||||||
|
return covered
|
||||||
|
for r in rows:
|
||||||
|
sym = (r["symbol"] or "").strip()
|
||||||
|
ex = (r["exchange_symbol"] or normalize_exchange_symbol(sym)).strip()
|
||||||
|
direction = (r["direction"] or "long").lower()
|
||||||
|
for s in (ex, sym, _unified_symbol_for_match(ex), _unified_symbol_for_match(sym)):
|
||||||
|
if s:
|
||||||
|
covered.add((s, direction))
|
||||||
|
return covered
|
||||||
|
|
||||||
|
|
||||||
|
def _strategy_managed_position_keys(active_orders, conn=None):
|
||||||
|
covered = _active_monitor_position_keys(active_orders)
|
||||||
|
covered |= _active_trend_plan_position_keys(conn)
|
||||||
|
return covered
|
||||||
|
|
||||||
|
|
||||||
|
def collect_orphan_exchange_positions(active_orders, conn=None):
|
||||||
|
"""交易所有持仓但未匹配本地策略/监控(order_monitors 或运行中趋势计划)。"""
|
||||||
from hub_position_metrics import (
|
from hub_position_metrics import (
|
||||||
parse_position_mark_price,
|
parse_position_mark_price,
|
||||||
position_contracts,
|
position_contracts,
|
||||||
@@ -3643,7 +3671,7 @@ def collect_orphan_exchange_positions(active_orders):
|
|||||||
rows = _fetch_all_swap_positions_live()
|
rows = _fetch_all_swap_positions_live()
|
||||||
if not rows:
|
if not rows:
|
||||||
return []
|
return []
|
||||||
covered = _active_monitor_position_keys(active_orders)
|
covered = _strategy_managed_position_keys(active_orders, conn)
|
||||||
orphans = []
|
orphans = []
|
||||||
seen = set()
|
seen = set()
|
||||||
for p in rows:
|
for p in rows:
|
||||||
@@ -5639,7 +5667,7 @@ def render_main_page(page="trade"):
|
|||||||
orphan_positions: list = []
|
orphan_positions: list = []
|
||||||
if page == "trade":
|
if page == "trade":
|
||||||
try:
|
try:
|
||||||
orphan_positions = collect_orphan_exchange_positions(order_list)
|
orphan_positions = collect_orphan_exchange_positions(order_list, conn)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"[render_main_page] orphan positions: {exc}")
|
print(f"[render_main_page] orphan positions: {exc}")
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -6160,6 +6188,19 @@ def api_order_relink_orphan():
|
|||||||
if active:
|
if active:
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify({"ok": True, "msg": "已有运行中的监控", "order_id": int(active["id"])})
|
return jsonify({"ok": True, "msg": "已有运行中的监控", "order_id": int(active["id"])})
|
||||||
|
trend_plan = conn.execute(
|
||||||
|
"SELECT id FROM trend_pullback_plans WHERE status='active' AND symbol=? AND direction=? "
|
||||||
|
"AND COALESCE(first_order_done, 0) != 0 LIMIT 1",
|
||||||
|
(symbol, direction),
|
||||||
|
).fetchone()
|
||||||
|
if trend_plan:
|
||||||
|
conn.close()
|
||||||
|
return jsonify(
|
||||||
|
{
|
||||||
|
"ok": False,
|
||||||
|
"msg": f"该持仓由趋势回调计划 #{int(trend_plan['id'])} 管理,请在策略页操作",
|
||||||
|
}
|
||||||
|
), 400
|
||||||
row = conn.execute(
|
row = conn.execute(
|
||||||
"""
|
"""
|
||||||
SELECT * FROM order_monitors
|
SELECT * FROM order_monitors
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""One-shot SQLite backup before code deploy. Reads DB_PATH from .env (default crypto.db)."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sqlite3
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PROJECT_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def _read_env_db_path() -> Path:
|
||||||
|
env_file = PROJECT_DIR / ".env"
|
||||||
|
default = PROJECT_DIR / "crypto.db"
|
||||||
|
if not env_file.is_file():
|
||||||
|
return default
|
||||||
|
for line in env_file.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
key, val = line.split("=", 1)
|
||||||
|
if key.strip() != "DB_PATH":
|
||||||
|
continue
|
||||||
|
val = val.strip().strip('"').strip("'")
|
||||||
|
p = Path(val)
|
||||||
|
return p if p.is_absolute() else PROJECT_DIR / p
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
db_path = _read_env_db_path()
|
||||||
|
if not db_path.is_file():
|
||||||
|
print(f"error: database not found: {db_path}")
|
||||||
|
return 1
|
||||||
|
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
dest_dir = PROJECT_DIR / "backups" / stamp
|
||||||
|
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
dest = dest_dir / db_path.name
|
||||||
|
try:
|
||||||
|
src = sqlite3.connect(str(db_path))
|
||||||
|
dst = sqlite3.connect(str(dest))
|
||||||
|
src.backup(dst)
|
||||||
|
dst.close()
|
||||||
|
src.close()
|
||||||
|
method = "sqlite3 backup"
|
||||||
|
except Exception:
|
||||||
|
shutil.copy2(db_path, dest)
|
||||||
|
method = "file copy"
|
||||||
|
manifest = dest_dir / "manifest.txt"
|
||||||
|
manifest.write_text(
|
||||||
|
"\n".join(
|
||||||
|
[
|
||||||
|
f"project_dir={PROJECT_DIR}",
|
||||||
|
f"source_db={db_path}",
|
||||||
|
f"backup_file={dest}",
|
||||||
|
f"method={method}",
|
||||||
|
f"created_at={stamp}",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
print(f"ok: {dest} ({method})")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user