#!/usr/bin/env python3 """一次性:为 okx/gate/gate_bot 注入与 binance 一致的计仓模式补丁(已 patch 过则跳过)。""" from __future__ import annotations import re from pathlib import Path ROOT = Path(__file__).resolve().parents[1] IMPORT_BLOCK = '''from position_sizing_lib import ( OPEN_SOURCE_KEY_AUTO, OPEN_SOURCE_MANUAL, assert_open_source_allowed, compute_full_margin_sizing, full_margin_requires_flat_position, is_full_margin_mode, leverage_for_full_margin, load_position_sizing_mode, mode_label_zh, ) from key_monitor_full_margin_lib import ( monitor_type_disallowed_in_full_margin, purge_disallowed_key_monitors, ) ''' ENV_LINE = ( "# 计仓模式:risk=以损定仓(默认);full_margin=合约可用×比例全仓杠杆(仅 env 切换,须无仓)\n" "POSITION_SIZING_MODE = load_position_sizing_mode()\n" ) PURGE_FN = ''' def _purge_key_monitors_if_full_margin(): if not is_full_margin_mode(POSITION_SIZING_MODE): return conn = get_db() try: cancel = globals().get("_cancel_fib_monitor_limit") if not callable(cancel): cancel = lambda _row: None purge_disallowed_key_monitors( conn, sizing_mode=POSITION_SIZING_MODE, select_rows=lambda c: c.execute("SELECT * FROM key_monitors").fetchall(), cancel_fib_limit=cancel, delete_monitor=lambda c, kid: c.execute("DELETE FROM key_monitors WHERE id=?", (kid,)), send_wechat=send_wechat_msg, ) conn.commit() except Exception as e: print(f"[full_margin] purge key monitors: {e}", flush=True) finally: conn.close() ''' MARKET_OPEN_GUARD = ''' ok_src, src_msg = assert_open_source_allowed(POSITION_SIZING_MODE, OPEN_SOURCE_KEY_AUTO) if not ok_src: return False, src_msg, None ''' ADD_KEY_GUARD = ''' if is_full_margin_mode(POSITION_SIZING_MODE) and monitor_type_disallowed_in_full_margin(mt): flash( "全仓杠杆模式下不可添加箱体/收敛突破或斐波监控;" "请改用阻力/支撑(仅提醒),或切换 POSITION_SIZING_MODE=risk 并重启(须无持仓)。" ) return redirect("/key_monitor") ''' TEMPLATE_RULE = '''
计仓模式:{{ position_sizing_mode_label }}(仅 .env POSITION_SIZING_MODE,须无仓后重启) {% if position_sizing_mode == 'full_margin' %} |全仓:合约可用×{{ full_margin_buffer_ratio }},BTC/ETH {{ btc_leverage }}x、其它 {{ alt_leverage }}x,单仓;张数按交易所精度 {% else %} |以损定仓:风险 {{ risk_percent }}% {% endif %} |移动保本:下单可勾选关闭;开启时 {{ breakeven_rr_trigger }}R 触发(每 1R 阶梯上移),偏移 {{ breakeven_offset_pct }}%
''' APPS = [ ("crypto_monitor_okx", 4, "_market_open_for_key_monitor", True), ("crypto_monitor_gate", 2, "_market_open_for_key_monitor", True), ("crypto_monitor_gate_bot", 4, None, False), ] def patch_app(app_dir: str, funds_dec: int, market_fn: str | None, has_fib: bool): path = ROOT / app_dir / "app.py" text = path.read_text(encoding="utf-8") if "POSITION_SIZING_MODE" in text: print(f"SKIP {app_dir}/app.py (already patched)") return if "from position_sizing_lib import" not in text: anchor = "from key_monitor_lib import (" if anchor not in text: anchor = "from form_submit_lib import" text = text.replace( anchor, IMPORT_BLOCK + "\n" + anchor, 1, ) else: text = text.replace(anchor, IMPORT_BLOCK + anchor, 1) if "POSITION_SIZING_MODE = load_position_sizing_mode()" not in text: text = text.replace( "AUTO_TRANSFER_BJ_HOUR = int(os.getenv(\"AUTO_TRANSFER_BJ_HOUR\", \"8\"))\n", "AUTO_TRANSFER_BJ_HOUR = int(os.getenv(\"AUTO_TRANSFER_BJ_HOUR\", \"8\"))\n" + ENV_LINE, 1, ) if "_purge_key_monitors_if_full_margin" not in text: text = text.replace("init_db()\n\n\ndef get_db():", "init_db()" + PURGE_FN + "\ndef get_db():", 1) text = text.replace( "install_strategy_trend(app,", "_purge_key_monitors_if_full_margin()\n\ninstall_strategy_trend(app,", 1, ) if market_fn and MARKET_OPEN_GUARD.strip() not in text: text = text.replace( f"def {market_fn}(\n", f"def {market_fn}(\n", 1, ) text = text.replace( ' """\n 与手动', MARKET_OPEN_GUARD + ' """\n 与手动', 1, ) # fallback: after docstring closing if MARKET_OPEN_GUARD.strip() not in text: pat = rf"(def {market_fn}\([^)]+\):\s*\n\s*\"\"\"[^\"\"]*\"\"\"\s*\n)" text = re.sub(pat, r"\1" + MARKET_OPEN_GUARD, text, count=1) if has_fib and ADD_KEY_GUARD.strip() not in text: text = text.replace( ' if mt not in allowed_types:', ADD_KEY_GUARD + ' if mt not in allowed_types:', 1, ) if "if mt not in allowed_types:" in text else text.replace( ' rank, total = _daily_volume_rank(symbol)', ADD_KEY_GUARD + ' rank, total = _daily_volume_rank(symbol)', 1, ) # render_template risk_percent= add template vars if "position_sizing_mode=POSITION_SIZING_MODE" not in text: text = text.replace( "risk_percent=RISK_PERCENT,\n", "risk_percent=RISK_PERCENT,\n" " position_sizing_mode=POSITION_SIZING_MODE,\n" " position_sizing_mode_label=mode_label_zh(POSITION_SIZING_MODE),\n" " open_position_button_label=(\n" ' "开仓(全仓杠杆)" if is_full_margin_mode(POSITION_SIZING_MODE) else "开仓(以损定仓)"\n' " ),\n", 1, ) path.write_text(text, encoding="utf-8") print(f"DONE {app_dir}/app.py (partial — verify add_order block manually if needed)") def patch_template(app_dir: str): tpl = ROOT / app_dir / "templates" / "index.html" if not tpl.exists(): return text = tpl.read_text(encoding="utf-8") if "position_sizing_mode_label" in text: print(f"SKIP {tpl}") return old = re.search( r'
\s*以损定仓:风险 \{\{ risk_percent \}\}%.*?
', text, re.S, ) if old: text = text[: old.start()] + TEMPLATE_RULE + text[old.end() :] text = text.replace( '', '', ) text = text.replace( '', '{% if position_sizing_mode != \'full_margin\' %}\n' ' \n' ' {% endif %}', 1, ) tpl.write_text(text, encoding="utf-8") print(f"DONE {tpl}") def main(): for app_dir, funds, mfn, fib in APPS: patch_app(app_dir, funds, mfn, fib) patch_template(app_dir) if __name__ == "__main__": main()