# Copyright (c) 2025-2026 马建军. All rights reserved. # 专有软件 — 未经授权禁止复制、传播、转售。 # 严禁用于:带单/代客理财、向他人推荐期货品种或买卖建议、融资配资等业务。 # 详见 LICENSE.zh-CN.txt 与 docs/软件购买与使用协议.md """国内期货合约乘数与参考保证金比例(用于估算保证金与风险)。""" import re from typing import Optional DEFAULT_SPEC = {"mult": 10, "margin_rate": 0.10, "tick_size": 1.0} # 参考交易所常见规格(乘数 + 保证金比例 + 最小变动价位) _SPEC_BY_THS: dict[str, dict] = { "ag": {"mult": 15, "margin_rate": 0.14, "tick_size": 1.0}, "au": {"mult": 1000, "margin_rate": 0.10, "tick_size": 0.02}, "cu": {"mult": 5, "margin_rate": 0.10, "tick_size": 10.0}, "al": {"mult": 5, "margin_rate": 0.10}, "zn": {"mult": 5, "margin_rate": 0.10}, "pb": {"mult": 5, "margin_rate": 0.10}, "ni": {"mult": 1, "margin_rate": 0.12}, "sn": {"mult": 1, "margin_rate": 0.12}, "rb": {"mult": 10, "margin_rate": 0.09}, "hc": {"mult": 10, "margin_rate": 0.09}, "ss": {"mult": 5, "margin_rate": 0.11}, "sc": {"mult": 1000, "margin_rate": 0.11}, "fu": {"mult": 10, "margin_rate": 0.11}, "bu": {"mult": 10, "margin_rate": 0.11}, "ru": {"mult": 10, "margin_rate": 0.11}, "sp": {"mult": 10, "margin_rate": 0.10}, "i": {"mult": 100, "margin_rate": 0.11}, "j": {"mult": 100, "margin_rate": 0.12}, "jm": {"mult": 60, "margin_rate": 0.12}, "m": {"mult": 10, "margin_rate": 0.08}, "y": {"mult": 10, "margin_rate": 0.08}, "p": {"mult": 10, "margin_rate": 0.09}, "c": {"mult": 10, "margin_rate": 0.08}, "cs": {"mult": 10, "margin_rate": 0.08}, "jd": {"mult": 10, "margin_rate": 0.09}, "lh": {"mult": 16, "margin_rate": 0.12}, "l": {"mult": 5, "margin_rate": 0.09}, "pp": {"mult": 5, "margin_rate": 0.09}, "v": {"mult": 5, "margin_rate": 0.09}, "eg": {"mult": 10, "margin_rate": 0.09}, "eb": {"mult": 5, "margin_rate": 0.10}, "pg": {"mult": 20, "margin_rate": 0.10}, "RM": {"mult": 10, "margin_rate": 0.08}, "OI": {"mult": 10, "margin_rate": 0.08}, "SR": {"mult": 10, "margin_rate": 0.08}, "CF": {"mult": 5, "margin_rate": 0.08}, "MA": {"mult": 10, "margin_rate": 0.09}, "TA": {"mult": 5, "margin_rate": 0.09}, "FG": {"mult": 20, "margin_rate": 0.10}, "SA": {"mult": 20, "margin_rate": 0.10}, "UR": {"mult": 20, "margin_rate": 0.10}, "SF": {"mult": 5, "margin_rate": 0.10}, "SM": {"mult": 5, "margin_rate": 0.10}, "AP": {"mult": 10, "margin_rate": 0.10}, "CJ": {"mult": 5, "margin_rate": 0.10}, "PK": {"mult": 5, "margin_rate": 0.10}, "IF": {"mult": 300, "margin_rate": 0.12, "tick_size": 0.2}, "IH": {"mult": 300, "margin_rate": 0.12, "tick_size": 0.2}, "IC": {"mult": 200, "margin_rate": 0.12, "tick_size": 0.2}, "IM": {"mult": 200, "margin_rate": 0.12, "tick_size": 0.2}, } _TICK_OVERRIDES: dict[str, float] = { "sc": 0.1, "TA": 2.0, "CF": 5.0, "SF": 2.0, "SM": 2.0, } def get_contract_spec(ths_code: str) -> dict: code = (ths_code or "").strip() m = re.match(r"^([A-Za-z]+)", code) if not m: return dict(DEFAULT_SPEC) letters = m.group(1) spec = _SPEC_BY_THS.get(letters) or _SPEC_BY_THS.get(letters.upper()) or _SPEC_BY_THS.get(letters.lower()) if spec: tick = spec.get("tick_size") if tick is None: tick = _TICK_OVERRIDES.get(letters) or _TICK_OVERRIDES.get(letters.upper()) or 1.0 return {"mult": spec["mult"], "margin_rate": spec["margin_rate"], "tick_size": float(tick)} return dict(DEFAULT_SPEC) def margin_one_lot( ths_code: str, price: float, *, direction: str = "long", trading_mode: str | None = None, ) -> tuple[float, str, dict]: """1 手保证金。CTP 已连接时优先读柜台合约保证金率,否则用本地参考规格估算。 direction 可为 long / short / max(多空费率取较大值,用于可开仓品种表)。 返回 (保证金, 来源 estimate|ctp, 合约规格片段)。 """ spec = get_contract_spec(ths_code) est = 0.0 if price and price > 0: est = round(float(price) * spec["mult"] * spec["margin_rate"], 2) if trading_mode: try: from vnpy_bridge import ctp_estimate_margin_one_lot, ctp_lookup_contract_spec, ctp_status if ctp_status(trading_mode).get("connected"): ctp_margin = ctp_estimate_margin_one_lot( trading_mode, ths_code, float(price), direction=direction, ) if ctp_margin and ctp_margin > 0: merged = dict(spec) ctp_spec = ctp_lookup_contract_spec(trading_mode, ths_code) or {} if ctp_spec.get("mult"): merged["mult"] = ctp_spec["mult"] if ctp_spec.get("tick_size"): merged["tick_size"] = ctp_spec["tick_size"] if ctp_spec.get("margin_rate"): merged["margin_rate"] = ctp_spec["margin_rate"] return float(ctp_margin), "ctp", merged except Exception: pass return est, "estimate", spec def calc_position_metrics( direction: str, entry: float, stop_loss: float, take_profit: float, lots: float, mark_price: Optional[float], capital: float, ths_code: str, ) -> dict: spec = get_contract_spec(ths_code) mult = spec["mult"] margin_rate = spec["margin_rate"] lots = lots or 1.0 margin = entry * mult * lots * margin_rate if direction == "long": risk_amt = max(0.0, (entry - stop_loss) * mult * lots) reward = max(0.0, (take_profit - entry) * mult * lots) float_pnl = (mark_price - entry) * mult * lots if mark_price is not None else None else: risk_amt = max(0.0, (stop_loss - entry) * mult * lots) reward = max(0.0, (entry - take_profit) * mult * lots) float_pnl = (entry - mark_price) * mult * lots if mark_price is not None else None risk_pct = (risk_amt / capital * 100) if capital > 0 else 0.0 pos_pct = (margin / capital * 100) if capital > 0 else 0.0 rr = (reward / risk_amt) if risk_amt > 0 else None float_pct = (float_pnl / margin * 100) if margin > 0 and float_pnl is not None else None return { "mult": mult, "margin_rate": margin_rate, "margin": round(margin, 2), "risk_amount": round(risk_amt, 2), "risk_pct": round(risk_pct, 2), "position_pct": round(pos_pct, 2), "float_pnl": round(float_pnl, 2) if float_pnl is not None else None, "float_pct": round(float_pct, 2) if float_pct is not None else None, "reward_amount": round(reward, 2) if reward else None, "rr_ratio": round(rr, 2) if rr is not None else None, }