"""各交易所 app 模块 → strategy_register 配置(统一工厂)。""" from __future__ import annotations import sys from typing import Any def resolve_trading_app_module(app_module: Any = None) -> Any: """ 须在 login_required 定义之后调用。 PM2 / python app.py 时 __name__ 为 __main__,请传入 sys.modules[__name__]。 """ if app_module is None: main = sys.modules.get("__main__") if main is not None and hasattr(main, "login_required"): m = main else: import inspect m = None for fr in inspect.stack(): g = fr.frame.f_globals if callable(g.get("login_required")) and callable(g.get("get_db")): m = g break if m is None: raise RuntimeError( "策略交易注册失败:请使用 install_strategy_trading(app, repo_root, app_module=sys.modules[__name__])" ) else: m = app_module if not hasattr(m, "login_required"): raise RuntimeError( "策略交易注册须在 login_required 定义之后执行(将 install_strategy_trading 放在 app.py 末尾)" ) return m def build_strategy_config( app_module: Any = None, *, trend_enabled: bool = False, trend_disabled_note: str = "" ) -> dict: m = resolve_trading_app_module(app_module) def get_trading_capital_usdt(conn): if hasattr(m, "get_exchange_capitals"): _, tc = m.get_exchange_capitals(force=True) if tc is not None: return float(tc) if hasattr(m, "get_available_trading_usdt"): snap = m.get_available_trading_usdt() if snap is not None: return float(snap) day = m.get_trading_day(m.app_now()) row = m.ensure_session(conn, day) return float(row["current_capital"]) def get_position(ex_sym, direction): qty = m.get_live_position_contracts(ex_sym, direction) entry = None try: rows = m.exchange.fetch_positions([ex_sym]) for p in rows or []: matcher = getattr(m, "_row_matches_monitor_direction", None) if matcher and not matcher(direction, p): continue contracts = getattr(m, "_position_row_effective_contracts", lambda x: abs(float(x.get("contracts") or 0)))(p) if contracts <= 0: continue coerce = getattr(m, "_coerce_float", None) if coerce: entry = coerce( p.get("entryPrice"), p.get("average"), (p.get("info") or {}).get("entryPrice"), ) if entry: break except Exception: pass return {"contracts": float(qty or 0), "entry_price": entry} def amount_to_precision(ex_sym, amount): try: return float(m.exchange.amount_to_precision(ex_sym, float(amount))) except Exception: return None def price_to_precision(ex_sym, price): try: return float(m.exchange.price_to_precision(ex_sym, float(price))) except Exception: return None def market_add(ex_sym, direction, amount, leverage): return m.place_exchange_order(ex_sym, direction, amount, leverage, stop_loss=None, take_profit=None) def limit_add(ex_sym, direction, amount, price, leverage): m.exchange.set_leverage(int(leverage), ex_sym) side = "buy" if direction == "long" else "sell" params = {} if hasattr(m, "build_gate_order_params"): params = m.build_gate_order_params(direction, reduce_only=False) return m.exchange.create_order(ex_sym, "limit", side, float(amount), float(price), params or None) def replace_tpsl(ex_sym, direction, sl, tp, order_row): row = order_row or {"symbol": ex_sym, "exchange_symbol": ex_sym, "direction": direction} m.replace_active_monitor_tpsl_on_exchange(row, sl, tp) def count_trends(conn): try: return int( conn.execute( "SELECT COUNT(*) FROM trend_pullback_plans WHERE status='active'" ).fetchone()[0] ) except Exception: return 0 note = trend_disabled_note or ( "趋势回调(自动补仓)请在 Gate 趋势机器人实例使用:/strategy/trend" ) return { "exchange_display": getattr(m, "EXCHANGE_DISPLAY_NAME", ""), "trend_enabled": trend_enabled, "trend_disabled_note": note, "login_required": m.login_required, "get_db": m.get_db, "normalize_symbol_input": m.normalize_symbol_input, "normalize_exchange_symbol": m.normalize_exchange_symbol, "get_price": m.get_price, "get_trading_capital_usdt": get_trading_capital_usdt, "get_position": get_position, "amount_to_precision": amount_to_precision, "price_to_precision": price_to_precision, "market_add": market_add, "limit_add": limit_add, "replace_tpsl": replace_tpsl, "ensure_live_ready": m.ensure_exchange_live_ready, "default_risk_percent": float(getattr(m, "RISK_PERCENT", 2)), "default_leverage": m.infer_leverage, "friendly_error": lambda e: m.friendly_exchange_error(e, available_usdt=m.get_available_trading_usdt()) if "friendly_exchange_error" in dir(m) else str(e), "app_now_str": m.app_now_str, "resolve_fill_price": m.resolve_order_entry_price, "price_fmt": m.format_price_for_symbol, "count_active_trend_plans": count_trends if trend_enabled else count_trends, }