167 lines
6.3 KiB
Python
167 lines
6.3 KiB
Python
"""各交易所 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 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
|
|
|
|
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)
|
|
|
|
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": 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,
|
|
}
|