# Copyright (c) 2025-2026 马建军. All rights reserved. # 专有软件 — 未经授权禁止复制、传播、转售。 # 严禁用于:带单/代客理财、向他人推荐期货品种或买卖建议、融资配资等业务。 # 详见 LICENSE.zh-CN.txt 与 docs/软件购买与使用协议.md """交易上下文:设置读取、资金、模式。""" from __future__ import annotations from typing import Callable, Optional TRADING_MODE_SIM = "simulation" # SimNow CTP TRADING_MODE_LIVE = "live" # 期货公司 CTP def get_trading_mode(get_setting: Callable[[str, str], str]) -> str: m = (get_setting("trading_mode", TRADING_MODE_SIM) or TRADING_MODE_SIM).strip().lower() return m if m in (TRADING_MODE_SIM, TRADING_MODE_LIVE) else TRADING_MODE_SIM def get_sizing_mode(get_setting: Callable[[str, str], str]) -> str: from modules.trading.position_sizing import normalize_sizing_mode return normalize_sizing_mode(get_setting("position_sizing_mode", "fixed")) def get_fixed_lots(get_setting: Callable[[str, str], str]) -> int: try: return max(1, int(float(get_setting("fixed_lots", "1") or 1))) except (TypeError, ValueError): return 1 def get_fixed_amount(get_setting: Callable[[str, str], str]) -> float: try: return max(1.0, float(get_setting("fixed_amount", "5000") or 5000)) except (TypeError, ValueError): return 5000.0 def get_risk_percent(get_setting: Callable[[str, str], str]) -> float: try: return max(0.1, float(get_setting("risk_percent", "1") or 1)) except (TypeError, ValueError): return 1.0 def get_max_margin_pct(get_setting: Callable[[str, str], str]) -> float: """单笔/总仓位保证金占权益上限(%),默认 30。""" try: return max(1.0, min(100.0, float(get_setting("max_margin_pct", "30") or 30))) except (TypeError, ValueError): return 30.0 def get_roll_max_margin_pct(get_setting: Callable[[str, str], str]) -> float: """滚仓后总保证金占权益上限(%),默认 50。""" try: return max(1.0, min(100.0, float(get_setting("roll_max_margin_pct", "50") or 50))) except (TypeError, ValueError): return 50.0 def get_trailing_be_tick_buffer(get_setting: Callable[[str, str], str]) -> int: """移动保本:止损移至开仓价 ± N 个最小变动价位(默认 2)。""" try: return max(1, min(20, int(float(get_setting("trailing_be_tick_buffer", "2") or 2)))) except (TypeError, ValueError): return 2 def get_pending_order_timeout_min(get_setting: Callable[[str, str], str]) -> int: """开仓限价委托未成交自动撤单时间(分钟),默认 5。""" try: return max(1, min(60, int(float(get_setting("pending_order_timeout_min", "5") or 5)))) except (TypeError, ValueError): return 5 def get_pending_order_timeout_sec(get_setting: Callable[[str, str], str]) -> int: return get_pending_order_timeout_min(get_setting) * 60 def _cached_ctp_account(mode: str) -> dict[str, float]: """CTP 未连接时,用最近一次 worker/持仓快照里的账户权益。""" import json try: from modules.trading.position_stream import position_hub snap = position_hub.get_snapshot() or {} cap = float(snap.get("capital") or 0) avail = snap.get("account_available") if cap > 0 or avail is not None: out: dict[str, float] = {} if cap > 0: out["balance"] = cap if avail is not None: out["available"] = float(avail) return out except Exception: pass try: from modules.core.db_conn import connect_db conn = connect_db() try: row = conn.execute( "SELECT value FROM ctp_worker_snapshots WHERE key='account' LIMIT 1" ).fetchone() finally: conn.close() if row and row["value"]: acc = json.loads(row["value"]) balance = float(acc.get("balance") or 0) available = acc.get("available") out: dict[str, float] = {} if balance > 0: out["balance"] = balance if available is not None: out["available"] = float(available) return out except Exception: pass del mode return {} def _ctp_status_from_snapshot(mode: str) -> Optional[dict]: """读持仓快照中的 CTP 状态,避免页面渲染同步 IPC。""" try: from modules.trading.position_stream import position_hub snap = position_hub.get_snapshot() or {} st = snap.get("ctp_status") if isinstance(st, dict) and st: return st except Exception: pass del mode return None def get_account_capital(conn, get_setting: Callable[[str, str], str]) -> float: """优先读持仓/Worker 快照权益;无快照时才同步问 CTP。""" del conn mode = get_trading_mode(get_setting) cached = _cached_ctp_account(mode) balance = float(cached.get("balance") or 0) if balance > 0: return balance try: from modules.ctp.vnpy_bridge import ctp_status, get_ctp_balance st = ctp_status(mode) if st.get("connected"): bal = get_ctp_balance(mode) if bal and bal > 0: return float(bal) except Exception: pass try: return float(get_setting("live_capital", "0") or 0) except (TypeError, ValueError): return 0.0 def get_recommend_capital(conn, get_setting: Callable[[str, str], str]) -> float: """可开仓品种表用权益:已连接 CTP 用柜台权益,未连接固定 10 万。""" from modules.trading.product_recommend import DISCONNECTED_RECOMMEND_CAPITAL if is_ctp_connected(get_setting): return get_account_capital(conn, get_setting) return float(DISCONNECTED_RECOMMEND_CAPITAL) def is_ctp_connected(get_setting: Callable[[str, str], str]) -> bool: """当前交易模式(SimNow / 实盘)是否已连接 CTP。""" mode = get_trading_mode(get_setting) st = _ctp_status_from_snapshot(mode) if st is not None: return bool(st.get("connected")) try: from modules.ctp.vnpy_bridge import ctp_status return bool(ctp_status(mode).get("connected")) except Exception: return False def trading_mode_label(get_setting: Callable[[str, str], str]) -> str: return "SimNow" if get_trading_mode(get_setting) == TRADING_MODE_SIM else "期货公司实盘"