"""策略交易写入 trade_records 时的类型与复盘开仓类型标注。""" from __future__ import annotations from typing import Optional MONITOR_TYPE_TREND_PULLBACK = "趋势回调" MONITOR_TYPE_ROLL = "顺势加仓" ENTRY_REASON_TREND_PULLBACK = "趋势回调" ENTRY_REASON_ROLL = "顺势加仓" STRATEGY_ENTRY_REASON_OPTIONS = ( ENTRY_REASON_TREND_PULLBACK, ENTRY_REASON_ROLL, ) # 趋势回调保本移交下单监控:order_monitors.key_signal_type / 平仓备注 TREND_HANDOFF_KEY_SIGNAL = ENTRY_REASON_TREND_PULLBACK TREND_HANDOFF_TRADE_NOTE = "趋势回调计划" def handoff_trade_miss_reason(miss_reason, row) -> Optional[str]: """趋势保本移交的监控单平仓:交易记录备注带来源。""" if trend_plan_id_from_monitor_row(row) is None: return miss_reason base = (miss_reason or "").strip() if TREND_HANDOFF_TRADE_NOTE in base: return base or TREND_HANDOFF_TRADE_NOTE if base: return f"{TREND_HANDOFF_TRADE_NOTE};{base}" return TREND_HANDOFF_TRADE_NOTE def trend_plan_id_from_monitor_row(row) -> Optional[int]: if row is None: return None try: keys = row.keys() if hasattr(row, "keys") else [] except Exception: keys = [] if "trend_plan_id" not in keys or row["trend_plan_id"] in (None, ""): return None try: tid = int(row["trend_plan_id"]) return tid if tid > 0 else None except (TypeError, ValueError): return None def order_had_roll_fills(conn, order_monitor_id) -> bool: try: oid = int(order_monitor_id) except (TypeError, ValueError): return False if oid <= 0: return False try: row = conn.execute( """SELECT 1 FROM roll_legs l INNER JOIN roll_groups g ON g.id = l.roll_group_id WHERE g.order_monitor_id=? AND l.status='filled' LIMIT 1""", (oid,), ).fetchone() return row is not None except Exception: return False def _row_monitor_type(row, default_manual: str) -> str: if row is None: return default_manual try: keys = row.keys() if hasattr(row, "keys") else [] except Exception: keys = [] if "monitor_type" in keys: mt = (row["monitor_type"] or "").strip() if mt: return mt return default_manual def _row_key_signal_type(row) -> str: if row is None: return "" try: keys = row.keys() if hasattr(row, "keys") else [] except Exception: keys = [] if "key_signal_type" not in keys: return "" return (row["key_signal_type"] or "").strip() def order_monitor_source_type(row, *, default_manual: str = "下单监控") -> str: """展示/平仓记录:趋势保本移交单来源为「趋势回调」,非「下单监控」。""" if trend_plan_id_from_monitor_row(row) is not None: return MONITOR_TYPE_TREND_PULLBACK mt = _row_monitor_type(row, default_manual) if mt != default_manual: return mt kst = _row_key_signal_type(row) if kst in ( MONITOR_TYPE_TREND_PULLBACK, TREND_HANDOFF_KEY_SIGNAL, TREND_HANDOFF_TRADE_NOTE, ENTRY_REASON_TREND_PULLBACK, ): return MONITOR_TYPE_TREND_PULLBACK return mt def apply_order_monitor_source_labels(item: dict, *, default_manual: str = "下单监控") -> dict: """实例页 / 中控 API:统一修正 order_monitors 展示用 monitor_type。""" out = dict(item or {}) out["monitor_type"] = order_monitor_source_type(out, default_manual=default_manual) return out def trade_record_monitor_type(conn, order_row, *, default_manual: str = "下单监控") -> str: """平仓写入 trade_records 时:曾顺势加仓则标「顺势加仓」,否则沿用监控单来源类型。""" oid = None try: keys = order_row.keys() if hasattr(order_row, "keys") else [] if "id" in keys and order_row["id"] is not None: oid = int(order_row["id"]) except Exception: oid = None if oid and order_had_roll_fills(conn, oid): return MONITOR_TYPE_ROLL return order_monitor_source_type(order_row, default_manual=default_manual) def entry_reason_for_monitor_type(monitor_type: str | None) -> str: mt = (monitor_type or "").strip() if mt == MONITOR_TYPE_TREND_PULLBACK: return ENTRY_REASON_TREND_PULLBACK if mt == MONITOR_TYPE_ROLL: return ENTRY_REASON_ROLL return ""