"""各交易所 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" if hasattr(m, "build_okx_order_params"): params = m.build_okx_order_params(direction, reduce_only=False) elif hasattr(m, "build_binance_order_params"): params = m.build_binance_order_params(direction, reduce_only=False) elif hasattr(m, "build_gate_order_params"): params = m.build_gate_order_params(direction, reduce_only=False) else: params = {} return m.exchange.create_order( ex_sym, "limit", side, float(amount), float(price), params if params is not None else {} ) 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 def friendly_error(err): fn = getattr(m, "friendly_exchange_error", None) or getattr( m, "friendly_okx_error", None ) if not callable(fn): return str(err) try: snap = m.get_available_trading_usdt() except Exception: snap = None try: return fn(err, available_usdt=snap) except TypeError: return fn(err) def limit_order_status(ex_sym, order_id): fn = getattr(m, "fib_limit_order_status", None) if callable(fn): return fn(ex_sym, order_id) return "unknown" def cancel_limit_order(ex_sym, order_id): fn = getattr(m, "cancel_fib_limit_order", None) if callable(fn): try: return fn(ex_sym, order_id) except Exception: pass if not order_id: return False try: m.exchange.cancel_order(str(order_id), ex_sym) return True except Exception: return False def get_mark_price(symbol): fn = getattr(m, "get_symbol_mark_price", None) or getattr(m, "get_price", None) if not callable(fn): return None try: return fn(symbol) except Exception: return None def wechat_account_label(): fn = getattr(m, "_wechat_account_label", None) if callable(fn): try: return fn() except Exception: pass return getattr(m, "EXCHANGE_DISPLAY_NAME", "") or "" def wechat_direction_text(direction): fn = getattr(m, "_wechat_direction_text", None) if callable(fn): try: return fn(direction) except Exception: pass d = (direction or "long").strip().lower() return "做多" if d == "long" else "做空" def send_wechat(content): fn = getattr(m, "send_wechat_msg", None) if callable(fn): fn(content) note = trend_disabled_note or ( "趋势回调(自动补仓)请在 Gate 趋势机器人实例使用:/strategy/trend" ) return { "app_module": m, "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": friendly_error, "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, "limit_order_status": limit_order_status, "cancel_limit_order": cancel_limit_order, "get_mark_price": get_mark_price, "send_wechat": send_wechat, "format_price": getattr(m, "format_price_for_symbol", None), "wechat_account_label": wechat_account_label, "wechat_direction_text": wechat_direction_text, }