Restructure into modules/ with single-process CTP and config/ layout.
Move business code under modules/, env template to config/, PM2 single qihuo process, and _legacy shims for old imports. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
# 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)
|
||||
if cap > 0:
|
||||
return {"balance": cap}
|
||||
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 "期货公司实盘"
|
||||
Reference in New Issue
Block a user